diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 00000000000..0152f61ef03 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1 @@ +extends @wordpress/browserslist-config diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 00000000000..16f0015a964 --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,96 @@ +steps: + + - label: "Pull PHP 7.1" + command: "docker pull public.ecr.aws/q7y6e9n2/unit-test-images/php:7.1" + retry: + automatic: true + + - label: "Pull PHP 7.2" + command: "docker pull public.ecr.aws/q7y6e9n2/unit-test-images/php:7.2" + retry: + automatic: true + + - wait + + - label: "Pull PHP 7.3" + command: "docker pull public.ecr.aws/q7y6e9n2/unit-test-images/php:7.3" + retry: + automatic: true + + - label: "Pull PHP 7.4" + command: "docker pull public.ecr.aws/q7y6e9n2/unit-test-images/php:7.4" + retry: + automatic: true + + - wait + + - label: "Pull PHP 8.0" + command: "docker pull public.ecr.aws/q7y6e9n2/unit-test-images/php:8.0" + retry: + automatic: true + + - label: "Pull MariaDB" + command: "docker pull mariadb:10.4" + retry: + automatic: true + + - wait + + - env: + TEST_INPLACE: "0" + TEST_PHP_VERSION: "{{matrix.php}}" + TEST_WP_VERSION: "{{matrix.wp}}" + WP_MULTISITE: "{{matrix.multisite}}" + COMPOSE_HTTP_TIMEOUT: 180 + DOCKER_CLIENT_TIMEOUT: 180 + label: 'PHP: "{{matrix.php}}" | WP: "{{matrix.wp}}" | Multisite: {{matrix.multisite}}' + plugins: + - docker-compose#v4.14.0: + config: docker-compose-phpunit.yml + propagate-environment: true + propagate-uid-gid: true + pull-retries: 3 + run: wordpress + matrix: + setup: # Max of 6 Dimensions. + php: # Max of 20 elements. + - "8.0" + - "7.4" + wp: + - "6.1.1" + - "latest" + multisite: + - "0" + adjustments: # Max of 12 adjustments. + - with: + php: "8.0" + wp: "latest" + multisite: "1" + - with: + php: "7.1" + wp: "5.4.12" + multisite: "0" + - with: + php: "7.1" + wp: "5.5.11" + multisite: "0" + - with: + php: "7.1" + wp: "5.6.10" + multisite: "0" + - with: + php: "7.1" + wp: "5.7.8" + multisite: "0" + - with: + php: "7.1" + wp: "5.8.6" + multisite: "0" + - with: + php: "7.2" + wp: "5.9.5" + multisite: "0" + - with: + php: "7.3" + wp: "6.0.3" + multisite: "0" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..af21c665605 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# WordPress Coding Standards +# https://make.wordpress.org/core/handbook/coding-standards/ + +# Based from https://github.com/ntwb/wp.editorconfig/blob/master/.editorconfig +# +# Need an IDE plugin? http://editorconfig.org/#download + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = tab + +[*.json] +indent_style = space +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.txt,wp-config-sample.php] +end_of_line = crlf diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000000..148e6e422e8 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +node_modules +vendor +public +assets/js/*.js +assets/js/*.js.map diff --git a/.gitattributes b/.gitattributes new file mode 100755 index 00000000000..b3f85ecd8a8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Items to ignore when downloading a zip + +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.jshintrc export-ignore +/bin/ export-ignore +apigen.neon export-ignore +composer.json export-ignore +Gruntfile.js export-ignore +package.json export-ignore +phpunit.xml export-ignore +readme.txt export-ignore +/tests/ export-ignore +docker-compose-phpunit.yml export-ignore +/.buildkite/ export-ignore +/.github/ export-ignore diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..aed79af02ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: type-bug, workflow-pending-triage, workflow-needs-replication +assignees: '' + +--- + +## Bug Report +### Expected behavior +When I do X, I expect Y to happen. + +### Actual behavior +When I do X, Z happens. + +### Steps to reproduce the behavior +1) +2) +3) +etc, etc + +### Information (if a specific version is affected): +PHP Version: + +EDD Version (or branch): + +WordPress Version: + +Any other relevant information: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..17ad8bd9131 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,13 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: type-request, workflow-pending-triage +assignees: 'cklosowski' + +--- + +## Enhancement Request +### Explain your enhancement (please be detailed) + +### Justification or use case diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 00000000000..f0d28957ba1 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,33 @@ +# Instructions (START): +The purpose of this template is to standardize GitHub issues for bug reports and enhancement requests. Please follow the instructions below. +1. Determine whether your new issue is a "Bug Report" or "Enhancement Request." +2. Based on your issue type, delete the unneeded template section below. For example, if you are submitting a bug report, find the entire "Enhancement Request" section of the template and delete it. +3. Delete this entire block of instructions, from START to END, and fill out the sections to describe your reason for opening an issue. +# Instructions (END): + +## Bug Report +### Expected behavior +When I do X, I expect Y to happen. + +### Actual behavior +When I do X, Z happens. + +### Steps to reproduce the behavior +1) +2) +3) +etc, etc + +### Information (if a specific version is affected): +PHP Version: + +EDD Version (or branch): + +WordPress Version: + +Any other relevant information: + +## Enhancement Request +### Explain your enhancement (please be detailed) + +### Justification or use case diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..311303791e9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ +Fixes # + +Proposed Changes: +1. +2. +3. + +_Please do not submit PRs with minified CSS or JS files. This is managed at the time of release by the Core Team_ diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 96c27495ebb..e96bb716925 --- a/.gitignore +++ b/.gitignore @@ -1,167 +1,40 @@ -################# -## Eclipse -################# - -*.pydevproject -.project -.metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.classpath -.settings/ -.loadpath - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# PDT-specific -.buildpath - - -################# -## Visual Studio -################# - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates - -# Build results -[Dd]ebug/ -[Rr]elease/ -*_i.c -*_p.c -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.vspscc -.builds -*.dotCover - -## TODO: If you have NuGet Package Restore enabled, uncomment this -#packages/ - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf - -# Visual Studio profiler -*.psess -*.vsp - -# ReSharper is a .NET coding add-in -_ReSharper* - -# Installshield output folder -[Ee]xpress - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish - -# Others -[Bb]in -[Oo]bj -sql -TestResults -*.Cache -ClientBin -stylecop.* -~$* -*.dbmdl -Generated_Code #added for RIA/Silverlight projects - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML - - - -############ -## Windows -############ - -# Windows image file caches -Thumbs.db - -# Folder config file -Desktop.ini - - -############# -## Python -############# - -*.py[co] - -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox - -#Translations -*.mo - -#Mr Developer -.mr.developer.cfg - -# Mac crap -.DS_Store - -# Sublime -*.sublime-project -*.sublime-workspace \ No newline at end of file +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + +# Sublime +*.sublime-project +*.sublime-workspace + +# PhpStorm +.idea +Easy-Digital-Downloads.iml + +# Eclipse +*.pydevproject +.project +.metadata +/build +tmp/ +tests/clover.xml + +# Node +node_modules + +# Temporary files +*~ + +# Stripe +includes/gateways/stripe + +# Blocks +includes/blocks diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000000..792f8eb0962 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,15 @@ +{ + "boss": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "noarg": true, + "quotmark": "single", + "undef": true, + "unused": true, + "browser": true, + "globals": { + "jQuery": false, + "edd_scripts": false + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 00000000000..83880470e6c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,85 @@ +## Contribute To Easy Digital Downloads + +Community made patches, localisations, bug reports and contributions are always welcome and are crucial to ensure Easy Digital Downloads remains the #1 eCommerce platform for digital goods on WordPress. + +When contributing please ensure you follow the guidelines below so that we can keep on top of things. + +__Please Note:__ GitHub is for bug reports and contributions only - if you have a support question or a request for a customization don't post here, go to our [Support page](https://easydigitaldownloads.com/support/) instead. + +## Getting Started + +* __Do not report potential security vulnerabilities here. Email them privately to our security team at [security@easydigitaldownloads.com](mailto:security@easydigitaldownloads.com)__ +* Before submitting a ticket, please be sure to replicate the behavior with no other plugins active and on a base theme like Twenty Seventeen. +* Submit a ticket for your issue, assuming one does not already exist. + * Raise it on our [Issue Tracker](https://github.com/easydigitaldownloads/Easy-Digital-Downloads/issues) + * Clearly describe the issue including steps to reproduce the bug. + * Make sure you fill in the earliest version that you know has the issue as well as the version of WordPress you're using. + +## Making Changes + +* Fork the repository on GitHub +* Make the changes to your forked repository + * Ensure you stick to the [WordPress Coding Standards](https://codex.wordpress.org/WordPress_Coding_Standards) +* When committing, reference your issue (if present) and include a note about the fix +* If possible, and if applicable, please also add/update unit tests for your changes +* Push the changes to your fork and submit a pull request to the 'master' branch of the EDD repository + +## Code Documentation + +* We ensure that every EDD function is documented well and follows the standards set by phpDoc +* An example function can be found [here](https://gist.github.com/sunnyratilal/5308969) +* Please make sure that every function is documented so that when we update our API Documentation things don't go awry! + * If you're adding/editing a function in a class, make sure to add `@access {private|public|protected}` +* Finally, please use tabs and not spaces. The tab indent size should be 4 for all EDD code. + +At this point you're waiting on us to merge your pull request. We'll review all pull requests, and make suggestions and changes if necessary. + +## Developer Certificate of Origin +By contributing to Easy Digital Downloads, you agree to the Developer Certificate of Origin. + +In its simplest form, the DCO states that you have permission to supply the code submitted to Easy Digital Downloads. Here is the DCO in detail: +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +# Additional Resources +* [EDD Developer's API](https://easydigitaldownloads.com/docs/developers-intro-to-easy-digital-downloads/) +* [General GitHub Documentation](https://help.github.com/) +* [GitHub Pull Request documentation](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) +* [PHPUnit Tests Guide](https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html) diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000000..50ba00a309e --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,210 @@ +module.exports = function ( grunt ) { + // Load multiple grunt tasks using globbing patterns + require( 'load-grunt-tasks' )( grunt ); + + // Project configuration. + grunt.initConfig( { + + pkg: grunt.file.readJSON( 'package.json' ), + + checktextdomain: { + options: { + text_domain: 'easy-digital-downloads', + correct_domain: true, + keywords: [ + '__:1,2d', + '_e:1,2d', + '_x:1,2c,3d', + 'esc_html__:1,2d', + 'esc_html_e:1,2d', + 'esc_html_x:1,2c,3d', + 'esc_attr__:1,2d', + 'esc_attr_e:1,2d', + 'esc_attr_x:1,2c,3d', + '_ex:1,2c,3d', + '_n:1,2,3,4d', + '_nx:1,2,4c,5d', + '_n_noop:1,2,3d', + '_nx_noop:1,2,3c,4d', + ' __ngettext:1,2,3d', + '__ngettext_noop:1,2,3d', + '_c:1,2d', + '_nc:1,2,4c,5d', + ], + }, + files: { + src: [ + '**/*.php', // Include all files + '!node_modules/**', // Exclude node_modules/ + '!build/**', // Exclude build/ + ], + expand: true, + }, + }, + + // Clean up build directory + clean: { + main: [ 'build/**' ], + }, + + // Copy the plugin into the build directory + copy: { + pro: { + src: [ + 'assets/sample-products-import.xml', + 'assets/css/*.min.css', + 'assets/css/admin/style.css', + 'assets/js/*.js', + 'assets/js/*.min.js', + 'assets/js/vendor/**', + 'assets/images/**', + 'i18n/**', + 'includes/**', + 'languages/**', + 'templates/**', + 'vendor/**', + 'src/**', + '*.php', + '*.txt', + 'assets/pro/js/*.js', + '!src/Lite/**', + '!assets/lite/**' + ], + dest: 'build/<%= pkg.name %>-pro/', + }, + lite: { + src: [ + 'assets/sample-products-import.xml', + 'assets/css/*.min.css', + 'assets/css/admin/style.css', + 'assets/js/*.js', + 'assets/js/*.min.js', + 'assets/js/vendor/**', + 'assets/images/**', + 'i18n/**', + 'includes/**', + '!includes/blocks/pro/**', + '!includes/blocks/assets/pro/**', + '!includes/blocks/build/pro/**', + 'languages/**', + 'templates/**', + 'vendor/**', + 'src/**', + '*.php', + '*.txt', + '!src/Pro/**', + '!assets/pro/**', + 'assets/lite/**' + ], + dest: 'build/<%= pkg.name %>/', + }, + }, + + // Compress build directory into .zip and -.zip + compress: { + pro: { + options: { + mode: 'zip', + archive: './build/<%= pkg.name %>-pro-<%= pkg.version %>.zip', + }, + expand: true, + cwd: 'build/<%= pkg.name %>-pro/', + src: [ '**/*' ], + dest: '<%= pkg.name %>-pro/', + }, + lite: { + options: { + mode: 'zip', + archive: './build/<%= pkg.name %>-<%= pkg.version %>.zip', + }, + expand: true, + cwd: 'build/<%= pkg.name %>/', + src: [ '**/*' ], + dest: '<%= pkg.name %>/', + }, + }, + + replace: { + stripe: { + options: { + patterns: [ + { + match: /edd_stripe_bootstrap/g, + replacement: 'edd_stripe_core_bootstrap', + expression: true, + }, + { + match: /remove_action(.*);/g, + replacement: '', + expression: true, + } + ] + }, + files: [ + { + expand: true, + flatten: true, + src: [ 'includes/gateways/stripe/edd-stripe.php' ], + dest: 'includes/gateways/stripe' + } + ] + }, + blocks: { + options: { + patterns: [ + { + match: /init_blocks/g, + replacement: 'init_core_blocks', + expression: true, + }, + { + match: /update_required_pages/g, + replacement: 'update_core_required_pages', + expression: true, + }, + { + match: /remove_action(.*);/g, + replacement: '', + expression: true, + }, + { + match: /remove_filter(.*);/g, + replacement: '', + expression: true, + }, + ] + }, + files: [ + { + expand: true, + flatten: true, + src: [ 'includes/blocks/edd-blocks.php' ], + dest: 'includes/blocks' + } + ] + }, + pro: { + options: { + patterns: [ + { + match: /Plugin Name: Easy Digital Downloads \(Pro\)/g, + replacement: 'Plugin Name: Easy Digital Downloads', + expression: true, + } + ] + }, + files: [ + { + expand: true, + flatten: true, + src: [ 'build/easy-digital-downloads/easy-digital-downloads.php' ], + dest: 'build/easy-digital-downloads' + } + ] + } + } + } ); + + // Build task(s). + grunt.registerTask( 'prep', [ 'clean', 'force:checktextdomain' ] ); +}; diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 00000000000..b4c4aae8e1c --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,13 @@ +Getting help with Easy Digital Downloads, EDD themes and EDD addons +======================== + +We look forward to helping resolve any issues or relaying any feedback or ideas you have about Easy Digital Downloads. To report an issue, receive support, give us feedback, or send us an idea please open a support ticket. + +Support Ticket +------ + +We recommend all users with support questions email us via the support form found at [easydigitaldownloads.com/support/](https://easydigitaldownloads.com/support/). GitHub is used for core development only, and is not the place to seek help or report non-developer issues for Easy Digital Downloads, or EDD addons or themes. + +Before opening a support ticket, please also review our [documentation](https://easydigitaldownloads.com/docs) for assistance with common issues and FAQs. + +If reporting a bug, please be as descriptive as possible and include links to screenshots or screenshares that demonstrate the issue. diff --git a/apigen.neon b/apigen.neon new file mode 100644 index 00000000000..aa731881132 --- /dev/null +++ b/apigen.neon @@ -0,0 +1,4 @@ +source: + - ./ + +destination: codex diff --git a/assets/banner-772x250.png b/assets/banner-772x250.png deleted file mode 100755 index d42741fd210..00000000000 Binary files a/assets/banner-772x250.png and /dev/null differ diff --git a/assets/css/admin/chosen/_base.scss b/assets/css/admin/chosen/_base.scss new file mode 100644 index 00000000000..2a06f225efc --- /dev/null +++ b/assets/css/admin/chosen/_base.scss @@ -0,0 +1,43 @@ +.chosen-container { + font-size: 14px; +} + +.chosen-container-multi .search-field, +.chosen-container-single .chosen-single, +.chosen-container-active.chosen-with-drop .chosen-single { + background: $white url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E) no-repeat right 5px top 55%; + background-size: 16px 16px; + border: 1px solid $wp-input-border; + box-shadow: 0 0 0 transparent; + color: $wp-input-text; +} + +.chosen-container-multi .chosen-choices, +.chosen-container-single .chosen-single { + border-radius: 4px; + border-color: $wp-input-border; +} + +.chosen-container-multi.chosen-with-drop .chosen-choices, +.chosen-container-active.chosen-with-drop .chosen-single { + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); +} + +.chosen-container-single.chosen-container-active.chosen-with-drop.chosen-dropup .chosen-single, +.chosen-container-multi.chosen-container-active.chosen-dropup .chosen-choices { + border-radius: 0 0 4px 4px; + border-width: 0 1px 0 1px; + z-index: 1011; +} + +.chosen-container .chosen-drop { + position: absolute; + top: 100%; + z-index: 1010; + width: 100%; + border-radius: 0 0 4px 4px; + border-width: 0 1px 0 1px; + border-color: transparent; + background: $white; + outline: 2px solid transparent; +} diff --git a/assets/css/admin/chosen/_colors.scss b/assets/css/admin/chosen/_colors.scss new file mode 100644 index 00000000000..efe11f31333 --- /dev/null +++ b/assets/css/admin/chosen/_colors.scss @@ -0,0 +1,24 @@ + +/* WordPress Color Schemes */ + +:root { + @include edd-admin-colors(); +} + +.chosen-container.chosen-container-active .chosen-single, +.chosen-container.chosen-container-active .chosen-choices, +.chosen-with-drop.chosen-dropup .chosen-drop { + border-color: var(--wp-admin-theme-color); + box-shadow: 0 0 0 1px var(--wp-admin-theme-color); +} + +.chosen-with-drop.chosen-dropup .chosen-single, +.chosen-container-active.chosen-dropup .chosen-choices, +.chosen-container .chosen-drop { + border-color: var(--wp-admin-theme-color); + box-shadow: 0 1px 0 1px var(--wp-admin-theme-color); +} + +.chosen-container .chosen-results li.highlighted { + background-color: var(--wp-admin-theme-color); +} diff --git a/assets/css/admin/chosen/_edd.scss b/assets/css/admin/chosen/_edd.scss new file mode 100644 index 00000000000..16c112d3c72 --- /dev/null +++ b/assets/css/admin/chosen/_edd.scss @@ -0,0 +1,20 @@ +.edd-select-chosen { + width: 100%; + max-width: 300px; +} + +.edd-select-chosen.edd-customer-select { + width: 100% !important; +} + +.edd-select-chosen.edd-time { + width: 55px; + max-width: 55px; +} + +@media screen and (max-width: $break-medium) { + .edd-select-chosen.edd-time { + width: 70px; + max-width: 70px; + } +} diff --git a/assets/css/admin/chosen/_multi.scss b/assets/css/admin/chosen/_multi.scss new file mode 100644 index 00000000000..aeb61573d67 --- /dev/null +++ b/assets/css/admin/chosen/_multi.scss @@ -0,0 +1,67 @@ +.chosen-container-multi { + .search-field { + border: none; + } + + &.chosen-with-drop .chosen-choices { + border-bottom-color: transparent; + } + + .chosen-choices li.search-field { + min-width: 100px !important; + width: 100%; + } + + .chosen-choices li.search-field input[type=text] { + color: $wp-input-text; + font-family: unset; + height: unset; + margin: 0; + padding: 3px 10px; + width: 100% !important; + } + + .spinner { + position: absolute; + top: 4px; + right: -4px; + } + + .chosen-choices li.search-choice { + margin: 3px 5px 3px 0; + padding: 5px 22px 5px 5px; + border: 1px solid $wp-input-border; + max-width: 100%; + border-radius: 4px; + background: #f4f4f4; + box-shadow: none; + color: $wp-input-text; + cursor: default; + } + + .chosen-choices li.search-choice .search-choice-close { + position: absolute; + top: 4px; + right: 3px; + display: block; + width: 15px; + height: 15px; + } + + .chosen-choices li.search-choice .search-choice-close:before { + height: 15px; + width: 15px; + position: absolute; + top: 0; + right: 0; + color: $wp-input-text; + font-family: 'dashicons'; + content: '\f158'; + font-size: 15px; + line-height: 1; + } + + .chosen-choices li.search-choice .search-choice-close:hover:before { + color: $wp-text; + } +} diff --git a/assets/css/admin/chosen/_results.scss b/assets/css/admin/chosen/_results.scss new file mode 100644 index 00000000000..bc0fa829eff --- /dev/null +++ b/assets/css/admin/chosen/_results.scss @@ -0,0 +1,16 @@ + +.chosen-container .chosen-results { + color: $wp-text; + position: relative; + overflow-x: hidden; + overflow-y: auto; + margin: 0 4px 4px 0; + padding: 0 0 0 4px; + max-height: 240px; +} + +.chosen-container .chosen-results li.highlighted { + background-image: none; + border-radius: 4px; + color: $white; +} diff --git a/assets/css/admin/chosen/_single.scss b/assets/css/admin/chosen/_single.scss new file mode 100644 index 00000000000..d3cba7e73d0 --- /dev/null +++ b/assets/css/admin/chosen/_single.scss @@ -0,0 +1,54 @@ +.chosen-container-single .chosen-single div b { + background-image: none; +} + +.chosen-container-single .chosen-search:after { + display: block; + position: absolute; + right: 6px; + top: 50%; + font-family: dashicons; + font-size: 17px; + content: '\f179'; + transform: translateY(-50%); +} + +.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single { + border-radius: 4px 4px 0 0; +} + +.chosen-container-single .chosen-single div { + width: 26px; +} + +.chosen-container-single .chosen-default { + color: $wp-input-text; +} + +.chosen-container-active .chosen-single { + border-color: transparent; + outline: 2px solid transparent; +} + +.chosen-container-single .chosen-search input[type=text] { + margin: 1px 0; + padding: 4px 20px 4px 5px; + width: 100% !important; + height: auto; + outline: 0; + border: 1px solid $wp-input-border; + border-radius: 4px; + line-height: normal; + box-shadow: inset 0 1px 2px rgba( 0, 0, 0, 0.07 ); +} +.chosen-container-single .chosen-single, +.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single { + min-height: 30px; + + @media screen and (max-width: $break-medium) { + font-size: 16px; + line-height: 1.625; + min-height: 40px; + padding: 5px 8px; + } +} diff --git a/assets/css/admin/chosen/style.scss b/assets/css/admin/chosen/style.scss new file mode 100644 index 00000000000..e35dbe54877 --- /dev/null +++ b/assets/css/admin/chosen/style.scss @@ -0,0 +1,17 @@ +/* Chosen styles +-------------------------------------------------------------- */ + +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/variables"; +@import "~@wordpress/base-styles/mixins"; +@import "~@wordpress/base-styles/breakpoints"; +@import "~@wordpress/base-styles/animations"; +@import "../../variables/colors"; +@import "../../variables/mixins"; + +@import 'base'; +@import 'single'; +@import 'multi'; +@import 'results'; +@import 'colors'; +@import 'edd'; diff --git a/assets/css/admin/datepicker.scss b/assets/css/admin/datepicker.scss new file mode 100644 index 00000000000..c9fb7204709 --- /dev/null +++ b/assets/css/admin/datepicker.scss @@ -0,0 +1,349 @@ +/* Date Picker Default Styles */ +.edd-datepicker { + padding: 0; + margin: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + background-color: #fff; + border: 1px solid #dfdfdf; + border-top: none; + -webkit-box-shadow: 0 3px 6px rgba(0, 0, 0, 0.075); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.075); + min-width: 17em; + width: auto; + z-index: 1000 !important; +} + +.edd-datepicker * { + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.edd-datepicker table { + font-size: 13px; + margin: 0; + border: none; + border-collapse: collapse; +} + +.edd-datepicker .ui-widget-header, +.edd-datepicker .ui-datepicker-header { + background-image: none; + border: none; + color: #fff; + font-weight: normal; + padding: .2em 0; +} + +.edd-datepicker .ui-datepicker-header .ui-state-hover { + background: transparent; + border-color: transparent; + cursor: pointer; +} + +.edd-datepicker .ui-datepicker-title { + margin: 0; + padding: 10px 0; + color: #fff; + font-size: 14px; + line-height: 14px; + text-align: center; +} + +.edd-datepicker .ui-datepicker-prev, +.edd-datepicker .ui-datepicker-next { + position: relative; + top: 0; + height: 34px; + width: 34px; +} + +.edd-datepicker .ui-state-hover.ui-datepicker-prev, +.edd-datepicker .ui-state-hover.ui-datepicker-next { + border: none; +} + +.edd-datepicker .ui-datepicker-prev, +.edd-datepicker .ui-datepicker-prev-hover { + left: 0; +} + +.edd-datepicker .ui-datepicker-next, +.edd-datepicker .ui-datepicker-next-hover { + right: 0; +} + +.edd-datepicker .ui-datepicker-next span, +.edd-datepicker .ui-datepicker-prev span { + display: none; +} + +.edd-datepicker .ui-datepicker-prev { + float: left; +} + +.edd-datepicker .ui-datepicker-next { + float: right; +} + +.edd-datepicker .ui-datepicker-prev:before, +.edd-datepicker .ui-datepicker-next:before { + font: normal 20px/34px 'dashicons'; + padding-left: 7px; + color: #fff; + speak: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 34px; + height: 34px; +} + +.edd-datepicker .ui-datepicker-prev:before { + content: '\f341'; +} + +.edd-datepicker .ui-datepicker-next:before { + content: '\f345'; +} + +.edd-datepicker .ui-datepicker-prev-hover:before, +.edd-datepicker .ui-datepicker-next-hover:before { + opacity: 0.7; +} + +.edd-datepicker select.ui-datepicker-month, +.edd-datepicker select.ui-datepicker-year { + width: 33%; +} + +.edd-datepicker thead { + color: #fff; + font-weight: 600; +} + +.edd-datepicker th { + padding: 10px; +} + +.edd-datepicker td { + padding: 0; + border: 1px solid #f4f4f4; +} + +.edd-datepicker td.ui-datepicker-other-month { + border: transparent; +} + +.edd-datepicker tr:first-of-type td { + border-top: 1px solid #f0f0f0; +} + +.edd-datepicker td.ui-datepicker-week-end { + background-color: #f4f4f4; + border: 1px solid #f0f0f0; +} + +.edd-datepicker td.ui-datepicker-today { + background-color: #f0f0c0; +} + +.edd-datepicker td.ui-datepicker-current-day { + background: #bbdd88; +} + +.edd-datepicker td .ui-state-default { + background: transparent; + border: none; + text-align: center; + text-decoration: none; + width: auto; + display: block; + padding: 5px 10px; + font-weight: normal; + color: #444; +} + +.edd-datepicker td.ui-state-disabled .ui-state-default { + opacity: 0.5; +} + +/* Default Color Scheme */ +.edd-datepicker .ui-widget-header, +.edd-datepicker .ui-datepicker-header { + background: #00a0d2; +} + +.edd-datepicker thead { + background: #32373c; +} + +.edd-datepicker td .ui-state-hover { + background: #0073aa; + color: #fff; +} + +/* WordPress Color Schemes */ + +/* Fresh */ +.admin-color-fresh .edd-datepicker .ui-widget-header, +.admin-color-fresh .edd-datepicker .ui-datepicker-header { + background: #00a0d2; +} + +.admin-color-fresh .edd-datepicker thead { + background: #32373c; +} + +.admin-color-fresh .edd-datepicker td .ui-state-hover { + background: #0073aa; + color: #fff; +} + +/* Blue */ +.admin-color-blue .edd-datepicker .ui-widget-header, +.admin-color-blue .edd-datepicker .ui-datepicker-header { + background: #52accc; +} + +.admin-color-blue .edd-datepicker thead { + background: #4796b3; +} + +.admin-color-blue .edd-datepicker td .ui-state-hover { + background: #096484; + color: #fff; +} + +/* Coffee */ +.admin-color-coffee .edd-datepicker .ui-widget-header, +.admin-color-coffee .edd-datepicker .ui-datepicker-header { + background: #59524c; +} + +.admin-color-coffee .edd-datepicker thead { + background: #46403c; +} + +.admin-color-coffee .edd-datepicker td .ui-state-hover { + background: #c7a589; + color: #fff; +} + +/* Ectoplasm */ +.admin-color-ectoplasm .edd-datepicker .ui-widget-header, +.admin-color-ectoplasm .edd-datepicker .ui-datepicker-header { + background: #523f6d; +} + +.admin-color-ectoplasm .edd-datepicker thead { + background: #413256; +} + +.admin-color-ectoplasm .edd-datepicker td .ui-state-hover { + background: #a3b745; + color: #fff; +} + +/* Midnight */ +.admin-color-midnight .edd-datepicker .ui-widget-header, +.admin-color-midnight .edd-datepicker .ui-datepicker-header { + background: #363b3f; +} + +.admin-color-midnight .edd-datepicker thead { + background: #26292c; +} + +.admin-color-midnight .edd-datepicker td .ui-state-hover { + background: #e14d43; + color: #fff; +} + +/* Ocean */ +.admin-color-ocean .edd-datepicker .ui-widget-header, +.admin-color-ocean .edd-datepicker .ui-datepicker-header { + background: #738e96; +} + +.admin-color-ocean .edd-datepicker thead { + background: #627c83; +} + +.admin-color-ocean .edd-datepicker td .ui-state-hover { + background: #9ebaa0; + color: #fff; +} + +/* Sunrise */ +.admin-color-sunrise .edd-datepicker .ui-widget-header, +.admin-color-sunrise .edd-datepicker .ui-datepicker-header, +.admin-color-sunrise .edd-datepicker .ui-datepicker-header .ui-state-hover { + background: #cf4944; +} + +.admin-color-sunrise .edd-datepicker th { + border-color: #be3631; + background: #be3631; +} + +.admin-color-sunrise .edd-datepicker td .ui-state-hover { + background: #dd823b; + color: #fff; +} + +/* Light */ +.admin-color-light .edd-datepicker .ui-widget-header, +.admin-color-light .edd-datepicker .ui-datepicker-header { + background: #e5e5e5; +} + +.admin-color-light .edd-datepicker thead { + background: #888; +} + +.admin-color-light .edd-datepicker .ui-datepicker-title, +.admin-color-light .edd-datepicker td .ui-state-default, +.admin-color-light .edd-datepicker .ui-datepicker-prev:before, +.admin-color-light .edd-datepicker .ui-datepicker-next:before { + color: #555; +} + +.admin-color-light .edd-datepicker td .ui-state-hover { + background: #e5e5e5; +} + +/* bbPress Color Schemes */ + +/* Evergreen */ +.admin-color-bbp-evergreen .edd-datepicker .ui-widget-header, +.admin-color-bbp-evergreen .edd-datepicker .ui-datepicker-header { + background: #56b274; +} + +.admin-color-bbp-evergreen .edd-datepicker thead { + background: #36533f; +} + +.admin-color-bbp-evergreen .edd-datepicker td .ui-state-hover { + background: #446950; + color: #fff; +} + +/* Mint */ +.admin-color-bbp-mint .edd-datepicker .ui-widget-header, +.admin-color-bbp-mint .edd-datepicker .ui-datepicker-header { + background: #4ca26a; +} + +.admin-color-bbp-mint .edd-datepicker thead { + background: #4f6d59; +} + +.admin-color-bbp-mint .edd-datepicker td .ui-state-hover { + background: #5fb37c; + color: #fff; +} diff --git a/assets/css/admin/downloads/_duplicate.scss b/assets/css/admin/downloads/_duplicate.scss new file mode 100644 index 00000000000..eb055bd6c7d --- /dev/null +++ b/assets/css/admin/downloads/_duplicate.scss @@ -0,0 +1,6 @@ +#edd-duplicate-action { + & ~ #publishing-action { + position: relative; + top: -10px; + } +} diff --git a/assets/css/admin/downloads/_prices.scss b/assets/css/admin/downloads/_prices.scss new file mode 100644 index 00000000000..36f7a090a16 --- /dev/null +++ b/assets/css/admin/downloads/_prices.scss @@ -0,0 +1,41 @@ + +.edd-custom-price-option { + &-sections-wrap { + display: none; + border-width: 0 1px 1px; + border-style: solid; + border-color: $wp-border; + box-sizing: border-box; + width: 100%; + } + + &-section { + display: block; + padding: 10px 8px; + border-bottom: 1px solid rgba( 222, 222, 222, 0.3 ); + + &-title { + display: block; + font-weight: 600; + padding: 0 0 10px; + } + + &-content { + display: flex; + gap: 12px; + margin-bottom: 6px; + } + + &:last-child { + border-bottom: none; + } + } +} + +.toggle-custom-price-option-section { + color: $wp-gray-40; + + &:hover { + color: $wp-text; + } +} diff --git a/assets/css/admin/downloads/_product_settings.scss b/assets/css/admin/downloads/_product_settings.scss new file mode 100644 index 00000000000..a9953abb5b3 --- /dev/null +++ b/assets/css/admin/downloads/_product_settings.scss @@ -0,0 +1,30 @@ +#edd_product_settings { + + .edd-product-options__title, + .inside strong { + border-top: 1px solid $wp-border; + border-bottom: 1px solid $wp-border; + background-color: $wp-alternate; + display: flex; + font-weight: 600; + margin: 0 -12px 16px; + padding: 8px 12px; + justify-content: space-between; + align-items: center; + } + + .edd-product-options-wrapper:first-of-type .edd-product-options__title, + .inside div:first-child strong { + margin-top: -8px; + } + + .edd-product-options__title .edd-help-tip, + .inside strong .edd-help-tip { + font-size: 20px; + } + + .label--block { + display: block; + margin: 0 0 4px; + } +} diff --git a/assets/css/admin/downloads/_repeatable_row.scss b/assets/css/admin/downloads/_repeatable_row.scss new file mode 100644 index 00000000000..2e4ae057f71 --- /dev/null +++ b/assets/css/admin/downloads/_repeatable_row.scss @@ -0,0 +1,188 @@ +.edd_repeatable_row.ui-sortable-placeholder { + line-height: 0; + padding: 0; + margin: 0; + box-sizing: border-box; + border: 1px dashed $wp-border; + visibility: visible !important; +} + +.edd-add-repeatable-row { + border-top: 1px solid $wp-border; + padding: 12px; + margin: 15px -12px -12px -12px; + display: flex; + justify-content: flex-end; + align-items: center; +} + +.edd_repeatable_row input[type="text"].large-text { + width: 100%; +} + +.edd_variable_prices_wrapper:not(:first-child), +.edd_repeatable_upload_wrapper:not(:first-child) { + margin-top: 12px; +} + +.edd_repeatable_row.ui-sortable-helper .edd-repeatable-row-actions .edd-remove-row { + display: none; +} + +.edd-repeatable-row-actions { + color: $wp-gray-40; + + a { + text-decoration: none; + width: auto; + cursor: pointer; + } +} + +.edd-repeatable-row-header, +.edd-bundle-products-header { + clear: both; + background: $wp-gray-0; + border: 1px solid $wp-border; + display: flex; + justify-content: space-between; +} + +.edd-repeatable-row-header { + cursor: move; +} + +.edd_repeatable_row:hover .edd-repeatable-row-header, +.edd_repeatable_row:hover .edd-repeatable-row-standard-fields { + border-color: $wp-border; +} + +.edd-repeatable-row-header:before, +.edd-repeatable-row-header:after, +.edd-bundled-product-row:before, +.edd-bundled-product-row:after { + content: ''; + display: table; +} + +.edd-repeatable-row-header:after, +.edd-bundled-product-row:after { + clear: both; +} + +.edd-repeatable-row-title, +.edd-bundle-products-header { + font-weight: 600; +} + +.edd-repeatable-row-title, +.edd-repeatable-row-actions, +.edd-bundle-products-header { + padding: 8px; + box-sizing: border-box; +} + +.edd-repeatable-row-actions { + flex-grow: 1; + text-align: right; +} + +.edd-repeatable-row-actions .edd-remove-row, +.edd-bundled-product-row .edd-remove-row { + width: auto; + cursor: pointer; +} + +.edd-repeatable-row-standard-fields, +.edd-bundled-product-row { + padding: 8px; + border-width: 0 1px 1px; + border-style: solid; + border-color: $wp-border; + display: flex; + justify-content: space-between; + align-items: center; + gap: 18px; +} + +/* @todo: remove these when .edd-form-row has been fully implemented */ +.edd-repeatable-row-standard-fields .edd-form-group, +.edd-bundled-product-row .edd-form-group { + margin-bottom: 0; + display: inline-flex; + flex-direction: column; + flex-grow: 1; + justify-content: space-between; +} + +.edd-repeatable-row-setting-label .edd-help-tip { + display: inline-block; + margin-left: 4px; +} + +.edd-bundled-product-item-reorder { + min-width: 30px; + + .edd-product-file-reorder { + font-size: 20px; + cursor: move; + color: $wp-gray-5; + font-family: "dashicons"; + content: "\f545"; + transition: .2s color; + + &:hover { + color: $wp-gray-20; + } + } +} + +.edd-bundled-product-actions { + align-self: center; +} + +#edd_products .edd-select, +.edd_repeatable_upload_wrapper .pricing select, +.edd_repeatable_product_wrapper .edd-select { + min-width: 100%; + max-width: 200px; +} + +.edd_repeatable_product_wrapper td { + overflow: visible; +} + +@media screen and (max-width: $break-mobile) { + + .edd-bundle-products-header, + .edd-repeatable-row-header, + .edd-repeatable-row-standard-fields, + .edd-bundled-product-row { + flex-wrap: wrap; + } + + .edd-repeatable-row-standard-fields .edd-form-group, + .edd-bundled-product-row .edd-form-group { + margin-left: 0 !important; + margin-bottom: 24px; + } +} + +/* still used by extensions - Software Licensing upgrade paths, Custom Prices */ +.edd_remove_repeatable { + border: none; + cursor: pointer; + display: inline-block; + padding: 0; + overflow: hidden; + margin: 8px 0 0 0; + text-indent: -9999px; + width: 10px; + height: 10px; +} + +.edd_remove_repeatable:active, +.edd_remove_repeatable:hover, +.edd_remove_repeatable:focus { + background-position: -10px 0 !important; +} diff --git a/assets/css/admin/downloads/_upload.scss b/assets/css/admin/downloads/_upload.scss new file mode 100644 index 00000000000..b5b01acde75 --- /dev/null +++ b/assets/css/admin/downloads/_upload.scss @@ -0,0 +1,27 @@ +.edd_repeatable_upload_wrapper .edd_repeatable_upload_field_container { + position: relative; + width: 100%; + + +span:first-child { + width: 100%; + } +} + +.edd_repeatable_upload_field { + padding-right: 32px; +} + +.edd_upload_file button { + background: $wp-gray-0; + border: none; + border-left: 1px solid $wp-border; + padding: 0 4px; + position: absolute; + height: calc(100% - 4px); + overflow: hidden; + top: 2px; + right: 2px; + display: inline-flex; + justify-content: center; + align-items: center; +} diff --git a/assets/css/admin/downloads/style.scss b/assets/css/admin/downloads/style.scss new file mode 100644 index 00000000000..59fd3a86ad0 --- /dev/null +++ b/assets/css/admin/downloads/style.scss @@ -0,0 +1,5 @@ +@import "prices"; +@import "product_settings"; +@import "repeatable_row"; +@import "upload"; +@import "duplicate"; diff --git a/assets/css/admin/email-tags.scss b/assets/css/admin/email-tags.scss new file mode 100644 index 00000000000..44314bb60bb --- /dev/null +++ b/assets/css/admin/email-tags.scss @@ -0,0 +1,63 @@ +/** + * EDD Admin CSS + * + * @package EDD + * @subpackage Admin Email Tags CSS + * @copyright Copyright (c) 2015, Pippin Williamson + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License + */ + +.edd-email-tags-filter { + margin: -15px -15px 15px; + padding: 15px; + border-bottom: 1px solid #ddd; + background: #f3f3f3; +} + +.edd-email-tags-filter-search { + padding: 10px; + width: 100%; + font-size: 20px; +} + +.edd-email-tags-list { + margin: 0; +} + +.edd-email-tags-list-item { + margin-bottom: 15px; +} + +.edd-email-tags-list-item::last-child { + margin-bottom: 0; +} + +.edd-email-tags-list-button { + cursor: pointer; + color: #666; + text-align: left; + padding: 0.8rem; + width: 100%; + background: none; + border: 1px solid #ddd; + border-radius: 3px; +} + +.edd-email-tags-list-button:hover { + background: #fafafa; +} + +.edd-email-tags-list-button strong { + color: #444; + font-size: 14px; + margin-bottom: 8px; +} + +.edd-email-tags-list-button code { + margin-left: 10px; +} + +.edd-email-tags-list-button span { + display: block; + margin-top: 10px; +} \ No newline at end of file diff --git a/assets/css/admin/extension-manager.scss b/assets/css/admin/extension-manager.scss new file mode 100644 index 00000000000..ad5ca8b75f3 --- /dev/null +++ b/assets/css/admin/extension-manager.scss @@ -0,0 +1,5 @@ +/* Extension Manager +------------------------------------------------------------- */ + +@import "../variables/variables"; +@import "extensions/style"; diff --git a/assets/css/admin/extensions/_bar.scss b/assets/css/admin/extensions/_bar.scss new file mode 100644 index 00000000000..76393337684 --- /dev/null +++ b/assets/css/admin/extensions/_bar.scss @@ -0,0 +1,27 @@ +.edd-extension-manager__bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1em; + flex-wrap: wrap; + + &-heading { + display: flex; + align-items: center; + gap: 1em; + } + + input[type="search"] { + background-color: $white; + background-image: $icon-search; + background-size: 1em; + background-repeat: no-repeat; + background-position: 97% center; + + &:active, + &:focus, + &:hover { + background-image: none; + } + } +} diff --git a/assets/css/admin/extensions/_body.scss b/assets/css/admin/extensions/_body.scss new file mode 100644 index 00000000000..2f1e5f064e2 --- /dev/null +++ b/assets/css/admin/extensions/_body.scss @@ -0,0 +1,23 @@ +.edd-extension-manager__body { + display: grid; + gap: 1.5em; + grid-template-rows: auto 1fr; + flex-grow: 1; + + img { + max-width: 100%; + } + + .notice { + max-width: 320px; + } +} + +.edd-extension-manager__title { + line-height: 1.4; + margin: 0 1em 1em 0; + + a { + color: $wp-input-text; + } +} diff --git a/assets/css/admin/extensions/_buttons.scss b/assets/css/admin/extensions/_buttons.scss new file mode 100644 index 00000000000..7c9030dbc4c --- /dev/null +++ b/assets/css/admin/extensions/_buttons.scss @@ -0,0 +1,154 @@ +.button.edd-extension-manager__action-upgrade { + background-color: $wp-green-50; + color: $white; + + &:hover, + &:active { + background-color: darken($wp-green-50,10%); + color: $white; + } +} + +.edd-extension-manager__card--installer .button { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; + margin: 0; + + &:before { + margin: 0; + } + + &.edd-button__install { + color: $wp-input-text; + border-color: $wp-input-border; + + &:before { + content: ' '; + display: block; + width: 1em; + height: 1em; + background-image: $icon-install; + background-size: 1em; + } + + &.edd-updating { + &:before { + background: none; + @include edd-spinner( + 12px, + $wp-alternate, + $wp-input-border + ); + } + } + } +} + +.edd-extension-manager__control { + display: flex; + align-items: center; + gap: .5em; + min-height: 30px; + + .edd-button__toggle { + position: relative; + margin: 0; + padding: 0; + width: 36px; + height: 20px; + min-height: unset; + background-color: $wp-border; + transition: background 0.2s ease; + border-radius: 30px; + box-shadow: none; + border: none; + + &:after { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 3px; + bottom: 3px; + background-color: $white; + transition: 0.1s transform ease; + border-radius: 50%; + + .edd-plugin__active & { + transform: translateX(16px); + } + } + + &:active, + &:focus { + outline: 0; + box-shadow: 0 0 0 1px $white, 0 0 0 3px $wp-input-border; + } + + &:hover { + background-color: $wp-input-border; + } + + &:disabled { + background-color: $wp-input-border !important; + + &:before { + position: absolute; + top: 3px; + @include edd-spinner( + 10px, + $wp-input-border, + $wp-alternate + ); + } + + &:after { + display: none; + } + } + + .edd-plugin__active & { + background-color: var(--wp-admin-theme-color); + + :active, + :focus { + box-shadow: 0 0 0 1px $white, 0 0 0 3px var(--wp-admin-theme-color); + } + } + } + + @media screen and (max-width: $break-medium) { + min-height: 40px; + } +} + +a.button.edd-extension-manager__button-settings { + display: none; + position: absolute; + top: 1em; + right: 1em; + background-image: $icon-settings; + background-size: 1.25em; + background-repeat: no-repeat; + background-position: center; + min-height: unset; + height: 1.5em; + width: 1.5em; + padding: 1em; + border: none; + background-color: $wp-alternate; + + &:hover, + &:active { + background-image: $icon-settings; + background-size: 1.25em; + background-repeat: no-repeat; + background-position: center; + } + + .edd-plugin__active & { + display: block; + } +} diff --git a/assets/css/admin/extensions/_card.scss b/assets/css/admin/extensions/_card.scss new file mode 100644 index 00000000000..b6c5f87faa0 --- /dev/null +++ b/assets/css/admin/extensions/_card.scss @@ -0,0 +1,16 @@ +.edd-extension-manager__card { + background-color: $white; + padding: 2em; + margin: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + + &.edd-hidden { + display: none; + } + + .inside & { + padding: 0; + } +} diff --git a/assets/css/admin/extensions/_features.scss b/assets/css/admin/extensions/_features.scss new file mode 100644 index 00000000000..8cb2893c455 --- /dev/null +++ b/assets/css/admin/extensions/_features.scss @@ -0,0 +1,11 @@ +.edd-extension-manager__features { + ul { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + } + + .dashicons-yes { + color: #008a20; + margin-right: .25em; + } +} diff --git a/assets/css/admin/extensions/_group.scss b/assets/css/admin/extensions/_group.scss new file mode 100644 index 00000000000..9dc93e8bea4 --- /dev/null +++ b/assets/css/admin/extensions/_group.scss @@ -0,0 +1,22 @@ +.edd-extension-manager__wrap { + max-width: $break-huge; +} + +.edd-extension-manager__card-group { + transition: all .5s; + + @supports(grid-area: auto) { + display: grid; + grid-template-columns: auto; + gap: 1em; + margin-top: 24px; + } +} + +.edd-extension-manager__group { + display: grid; +} + +.edd-extension-manager__unlock { + margin-top: 4em; +} diff --git a/assets/css/admin/extensions/_images.scss b/assets/css/admin/extensions/_images.scss new file mode 100644 index 00000000000..216bd297f69 --- /dev/null +++ b/assets/css/admin/extensions/_images.scss @@ -0,0 +1,21 @@ +.edd-extension-manager__icon { + border: 1px solid $gray-200; + border-radius: 4px; + width: 78px; + height: 78px; + + img { + border-radius: 12px; + display: block; + margin: 0; + padding: 9px; + width: 60px; + } +} + +.edd-extension-manager__image img { + display: block; + margin: 0 auto; + max-width: 500px; + width: 100%; +} diff --git a/assets/css/admin/extensions/_installer.scss b/assets/css/admin/extensions/_installer.scss new file mode 100644 index 00000000000..45b88cae813 --- /dev/null +++ b/assets/css/admin/extensions/_installer.scss @@ -0,0 +1,47 @@ +.edd-extension-manager__card--installer { + border: 1px solid $wp-gray-5; + border-radius: 3px; + padding: 0; + + > div { + padding: 2em; + + &:not(:last-child) { + border-bottom: 1px solid $gray-200; + } + } + + .notice { + margin: 0 -1em !important; + + &:not(:last-child) { + margin-bottom: 1em; + } + } + + .edd-extension-manager__body { + grid-template-columns: 80px 1fr; + grid-template-rows: unset; + position: relative; + + p:last-child { + margin-bottom: 0; + } + } + + .edd-extension-manager__actions { + background: $wp-alternate; + display: flex; + justify-content: space-between; + align-items: center; + padding: 1em 2em; + + > *:only-child { + margin-left: auto; + } + } + + .edd-extension-manager__status { + font-weight: 600; + } +} diff --git a/assets/css/admin/extensions/_key.scss b/assets/css/admin/extensions/_key.scss new file mode 100644 index 00000000000..5603b881dea --- /dev/null +++ b/assets/css/admin/extensions/_key.scss @@ -0,0 +1,10 @@ +.edd-extension-manager__key-notice { + background: white; + border: 1px solid $wp-red-50; + border-radius: 4px; + padding: 1em; + + p:first-child { + margin-top: 0; + } +} diff --git a/assets/css/admin/extensions/_steps.scss b/assets/css/admin/extensions/_steps.scss new file mode 100644 index 00000000000..e0e89e6a67a --- /dev/null +++ b/assets/css/admin/extensions/_steps.scss @@ -0,0 +1,19 @@ +.edd-extension-manager__step { + grid-area: 1/-1; + margin: 0; + + &:not(:first-of-type) { + display: none; + } + + .button { + display: table; + margin: 0 auto; + text-align: center; + white-space: normal; + } +} + +td .edd-extension-manager__step .button { + display: inline-block; +} diff --git a/assets/css/admin/extensions/_variations.scss b/assets/css/admin/extensions/_variations.scss new file mode 100644 index 00000000000..0d711e434ba --- /dev/null +++ b/assets/css/admin/extensions/_variations.scss @@ -0,0 +1,50 @@ +/* Extension Manager Style Variations +--------------------------------------- */ +.edd-extension-manager__card--horizontal { + margin: 24px 0; + max-width: 700px; +} + +.edd-extension-manager__card--detailed { + background-color: transparent; + max-width: 700px; +} + +.edd-extension-manager__card--detailed-2col { + background-color: transparent; + max-width: 900px; +} + +.edd-extension-manager__card--detailed .edd-extension-manager__title, +.edd-extension-manager__card--detailed-2col .edd-extension-manager__title { + border-bottom: 1px solid #ccc; + padding-bottom: 1em; +} + +@media screen and (min-width: $break-small) { + .edd-extension-manager__card-group { + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + } + + .edd-extension-manager__card--horizontal .edd-extension-manager__body { + grid-template-columns: minmax(0, 300px) 1fr; + grid-template-rows: 1fr auto; + grid-auto-flow: column; + } + + .edd-extension-manager__card--horizontal .edd-extension-manager__image { + grid-row: 1 / 4; + } + + .edd-extension-manager__features, + .edd-extension-manager__card--horizontal .edd-extension-manager__description { + align-self: center; + } +} + +@media screen and (min-width: 783px) { + .edd-extension-manager__card--detailed-2col .edd-extension-manager__body { + grid-template-columns: minmax(0, 375px) 1fr; + grid-auto-flow: column; + } +} diff --git a/assets/css/admin/extensions/style.scss b/assets/css/admin/extensions/style.scss new file mode 100644 index 00000000000..e9503db63e1 --- /dev/null +++ b/assets/css/admin/extensions/style.scss @@ -0,0 +1,14 @@ +/* Extension Manager +------------------------------------------------------------- */ + +@import "bar"; +@import "body"; +@import "buttons"; +@import "card"; +@import "features"; +@import "group"; +@import "images"; +@import "installer"; +@import "key"; +@import "steps"; +@import "variations"; diff --git a/assets/css/admin/forms/_form-group.scss b/assets/css/admin/forms/_form-group.scss new file mode 100644 index 00000000000..6d78d3405ac --- /dev/null +++ b/assets/css/admin/forms/_form-group.scss @@ -0,0 +1,86 @@ +/** + * Form Group + * + *
+ * + *
+ * + *
+ *

Help

+ *
+ * + * + *
+ * Label + * + *
+ * + * + *
+ * + *
+ * + * + *
+ *
+ * + */ +.edd-form-group { + margin-bottom: 16px; + + &:last-of-type { + margin-bottom: 0; + } +} + +.edd-form-group__label, +.edd-form-group>label { + display: block; + font-weight: 600; + margin-bottom: 8px; + padding: 0; +} + +.edd-form-group__control { + margin-bottom: 12px; + max-width: 100%; + + &.is-radio, + &.is-check { + margin-top: 4px; + } + + &:last-of-type { + margin-bottom: 0; + } + + &--is-inline { + display: inline-flex; + align-items: flex-end; + } +} + +.edd-form-group__input { + max-width: 100%; + + &[type="checkbox"], + &[type="radio"] { + margin-top: 0; + + +label { + display: unset; + } + } +} + +select.edd-form-group__input { + max-width: 100%; +} + +.edd-form-group__help { + color: $wp-gray-50; + font-size: 13px; + font-style: italic; + line-height: initial; + margin: 8px 0 0; +} diff --git a/assets/css/admin/forms/_form-row.scss b/assets/css/admin/forms/_form-row.scss new file mode 100644 index 00000000000..30f106877b5 --- /dev/null +++ b/assets/css/admin/forms/_form-row.scss @@ -0,0 +1,20 @@ +.edd-form-row { + display: flex; + flex-wrap: wrap; + gap: 12px; + + &__column { + display: inline-flex; + flex-direction: column; + justify-content: flex-end; + + &.edd-form-group { + margin-bottom: 0; + } + } + + label, + label.edd-form-group__label { + margin-bottom: 8px; + } +} diff --git a/assets/css/admin/forms/style.scss b/assets/css/admin/forms/style.scss new file mode 100644 index 00000000000..ec17c7f79fc --- /dev/null +++ b/assets/css/admin/forms/style.scss @@ -0,0 +1,2 @@ +@import "form-group"; +@import "form-row"; diff --git a/assets/css/admin/gateways/style.scss b/assets/css/admin/gateways/style.scss new file mode 100644 index 00000000000..d5e0bc5bf1d --- /dev/null +++ b/assets/css/admin/gateways/style.scss @@ -0,0 +1,17 @@ +/* PayPal Connect +-------------------------------------------------------------- */ +.edd-paypal-account-status ul { + margin-left: 25px; +} +.edd-paypal-account-status > li { + margin-bottom: 1em; +} +.edd-paypal-account-status ul:not(.edd-paypal-webhook-events) li { + margin: .25em 0; +} +.edd-paypal-account-status .dashicons-yes { + color: #008a20; +} +.edd-paypal-account-status .dashicons-no { + color: #d63638; +} diff --git a/assets/css/admin/general/_dialog.scss b/assets/css/admin/general/_dialog.scss new file mode 100644 index 00000000000..755f725a9e3 --- /dev/null +++ b/assets/css/admin/general/_dialog.scss @@ -0,0 +1,4 @@ +// Styles for our use of jQuery.dialog +.edd-dialog { + display: none; +} diff --git a/assets/css/admin/general/_item-header.scss b/assets/css/admin/general/_item-header.scss new file mode 100644 index 00000000000..559a90c6d5f --- /dev/null +++ b/assets/css/admin/general/_item-header.scss @@ -0,0 +1,13 @@ +.edd-item-header-small { + padding-bottom: 20px; + border-bottom: 1px solid #e5e5e5; + display: flex; + justify-content: flex-start; + align-items: center; + gap: 6px; + + span { + font-weight: 600; + font-size: 15px; + } +} diff --git a/assets/css/admin/general/_status-badge.scss b/assets/css/admin/general/_status-badge.scss new file mode 100644 index 00000000000..af36d45686c --- /dev/null +++ b/assets/css/admin/general/_status-badge.scss @@ -0,0 +1,53 @@ +@import "../../variables/colors"; +/** Status labels */ +.edd-status-badge, +.edd-admin-order-status-badge { + padding: 2px 7px; + border-radius: 4px; + background: #ececec; + display: inline-flex; + align-items: center; + gap: 2px; + + &__icon { + opacity: 0.8; + } + + &--red, + &--error, + &--failed, + &--failing, + &--expired, + &--rejected, + &--revoked { + color: #ac3d3d; + background: #ffd6d6; + } + + &--green, + &--success, + &--complete, + &--partially_refunded, + &--active, + &--completed, + &--edd_subscription, + &--approved { + color: #017d5c; + background: #e5f5f0; + } + + &--yellow, + &--warning, + &--pending { + color: $wp-yellow-50; + background: #f5f2e5; + } + + &--blue, + &--info, + &--processing, + &--trialling { + color: $blue-500; + background: #e5f1f5; + } +} diff --git a/assets/css/admin/general/_upgrade.scss b/assets/css/admin/general/_upgrade.scss new file mode 100644 index 00000000000..2cb8a6c05f3 --- /dev/null +++ b/assets/css/admin/general/_upgrade.scss @@ -0,0 +1,6 @@ +.edd-pro-upgrade, +.edd-pro-upgrade:hover{ + color: #1da867; + font-weight: 600; + text-decoration: none; +} diff --git a/assets/css/admin/general/style.scss b/assets/css/admin/general/style.scss new file mode 100644 index 00000000000..0ed03ab642a --- /dev/null +++ b/assets/css/admin/general/style.scss @@ -0,0 +1,4 @@ +@import "dialog"; +@import "item-header"; +@import "status-badge"; +@import "upgrade"; diff --git a/assets/css/admin/menu.scss b/assets/css/admin/menu.scss new file mode 100644 index 00000000000..0daac796b81 --- /dev/null +++ b/assets/css/admin/menu.scss @@ -0,0 +1,96 @@ +/** + * EDD Admin Menu CSS + * + * @package EDD + * @subpackage Admin CSS + * @copyright Copyright (c) 2015, Pippin Williamson + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License +*/ + +@import "~@wordpress/base-styles/breakpoints"; +@import "../variables/colors"; + + +/* Main Menu +-------------------------------------------------------------- */ +#menu-posts-download .wp-submenu { + display: flex; + flex-wrap: wrap; + li { + width: 100%; + } +} + +/* Submenu Styles +-------------------------------------------------------------- */ + +#menu-posts-download a[href^="edit.php?post_type=download"] { + &:hover, + &:focus { + box-shadow: inset 4px 0 0 0 currentColor; + transition: box-shadow .1s linear; + } +} + +#menu-posts-download li > a[href^="post-new.php?post_type=download"] { + display: none; +} + +/* Secondary Separators */ +#menu-posts-download li:not(:last-child) a[href^="post-new.php?post_type=download"]:after, +#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-discount"]:after, +#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-reports"]:after, +#menu-posts-download li:nth-last-child(2) a:after { + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + display: block; + float: left; + margin: 13px -15px 8px; + content: ''; + width: calc(100% + 26px); + + @media screen and (max-width: $break-medium) { + margin: 20px -20px 8px -20px; + width: calc(100% + 30px); + } +} + +#adminmenu #menu-posts-download { + /* WordPress 5.7 fix for left-shadow on hover */ + ul.wp-submenu-wrap li { + clear: both; + } + + a.wp-has-current-submenu:after { + display: none; + } +} + +/* Show Submenu Arrow */ +ul#adminmenu #menu-posts-download ul.wp-submenu li.current a:before { + right: 0; + border: solid 8px transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-right-color: $wp-gray-0; + margin-top: 2px; +} + +/* Onboarding Submenu Item */ +a.edd-onboarding__menu-item { + background: #dd823b !important; + color: $white !important; + font-weight: 600; + &:hover { + color: $black !important; + } +} + +a.edd-sidebar__upgrade-pro, +a.edd-sidebar__upgrade-pro:hover { + background-color: #1da867 !important; + color: $white !important; + font-weight: 600; +} diff --git a/assets/css/admin/notifications/style.scss b/assets/css/admin/notifications/style.scss new file mode 100644 index 00000000000..18a642f134a --- /dev/null +++ b/assets/css/admin/notifications/style.scss @@ -0,0 +1,123 @@ +/* Notifications +-------------------------------------------------------------- */ +.edd-overlay { + position: fixed; + z-index: 1052; + top: 0; + right: 0; + bottom: 0; + left: 160px; + background-color: #141b38; + opacity: .5; + transition: .5s; +} + +.edd-slide-in { + transform: translateX(100%) !important; + -webkit-transform: translateX(100%) !important; +} + +#edd-notifications-panel { + background-color: white; + height: 100%; + width: 100%; + max-width: 570px; + position: fixed; + z-index: 1053; + top: 0; + right: 0; + bottom: 0; + overflow-x: hidden; + transition: .5s; + + transform: translateX(0%); + -webkit-transform: translateX(0%); +} +body.admin-bar #edd-notifications-panel { + top: 32px; +} +@media screen and (max-width: 600px) { + body.admin-bar #edd-notifications-panel { + top: 46px; + } +} + +#edd-notifications-header { + display: flex; + align-items: center; + padding: 0 30px; + color: #fff; + background-color: #0c5d95; +} +#edd-notifications-header h3 { + color: #fff; + flex: 1; +} +#edd-notifications-header .edd-close { + background: none; + border: none; + color: #fff; + cursor: pointer; +} + +#edd-notifications-body { + padding: 30px; +} + +.edd-notification { + display: flex; + gap: 20px; + margin-bottom: 20px; +} +.edd-notification--icon { + color: #00aa63; +} +.edd-notification--icon.edd-notification--icon-info { + color: #005ae0; +} +.edd-notification--icon.edd-notification--icon-warning { + color: #f18200; +} +.edd-notification--icon.edd-notification--icon-error { + color: #df2a4a; +} +.edd-notification--body { + flex: 1; +} +.edd-notification--header { + align-items: center; + display: flex; + justify-content: space-between; + gap: 5px; + margin-bottom: 7px; +} +.edd-notification--title { + color: #141b38; + flex: 1; + font-size: 16px; + font-weight: 600; + margin: 0; +} +.edd-notification--date { + color: #71747e; + font-size: 12px; +} +.edd-notification--actions { + flex-wrap: wrap; + display: flex; + align-items: center; + gap: 5px; + margin-top: 10px; +} +.edd-notification--dismiss { + background: none !important; + border: none !important; + box-shadow: none !important; + color: #71747e !important; + cursor: pointer; + padding: 0 10px; + text-decoration: underline; +} +.edd-notification--dismiss:hover { + text-decoration: none; +} diff --git a/assets/css/admin/onboarding/style.scss b/assets/css/admin/onboarding/style.scss new file mode 100644 index 00000000000..164d3a00707 --- /dev/null +++ b/assets/css/admin/onboarding/style.scss @@ -0,0 +1,844 @@ +@import "~@wordpress/base-styles/breakpoints"; +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/variables"; +@import "~@wordpress/base-styles/mixins"; +@import "../../variables/colors"; +@import "../../variables/mixins"; + +:root { + @include edd-admin-colors(); +} + +.edd-onboarding { + margin-top: 80px; +} +.edd-onboarding__logo { + img { + display: block; + width: 300px; + margin: 0 auto 25px; + } +} +.edd-onboarding__wrapper { + max-width: 1000px; + margin: 0 auto; + position: relative; +} + +@media only screen and (max-width: $break-wide) { + .edd-onboarding__wrapper { + max-width: 850px; + } +} + +.edd-onboarding__loading { + z-index: 99; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + padding-left: 80px; + padding-top: 8px; + display: flex; + gap: 20px; + flex-wrap: wrap; + align-items: center; + justify-content: center; + text-align: center; + &:before { + position: absolute; + @include edd-spinner( + 35px, + $wp-input-border, + $wp-alternate + ); + } + .edd-onboarding__loading-status { + display: block; + text-align: center; + color: black; + flex-basis: 100%; + margin-top: 80px; + } +} + +@media only screen and (max-width: $break-small) { + .edd-onboarding__loading { + padding-left: 0; + } +} + +.edd-onboarding__loading-in-progress { + .edd-onboarding__single-step, .edd-onboarding__welcome-screen { + position: relative; + &:before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: rgba(255, 255, 255, 0.85); + z-index: 95; + } + } +} + +/* EDD ONBOARDING - Steps style */ +.edd-onboarding__steps { + margin-top: 25px; + ul { + display: flex; + gap: 15px; + position: relative; + &:before { + position: absolute; + top: 16px; + left: 50%; + transform: translateX(-50%); + width: 80%; + height: 2px; + background: #dedfe0; + content: ""; + z-index: -1; + } + li { + flex: 1; + text-align: center; + a { + display: block; + padding: 5px 10px; + color: #8a8e92; + text-align: center; + font-size: 12px; + text-decoration: none; + span { + color: $white; + width: 25px; + height: 25px; + line-height: 25px; + font-size: 12px; + font-weight: 400; + border-radius: 50%; + background: #c2c4c6; + display: inline-block; + text-align: center; + margin-bottom: 10px; + position: relative; + box-shadow: none; + &:before { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: -8px; + width: 8px; + height: 10px; + background: #F0F0F1; + content: ""; + z-index: -1; + } + &:after { + position: absolute; + top: 50%; + transform: translateY(-50%); + left: -8px; + width: 8px; + height: 10px; + background: #F0F0F1; + content: ""; + z-index: -1; + } + } + } + &.active-step a { + color: var(--wp-admin-theme-color); + font-weight: 500; + small { + font-weight: 500; + color: var(--wp-admin-theme-color); + } + span { + background: var(--wp-admin-theme-color); + box-shadow: 0 0 4px 1px rgba(var(--wp-admin-theme-color), 0.3); + } + } + &.completed-step a { + span { + width: 25px; + height: 25px; + line-height: 25px; + font-size: 14px; + background: $wp-green-30; + box-shadow: none; + } + } + } + } +} + +.edd-onboarding__steps__name { + color: $wp-gray-50; + display: block; + font-size: 11px; +} + +@media only screen and (max-width: $break-small) { + .edd-onboarding__steps ul li a span, .edd-onboarding__steps ul li.completed-step a span { + width: 20px; + height: 20px; + line-height: 20px; + } + .edd-onboarding__steps__name { + font-size: 10px; + } +} + +.edd-onboarding__current-step { + position: relative; +} + +.edd-onboarding__single-step { + background: white; + border: 1px solid #dedfe0; + border-radius: 3px; + position: relative; +} + +.edd-onboarding__single-step-inner { + padding: 70px 140px 40px 140px; + &.equal { + padding: 70px 140px; + } +} + +@media only screen and (max-width: $break-large) { + .edd-onboarding__single-step-inner { + padding: 35px 70px 20px 70px; + &.equal { + padding: 35px 70px; + } + } +} + +@media only screen and (max-width: $break-small) { + .edd-onboarding__single-step-inner { + padding: 17px 35px 10px 35px; + &.equal { + padding: 17px 35px; + } + } +} + +.edd-onboarding__steps-indicator { + opacity: 0.6; + display: block; +} + +h1.edd-onboarding__single-step-title { + font-size: 24px; + color: #141b38; + font-weight: 600; +} +.edd-onboarding__single-step-subtitle { + font-size: 16px; + line-height: 22px; + color: #141b38; + font-weight: normal; + max-width: 90%; +} + +.edd-onboarding__welcome-screen { + width: 100%; + height: 100%; + background: white; + display: flex; + align-items: center; + h1 { + line-height: 2rem; + } +} + +.edd-onboarding__welcome-screen-inner { + padding: 100px 80px; + text-align: center; +} + +.edd-onboarding__testimonials-wrapper { + display: grid; + gap: 1em; +} + +.edd-onboarding__testimonial { + display: flex; + font-size: 1rem; + text-align: left; + justify-content: space-between; + gap: 2em; + align-items: center; + + &:not(:last-of-type) { + border-bottom: 1px solid #dedfe0; + padding-bottom: 2em; + } +} + +.edd-onboarding__testimonial-content { + flex-grow: 1; +} + +.edd-onboarding__testimonial-content > span.big { + font-weight: 600; + font-size: 15px; + font-style: italic; +} + +.edd-onboarding__testimonial-avatar { + width: 75px; + height: 75px; + border-radius: 50%; + display: block; +} + +.edd-onboarding__testimonial-info { + display: flex; + flex-direction: column; + gap: .25em; + + > .testimonial-name { + font-weight: 600; + } + + > .testimonial-company { + font-size: 10px; + font-style: italic; + } + + > .testimonial-stars > span { + color: #ffbb38; + font-size: 12px; + height: 12px; + width: 12px; + } +} + +.edd-onboarding__welcome-screen-get-started { + color: $white !important; + background: $wp-green-30 !important; + border-color: $wp-green-30 !important; + margin: 1em auto !important; + + &:hover { + color: $white !important; + background: $wp-green-50 !important; + } +} + +.edd-onboarding__plugins-list { + border-top: 1px solid rgba(#ededed, 0.5); + + .edd-onboarding__plugins-plugin { + padding: 28px 20px; + border-bottom: 1px solid rgba(#ededed, 0.5); + border-left: 1px solid rgba(#ededed, 0.5); + border-right: 1px solid rgba(#ededed, 0.5); + transition: all 0.25s ease-out; + + h3 { + margin-top: 0; + transition: all 0.25s ease-out; + } + + p { + margin-bottom: 0; + transition: all 0.25s ease-out; + } + + .edd-onboarding__plugins-control { + width: 100px; + display: flex; + justify-content: flex-end; + + .checkbox-control { + padding: 0; + margin: 0; + position: relative; + } + + .checkbox-control__indicator { + position: relative; + top: 0; + } + } + .edd-onboarding__plugins-external-link { + text-decoration: none; + } + .edd-onboarding__plugins-details label { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1em; + } + &.disabled { + background: rgba(#72b281, 0.04); + &:hover { + background: rgba(#72b281, 0.04); + } + } + &:hover { + background: rgba(var( --wp-admin-theme-color ), 0.02); + .edd-onboarding__plugins-details { + h3 { + color: var( --wp-admin-theme-color ); + } + p { + color: var( --wp-admin-theme-color ); + } + } + } + } +} +.edd-onboarding__single-step-footer { + border-top: 1px solid #ededed; + padding: 35px 50px 35px 50px; + display: flex; + justify-content: space-between; + align-items: center; + .edd-onboarding__button-back { + color: $wp-gray-40; + text-decoration: none; + transition: all 0.2s ease-in-out; + background: none; + border: none; + cursor: pointer; + &:hover { + color: var( --wp-admin-theme-color ); + } + } + .edd-onboarding__button-skip-step { + opacity: 0.6; + } +} + +@media only screen and (max-width: $break-small) { + .edd-onboarding__single-step-footer { + padding: 17px 25px; + flex-wrap: wrap; + gap: 5px; + } +} + +.edd-onboarding__close-and-exit { + text-align: center; + margin-top: 20px; + + button.button-link { + color: #8a8e92 !important; + text-decoration: none !important; + } +} + +/* EDD ONBOARDING - Form style */ + +@media only screen and (max-width: $break-medium) { + .edd-form-group__control { + display: flex; + align-items: center; + gap: 10px; + } +} + +.edd-onboarding input:not([type="checkbox"]):not([type="radio"]) { + border: 1px solid #dedfe0 !important; + border-radius: 2px !important; + padding: 2px 8px !important; + width: 100%; +} + +.edd-onboarding .quicktags-toolbar input.ed_button { + width: auto; +} + +.edd-onboarding .edd-check-wrapper { + display: flex; + align-items: center; +} + +.wp-core-ui .edd-onboarding select { + font-size: 14px; + line-height: 2; + border-color: #dedfe0; + box-shadow: none; + border-radius: 2px; + padding: 0 24px 0 8px; + min-height: 30px; + max-width: 25rem; + -webkit-appearance: none; + background-size: 16px 16px; + cursor: pointer; + vertical-align: middle; +} + +.edd-onboarding .form-table th { + vertical-align: middle; +} + +.edd-onboarding .form-table, +.edd-onboarding .form-table td, +.edd-onboarding .form-table td p { + color: #8a8e92; + font-size: 13px; + line-height: 18px; +} + +.edd-onboarding .form-table th, +.edd-onboarding .form-wrap label { + color: #141b38; + font-weight: 400; + text-shadow: none; + vertical-align: baseline; +} + +.edd-onboarding td[colspan="2"] { + padding: 0; +} + +/* EDD ONBOARDING - Stripe section style */ + +.edd-onboarding__stripe-features-listing { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + list-style-type:none; + margin-left: 0; + padding-left: 0; + margin-top: 20px; + li { + list-style-type:none; + position: relative; + padding-left: 28px; + color: #3c434a; + font-size: 12px; + margin-bottom: 10px; + margin-right: 10px; + &:before { + position: absolute; + top: 0; + left: 0; + content: "✓"; + background: #e9e4fe; + color: #635BFF; + width: 20px; + height: 20px; + line-height: 20px; + text-align: center; + border-radius: 50%; + display: inline-block; + } + } +} +.edd-onboarding__stripe-content-holder { + max-width: 75%; + margin: 25px auto; + background: rgba(#f1eefa, 0.3); + padding: 50px 40px 50px 40px; + border-radius: 4px; + border: 1px solid rgba(#f1eefa, 0.5); + .edd-onboarding__stripe-content-logo { + text-align: center; + margin-bottom: 25px; + border-bottom: 1px solid #ededed; + padding-bottom: 25px; + img { + max-width: 180px; + } + span { + text-align: center; + font-size: 13px; + line-height: 20px; + display: block; + max-width: 300px; + margin: 0 auto; + } + } +} + +@media only screen and (max-width: $break-large) { + .edd-onboarding__stripe-content-holder { + padding:25px 20px 25px 20px; + max-width: 100%; + } +} + + +.edd-onboarding__button-stripe { + display: block; + text-align: center; + margin-top: 20px; +} + +#edds-stripe-disconnect-reconnect { + margin-top: 10px; +} + +.edd-onboarding__stripe-features-title { + display: block; + text-align: center; + font-size: 16px; + margin-bottom: 10px; + color: #625bff; + font-weight: 500; +} +.edd-onboarding__stripe-additional-text { + text-align: center; + font-size: 11px; + line-height: 14px; + display: block; + max-width: 400px; + margin: 30px auto 0 auto; + opacity: 0.6; +} + +/* EDD ONBOARDING - Checkboxes style */ +.checkbox-control { + position: relative; + padding-left: 30px; + margin-bottom: 15px; + cursor: pointer; + font-size: 18px; +} +.checkbox-control input { + position: absolute; + z-index: -1; + opacity: 0; +} +.checkbox-control__indicator { + position: absolute; + top: 2px; + left: 0; + height: 25px; + width: 25px; + background: #f0f0f1; + border-radius: 3px; +} +.checkbox-control:hover input ~ .checkbox-control__indicator, .checkbox-control input:focus ~ .checkbox-control__indicator { + background: #eaeaec; +} +.checkbox-control input:checked ~ .checkbox-control__indicator { + background: var( --wp-admin-theme-color ); +} +.checkbox-control:hover input:not([disabled]):checked ~ .checkbox-control__indicator, .checkbox-control input:checked:focus ~ .checkbox-control__indicator { + background: var( --wp-admin-theme-color ); +} +.checkbox-control input:disabled ~ .checkbox-control__indicator { + background: $wp-green-30; + pointer-events: none; +} +.checkbox-control__indicator:after { + content: ''; + position: absolute; + display: none; +} +.checkbox-control input:checked ~ .checkbox-control__indicator:after { + display: block; +} +.checkbox-control--checkbox .checkbox-control__indicator:after { + left: 9px; + top: 4px; + width: 5px; + height: 12px; + border: solid #fff; + border-width: 0 2.5px 2.5px 0; + transform: rotate(40deg); +} +.checkbox-control--checkbox input:disabled ~ .checkbox-control__indicator:after { + border-color: #fff; +} +/* Small checkbox style */ +.checkbox-control.small-checkbox { + padding-left: 24px; + margin-bottom: 10px; + font-size: 13px; +} +.small-checkbox .checkbox-control__indicator { + top: 0px; + left: 0; + height: 17px; + width: 17px; + background: #eaeaec; +} +.checkbox-control.small-checkbox:hover input ~ .checkbox-control__indicator, .checkbox-control.small-checkbox input:focus ~ .checkbox-control__indicator { + background: #dedfe0; +} +.checkbox-control.small-checkbox input:checked ~ .checkbox-control__indicator { + background: var( --wp-admin-theme-color ); +} +.checkbox-control.small-checkbox:hover input:not([disabled]):checked ~ .checkbox-control__indicator, .checkbox-control.small-checkbox input:checked:focus ~ .checkbox-control__indicator { + background: var( --wp-admin-theme-color ); +} +.checkbox-control.small-checkbox input:disabled ~ .checkbox-control__indicator { + background: #72b281; +} +.checkbox-control--checkbox.small-checkbox .checkbox-control__indicator:after { + left: 6px; + top: 2.5px; + width: 3px; + height: 8px; + border: solid #f6f7f7; + border-width: 0 2px 2px 0; + transform: rotate(40deg); +} + +/* EDD ONBOARDING - Sugestions section */ +.edd-onboarding__get-suggestions-section { + margin-top: 30px; + text-align: center; + padding: 50px 50px 40px 50px; + background: rgba(#0c5d95, 0.04); + border-radius: 2px; + border: 1px solid rgba(#0c5d95, 0.06); + h3 { + margin-top: 0; + line-height: 25px; + } + .edd-onboarding__get-suggestions-section_label { + display: block; + margin-bottom: 1em; + } +} + +.edd-onboarding__selected-plugins { + text-align: center; + margin-top: 25px; +} + +.edd-onboarding__install-success-wrapper { + z-index: 99; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + padding-left: 80px; + padding-top: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 21px; + .edd-onboarding__install-success { + display: flex; + flex-wrap: wrap; + gap: 25px; + text-align: center; + span { + display: block; + flex-basis: 100%; + } + .emoji { + font-size: 65px; + } + } + + @media only screen and (max-width: $break-large) { + padding-left: 0; + } +} + +/* EDD ONBOARDING - Create product */ + +.edd-onboarding__product-pricing-row td, +.edd-onboarding__product-files-row td { + padding: 0px; +} + +.edd-onboarding__product-image-wrapper { + display: flex; + justify-content: space-between; + gap: 4px; +} + +.edd-onboarding__pricing-option-pill { + display: flex; + button { + display: inline-block; + flex: 1; + border: 1px solid #ccc; + padding: 10px 15px; + cursor: pointer; + &:hover:not(.active) { + background: #dbdcdd; + } + } + .left-option { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + } + .right-option { + border-left: none; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + } + .active { + background: var(--wp-admin-theme-color); + border-color: var(--wp-admin-theme-color); + border-right-color: #ccc; + color: white; + } +} + +.no-table-row-padding { + td { + padding: 0px; + } +} + +.edd-onboarding__product-variable-price { + display: none; +} + +.edd-onboarding__multi-option-toggle, .edd-onboarding__upload-files-toggle { + display: flex; + align-items: center; + span { + margin-left: 10px; + } +} + +.edd-onboarding__upload-files-toggle { + span { + color: #1d2327; + font-size: 1.3em; + font-weight: 600; + display: block; + margin-top: 1em; + margin-bottom: 1em; + } +} + + +.edd-onboarding__pricing-options-label { + display: block; + color: #141b38; + font-weight: 400; + text-shadow: none; + vertical-align: baseline; + font-size: 14px; + margin-top: 20px; + margin-bottom: 20px; +} + +.edd-add-repeatable-row { + border-top: none; + padding-top: 8px; + margin-bottom: 5px; +} + +.edd-onboarding__actions { + display: flex; + gap: 1em; + justify-content: center; + margin-top: 2em; + + button.edd-promo-notice-dismiss { + margin: 0; + } +} diff --git a/assets/css/admin/orders/_refunds-modal.scss b/assets/css/admin/orders/_refunds-modal.scss new file mode 100644 index 00000000000..ce345aec71d --- /dev/null +++ b/assets/css/admin/orders/_refunds-modal.scss @@ -0,0 +1,124 @@ + +$fixed-column-width: 80px; +#edd-submit-refund-status { + text-align: center; + font-size: 1.2em; + + .edd-submit-refund-message { + &:before{ + font-family: dashicons; + font-size: 1.5em; + vertical-align: middle; + color: #fff; + border-radius: 16px; + margin: 5px; + } + + &.success:before { + content: "\f147"; + background-color: $wp-green-50; + padding-right: 1px; + } + + &.fail { + display: block; + margin-bottom: 16px; + + &::before { + content: "\f335"; + background-color: $wp-red-50; + } + } + } +} + +.refund-items { + td, + th.check-column { + vertical-align: baseline; + } + + .column-amount, + .column-quantity, + .column-subtotal, + .column-tax, + .column-discount, + .column-total { + width: $fixed-column-width; + } + + .edd-form-group__control { + display: flex; + align-items: center; + + select, + input { + background-color: transparent; + border: 0; + border-bottom: 1px solid; + border-radius: 0; + box-shadow: none; + text-align: right; + width: 100%; + + &:disabled { + border-bottom: none; + } + + &:focus { + border-bottom: 1px solid var(--wp-admin-theme-color-darker-10); + box-shadow: 0 1px 0 var(--wp-admin-theme-color-darker-10); + } + } + + select[data-original="1"] { + background: transparent; + } + + select, + .is-before + span > input { + text-align: left; + } + } + + .edd-refund-submit-line-total { + background-color: $white !important; + + td { + text-align: right; + } + } + + .edd-refund-submit-line-total-amount { + display: inline-block; + margin-left: 20px; + text-align: left; + width: $fixed-column-width; + } + + #edd-refund-submit-subtotal td { + border-top: 2px solid $wp-border; + } + + @media screen and ( max-width: 782px ) { + td.column-total { + margin-bottom: 16px; + } + + .edd-refund-submit-line-total-amount { + padding-right: 16px; + width: unset; + } + } +} + +.edd-submit-refund-actions { + margin: 16px 0 0; +} + +.did-refund { + .refund-items, + .edd-submit-refund-actions { + display: none; + } +} diff --git a/assets/css/admin/orders/style.scss b/assets/css/admin/orders/style.scss new file mode 100644 index 00000000000..8b9e14d9f55 --- /dev/null +++ b/assets/css/admin/orders/style.scss @@ -0,0 +1,2 @@ + +@import "refunds-modal"; diff --git a/assets/css/admin/pass-handler.scss b/assets/css/admin/pass-handler.scss new file mode 100644 index 00000000000..f65fa4e8f7a --- /dev/null +++ b/assets/css/admin/pass-handler.scss @@ -0,0 +1,83 @@ +@import "../variables/variables"; + +.edd-pass-handler { + &__description { + display: grid; + gap: 1em; + margin-bottom: 1em; + } + + &__control { + display: flex; + gap: 4px; + flex-wrap: wrap; + + > input { + max-width: 250px !important; + } + + + .notice { + max-width: 400px; + margin-top: 1em; + } + + .button { + margin: 0; + } + } + + &__loading { + display: flex; + align-items: center; + gap: .5em; + + &:before { + background: none; + display: block; + @include edd-spinner( + 12px, + $wp-input-border, + $wp-alternate + ); + } + } + + &__verifying-wrap { + display: flex; + position: fixed; + left: 36px; + right: 0; + top: 0; + bottom: 0; + background: rgba(0,0,0,.5); + justify-content: center; + align-items: center; + z-index: 1; + + p { + background: $white; + border: 1px solid $wp-input-border; + border-radius: 4px; + padding: 2em; + } + + @media only screen and (min-width: $break-large) { + .wp-admin:not(.folded) & { + left: 160px; + } + } + + @media only screen and (max-width: $break-medium) { + left: 0; + } + } + + &__verifying ul#adminmenu #menu-posts-download ul.wp-submenu li.current a:before { + border-right-color: #787878; + } + + &__actions { + display: flex; + gap: 4px; + } +} diff --git a/assets/css/admin/promos/_overlay.scss b/assets/css/admin/promos/_overlay.scss new file mode 100644 index 00000000000..6709429a9ed --- /dev/null +++ b/assets/css/admin/promos/_overlay.scss @@ -0,0 +1,89 @@ +/* Overlay Notice +-------------------------------------------------- */ + +.edd-promo-notice__overlay { + display: none; + position: fixed; + background: rgba($gray-100,.75); + top: 0; + right: 0; + bottom: 0; + left: 160px; + z-index: 11; + justify-content: center; + align-items: center; + + .folded & { + left: 36px; + } + + @media screen and (max-width: $break-medium) { + left: 0; + } +} + +.edd-admin-notice-overlay { + display: none; + background-color: $white; + padding: 2.5em; + text-align: center; + max-width: 650px; + position: relative; + flex-direction: column; + + .edd-promo-notice__overlay & { + display: flex; + } + + h2 { + line-height: 1.6em; + margin: 0 auto; + max-width: 540px; + } + + .edd-promo-notice__features { + text-align: left; + display: grid; + grid-template-columns: repeat( 3, auto ); + margin: 2em auto; + gap: 0 1.5em; + + li { + display: flex; + gap: .5em; + align-items: center; + } + + @media screen and (max-width: $break-small) { + grid-template-columns: unset; + } + } + + .button-primary { + padding: 4px 36px; + margin: 0 auto .5em; + max-width: 360px; + } + + &__link { + color: $gray-100; + } + + .edd-promo-notice-dismiss.button-link { + position: absolute; + color: $wp-text; + text-decoration: none; + font-size: 2em; + top: 0; + right: .5em; + + &:active, + &:hover { + color: $gray-100; + } + } + + @media screen and (max-width: $break-medium) { + margin: 1em; + } +} diff --git a/assets/css/admin/promos/style.scss b/assets/css/admin/promos/style.scss new file mode 100644 index 00000000000..a4414c4082f --- /dev/null +++ b/assets/css/admin/promos/style.scss @@ -0,0 +1,99 @@ +@import '../../variables/variables'; + +.edd-admin-notice-top-of-page { + font-size: 15px; + line-height: 1.4; + color: #fff; + margin-left: -20px; + padding: 12px 32px 12px 20px; + background: #2d6ca2; + + &.edd-pro-inactive { + background: $wp-red-50; + } + + @media screen and ( min-width: 783px ) { + padding: 10px 46px 10px 22px; + } + + @media screen and ( min-width: 961px ) { + text-align: center; + } + + a { + color: #fff; + + &:hover { + text-decoration: none; + } + } + + .button-link { + position: absolute; + top: 48px; + right: -1px; + font-size: 20px; + color: #fff; + font-weight: bold; + text-decoration: none; + margin-left: 5px; + padding: 6px 10px; + + &:hover, &:active, &:focus { + color: #fff; + text-decoration: none; + } + + @media screen and ( min-width: 601px ) { + top: 1px; + } + + @media screen and ( min-width: 783px ) { + right: 9px; + } + } +} + +/* Five Star Review Request +------------------------------------------------------------- */ +#edd-admin-notice-five-star-review { + display: grid; +} + +#edd_dashboard_sales .edd-promo-notice { + border-bottom: 1px solid #c3c4c7; +} + +.edd-review-actions { + display: flex; + gap: 6px; + margin: 0 0 16px 0; +} + +.edd-promo-notice .edd-peeking { + align-self: flex-end; + justify-self: flex-end; + margin-right: 16px; + margin-bottom: -1px; +} + +@media screen and (max-width: $break-medium) { + #edd-admin-notice-five-star-review.notice .edd-peeking { + margin-bottom: -6px; + } +} + +@media screen and (min-width: $break-mobile) { + .edd-promo-notice.notice-info .edd-peeking { + justify-self: flex-start; + margin-right: 0; + margin-left: 250px; + } +} + +.edd-review-step, +.edd-promo-notice .edd-peeking { + grid-area: 1/-1; +} + +@import "overlay"; diff --git a/assets/css/admin/reports/_filter.scss b/assets/css/admin/reports/_filter.scss new file mode 100644 index 00000000000..f0f4b378a90 --- /dev/null +++ b/assets/css/admin/reports/_filter.scss @@ -0,0 +1,346 @@ + +#edd-filters { + padding: 10px; + margin: 0; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + + .filter-items { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px; + float: none; + flex-grow: 1; + + .graph-option-section { + display: flex; + align-items: center; + } + + .edd-date-range-picker[data-range='other'] { + .edd-graphs-date-options { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + + .edd-date-range-dates, .edd-date-range-relative-dates { + display: none; + } + } + + .edd-date-range-options { + display: inline-block; + margin: 10px 0; + } + + .edd-graphs-date-options { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + } + + .edd-date-range-dates { + display: flex; + align-items: center; + border: 1px solid #8c8f94; + border-left: none; + color: #2c3338; + padding: 4px 10px; + margin-left: -5px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + cursor: pointer; + gap: 4px; + &.hidden { + display: none; + } + } + + .edd-date-range-selected-date { + display: inline-block; + } + + .edd-date-range-relative-dates { + display: flex; + align-items: center; + margin-left: 10px; + &.hidden { + display: none; + } + } + + .edd-date-range-selected-relative-date { + position: relative; + display: flex; + align-items: center; + border: 1px solid #8c8f94; + padding: 4px 2px 4px 6px; + color: #2c3338; + margin-left: 10px; + margin-right: 10px; + border-radius: 4px; + cursor: pointer; + .arrow-down { + width: 16px; + height: auto; + margin-left: 6px; + margin-top: 2px; + vertical-align: middle; + } + &.opened { + .edd-date-range-relative-dropdown { + display: block; + } + } + } + + .edd-date-range-relative-dropdown { + position: absolute; + z-index: 99; + width: 420px; + left: 50%; + top: 100%; + margin-top: 10px; + transform: translateX( -50% ); + background-color: #fff; + border: 1px solid #8c8f94; + border-radius: 4px; + box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.25); + display: none; + &:after { + height: 10px; + width: 10px; + position: absolute; + content: ''; + background: white; + border: 1px solid #8c8f94; + border-top-width: 0px; + border-left-width: 0px; + transform: rotate(-135deg); + top: -6px; + left: calc(50% - 4px); + } + + .spinner { + display: none; + } + &.loading { + padding: 10px; + text-align: center; + .spinner { + display: inline-block; + visibility: visible; + margin: 0; + float: unset; + } + } + &.loading *:not(.spinner) { + display: none; + } + + ul li { + display: flex; + align-items: center; + padding: 2px 10px; + opacity: 0.85; + gap: 20px; + &:hover, &.active { + cursor: pointer; + color: var( --wp-admin-theme-color ); + opacity: 1; + } + .date-range-name { + width: 110px; + } + } + } + + @media screen and ( max-width: 950px ) { + .graph-option-section { + margin-top: 8px; + width: 100%; + } + + .edd-date-range-picker { + flex-wrap: wrap; + } + + .edd-graphs-date-options { + width: 100%; + max-width: 100%; + min-height: 40px; + font-size: 14px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + + .edd-date-range-dates { + width: 100%; + margin-top: 10px; + border: 1px solid #8c8f94; + margin-left: unset; + border-radius: 4px; + font-size: 14px; + padding: 8px 6px 8px 8px; + } + + .edd-date-range-relative-dates { + width: 100%; + flex-wrap: wrap; + margin-left: 0px; + margin-top: 6px; + } + + .edd-date-range-selected-relative-date { + width: 100%; + margin-top: 8px; + margin-left: 0px; + margin-right: 0; + font-size: 14px; + padding: 8px 6px 8px 8px; + flex-wrap: wrap; + .arrow-down { + margin-left: auto; + } + } + .edd-date-range-relative-dropdown { + position: relative; + width: 100%; + left: 0; + top: 0; + transform: unset; + box-shadow: unset; + border: unset; + margin: 0; + + &:after { + display: none; + } + + ul { + margin-bottom: 0; + li { + padding-left: 0; + padding-right: 0; + justify-content: space-between; + flex-wrap: wrap; + gap: unset; + .date-range-name, .date-range-dates { + width: 100%; + } + } + } + + } + } + + } + + > p { + color: $gray-700; + } + + input[type="text"].edd_datepicker, + input[type="number"] { + max-width: 105px; + } + + + input[type="number"], + .button-secondary { + margin-bottom: 0; + } + + .search-form { + margin: 0; + } + + @media screen and ( max-width: 480px ) { + span { + margin: 2px 0; + } + } +} + +#edd-advanced-filters { + position: relative; + + .inside { + z-index: 99; + position: absolute; + top: 29px; + right: 0; + border: 1px solid $gray-200; + padding: 0; + background: $white; + box-shadow: 0 3px 5px rgba(0,0,0,0.2); + min-width: 285px; + opacity: 0; + visibility: hidden; + } + + fieldset { + display: block; + padding: 10px 15px 15px; + margin: 10px 0; + + &:not(:last-of-type) { + border-bottom: 1px solid $gray-200; + } + + &:last-of-type { + padding-bottom: 5px; + } + + &.edd-add-on-filters { + label, + span, + p, + div { + display: block; + margin-bottom: 2px; + } + } + } + + div.edd-select-chosen:not(:last-child) { + margin-bottom: 10px; + } + + &.open { + .edd-advanced-filters-button { + background: $gray-200; + border-color: $gray-600; + box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.5 ); + -webkit-transform: translateY(1px); + transform: translateY(1px); + } + + .inside { + visibility: visible; + opacity: 1; + -webkit-transition: opacity 0.2s ease-in; + -moz-transition: opacity 0.2s ease-in; + -o-transition: opacity 0.2s ease-in; + transition: opacity 0.2s ease-in; + } + } +} + +.download_page_edd-reports { + #edd-filters { + margin-bottom: -1px; + box-shadow: none; + + @media screen and ( max-width: 782px ) { + gap: 0; + } + } +} + +.edd-old-log-filters { + margin-top: -30px; + margin-left: 2px; +} diff --git a/assets/css/admin/reports/_layout.scss b/assets/css/admin/reports/_layout.scss new file mode 100644 index 00000000000..23cb1abd1fd --- /dev/null +++ b/assets/css/admin/reports/_layout.scss @@ -0,0 +1,27 @@ +@media screen and (min-width: $break-small) { + #edd-reports-charts-wrap { + display: -ms-grid; + display: grid; + grid-template-columns: repeat(2, minmax(200px, 50%)); + grid-gap: 20px; + } + + .edd-reports-chart { + margin-bottom: 0; + } + + .edd-reports-chart-line, + .edd-reports-chart-bar { + grid-column: 1 / span 2; + } +} + +.edd-reports-chart { + height: 500px; + max-height: 700px; +} + +.chart-timezone { + font-size: .75rem; + color: #CCCCCC; +} diff --git a/assets/css/admin/reports/_mobile-link.scss b/assets/css/admin/reports/_mobile-link.scss new file mode 100644 index 00000000000..6ee177f012b --- /dev/null +++ b/assets/css/admin/reports/_mobile-link.scss @@ -0,0 +1,27 @@ +.edd-mobile-link { + line-height: 32px; + + a { + text-decoration: none; + + &:before, + &:after { + display: inline-block; + -webkit-font-smoothing: antialiased; + font: normal 20px/30px "dashicons"; + vertical-align: top; + margin: 1px 0 0 0; + padding: 0; + } + + &:before { + content: "\f470"; + color: $gray-700; + margin-right: -3px; + } + + &:after { + content: "\f504"; + } + } +} diff --git a/assets/css/admin/reports/_tiles.scss b/assets/css/admin/reports/_tiles.scss new file mode 100644 index 00000000000..d13f679774a --- /dev/null +++ b/assets/css/admin/reports/_tiles.scss @@ -0,0 +1,145 @@ +#edd-reports-tiles-wrap #dashboard-widgets .sortable-placeholder { + padding: 0; + margin: 0 0 20px 0; + line-height: 0; + box-sizing: border-box; + height: 110px; +} + +#edd-reports-tiles-wrap #dashboard-widgets #primary-sortables { + margin-left: 0; +} + +#edd-reports-tiles-wrap #dashboard-widgets #tertiary-sortables { + margin-right: 0; +} + +#edd-reports-tiles-wrap { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + grid-gap: 20px; +} + +.edd-reports-tile { + text-align: center; + padding: 20px 10px 35px 10px; + display: flex; + flex-direction: column; + justify-content: center; + border: 1px solid #e5e5e5; + background: #fafafa; + position: relative; + box-sizing: border-box; + gap: .5em; + >span:not(.tile-compare) { + width: 100%; + } +} + +.edd-reports-tile .tile-label { + text-align: center; + text-transform: uppercase; + font-size: 12px; + font-weight: normal; + color: $wp-gray-2; +} + +.edd-reports-tile .tile-value { + color: #333; + font-size: 2em; + line-height: 1; + transition: all .2s ease-in-out; + display: flex; + justify-content: center; + flex-direction: column; + gap: .25em; +} + +.edd-reports-tile:hover { + border: 1px solid #aaa; +} + +.edd-reports-tile:hover .tile-value:not(.tile-no-data) { + transform: scale(1.05); +} + +.edd-reports-tile .tile-amount { + color: rgb(39, 148, 218); +} + +.edd-reports-tile .tile-number { + color: rgb(153, 102, 255); +} + +.edd-reports-tile .tile-amount, +.edd-reports-tile .tile-number { + color: #fff; +} + +.edd-reports-tile .tile-value.tile-no-data { + color: #ddd; +} + +.edd-reports-tile .tile-value.tile-url { + font-size: 1.5em; +} + +.edd-reports-tile .tile-relative { + font-size: 12px; + font-weight: normal; + color: #888; +} + +.edd-reports-tile span.dashicons { + display: inline-block; + font-size: 30px; + line-height: 20px; + height: 20px; + width: 20px; + position: relative; + top: 4px; + left: -5px; + margin-left: -5px; + color: $wp-inactive; +} + +.edd-reports-tile .tile-relative { + span.dashicons { + top: -5px; + left: -3px; + margin-left: 0px; + } + + span.dashicons-arrow-down, + span.dashicons-arrow-up.reverse { + color: $wp-red-50; + } + + span.dashicons-arrow-up, + span.dashicons-arrow-down.reverse { + color: $wp-green-50; + } +} + +.edd-reports-tile .tile-compare { + position: absolute; + right: 0; + bottom: 0; + color: #aaa; + font-size: 11px; + line-height: 1em; + background-color: #fff; + border-left: 1px solid #e5e5e5; + border-top: 1px solid #e5e5e5; + border-bottom: 1px solid #fff; + border-right: 1px solid #fff; + border-top-left-radius: 8px; + padding: 4px 0 0 9px; + margin: 0 -1px -1px 0; +} + +.edd-reports-tile:hover .tile-compare { + border-left: 1px solid #bbb; + border-top: 1px solid #bbb; + color: #777; +} diff --git a/assets/css/admin/reports/style.scss b/assets/css/admin/reports/style.scss new file mode 100644 index 00000000000..489bf2ab476 --- /dev/null +++ b/assets/css/admin/reports/style.scss @@ -0,0 +1,11 @@ +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/variables"; +@import "~@wordpress/base-styles/mixins"; +@import "~@wordpress/base-styles/breakpoints"; +@import "~@wordpress/base-styles/animations"; +@import "../../variables/colors"; + +@import "filter"; +@import "layout"; +@import "mobile-link"; +@import "tiles"; diff --git a/assets/css/admin/settings/_licenses.scss b/assets/css/admin/settings/_licenses.scss new file mode 100644 index 00000000000..33824a9bac2 --- /dev/null +++ b/assets/css/admin/settings/_licenses.scss @@ -0,0 +1,167 @@ +.wrap-licenses { + + .edd-licenses__description { + margin: 2em 1em; + } + + .form-table, + thead, + tfoot, + tr, + th, + caption { + display: block; + + @media screen and (min-width: $break-small) { + display: unset; + } + } + + tbody { + display: grid; + gap: 1em; + } + + .form-table { + tr { + margin: 0; + background: $white; + border: 1px solid $wp-gray-5; + border-radius: 3px; + padding: 0; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: space-between; + + @media screen and (min-width: $break-small) { + display: grid; + grid-template-columns: 200px 1fr; + } + } + + th { + background: $wp-alternate; + margin-bottom: 2.5em; + padding: 1em; + border-bottom: 1px solid $wp-gray-5; + width: unset; + + @media screen and (min-width: $break-small) { + border-bottom: none; + margin-bottom: 0; + display: flex; + align-items: center; + } + } + + td { + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 2.5em; + flex-grow: 1; + + @media screen and (min-width: $break-small) { + flex-direction: row; + gap: unset; + } + + input.regular-text { + margin: 0; + width: 100%; + max-width: 250px; + } + + button { + margin: 0; + } + } + + .edd-license__control { + flex-grow: 1; + padding: 0 1em; + display: flex; + gap: 4px; + align-items: center; + justify-content: center; + + @media screen and (min-width: $break-small) { + justify-content: flex-end; + } + } + + .edd-licensing__actions { + display: flex; + gap: 4px; + } + } + + .edd-license-data[class*="edd-license-"] { + background: $wp-alternate; + padding: 1em; + border-top: 1px solid $wp-gray-5; + margin: 0; + width: 100%; + box-sizing: border-box; + display: flex; + align-items: flex-end; + + a { + color: #444; + + &:hover { + text-decoration: none; + } + } + + @media screen and (min-width: $break-small) { + border-top: none; + width: unset; + flex-basis: 100%; + align-items: center; + + &:not(:only-child) { + flex: 0 1 300px; + } + } + } + + .edd-license-data { + &.license-expires-soon-notice { + background-color: #00a0d2; + color: #fff; + border-color: #00a0d2; + } + + &.edd-license-expired { + background-color: #e24e4e; + color: #fff; + border-color: #e24e4e; + } + + &.edd-license-error, + &.edd-license-missing, + &.edd-license-invalid, + &.edd-license-site_inactive, + &.edd-license-item_name_mismatch { + background-color: #ffebcd; + border-color: #ffebcd; + } + + p { + font-size: 13px; + margin-top: 0; + } + + &.license-expires-soon-notice a, + &.edd-license-expired a { + color: $white; + + &:hover { + text-decoration: none; + } + } + } +} diff --git a/assets/css/admin/settings/style.scss b/assets/css/admin/settings/style.scss new file mode 100644 index 00000000000..66fe927bc88 --- /dev/null +++ b/assets/css/admin/settings/style.scss @@ -0,0 +1,58 @@ +@import "../../variables/variables"; +@import 'licenses'; + +.edd-settings-content { + max-width: $break-huge; +} + +.edd-settings-colors, +.edd-settings-color { + display: flex; + flex-wrap: wrap; + gap: 1em; +} + +.edd-settings-color { + flex-direction: column; +} + +.edd-upload-button-wrapper { + width: 100%; + display: flex; + gap: 5px; +} + +.edd-upload-button-wrapper button.edd_settings_upload_button { + margin-bottom: 0; +} + +#edd-payment-gateways a.button.edd-settings__button-settings { + position: absolute; + right: 2em; + background-image: $icon-settings; + background-size: 1em; + background-repeat: no-repeat; + background-position: center; + min-height: unset; + height: 1.5em; + width: 1.5em; + border: none; + background-color: $wp-alternate; + + &:hover, + &:active { + background-image: $icon-settings; + background-size: 1em; + background-repeat: no-repeat; + background-position: center; + } + + .edd-plugin__active & { + display: block; + } +} + +.edd-settings__list--disc { + list-style: disc; + list-style-position: inside; +} diff --git a/assets/css/admin/style.scss b/assets/css/admin/style.scss new file mode 100644 index 00000000000..516da9c4319 --- /dev/null +++ b/assets/css/admin/style.scss @@ -0,0 +1,3374 @@ +/** + * EDD Admin CSS + * + * @package EDD + * @subpackage Admin CSS + * @copyright Copyright (c) 2015, Pippin Williamson + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License + */ + +@import "../variables/variables"; + +@import "downloads/style"; +@import "forms/style"; + +@import "upgrades/v3"; + +@import "reports/style"; + +@import "orders/style"; + +@import "promos/style"; + +@import "gateways/style"; + +@import "notifications/style"; + +@import "general/style"; + +@import "settings/style.scss"; + +/** + * Notes: + * + * [1] Backwards compatibility for vertical tabs < 3.0 + */ + +.edd-hidden { + display: none; +} + +.edd-clearfix:after { + content: ""; + display: table; + clear: both; +} + +.edd-wrap a, +.edd-notice .notice-dismiss { + text-decoration: none; +} + +/** + * Tag specificity should not be needed, but cannot + * safely be removed for fear of breaking even more things. + */ +.wp-core-ui .edd-delete, +a.edd-delete { + color: #a00; +} + +.wp-core-ui .edd-delete:hover, +a.edd-delete:hover { + color: #f00; +} + +/* General Settings Styles +-------------------------------------------------------------- */ +body.post-type-download #contextual-help-link-wrap, +body.post-type-download #screen-options-link-wrap { + top: 5px !important; +} + +body.post-type-download #screen-meta { + margin: 0px 0px -1px -20px; +} + +#edd-header { + border-top: 5px solid #0c5d95; + border-bottom: 1px solid #c3c4c7; + padding: 20px 0px; + margin-left: -20px; + background: #fff; +} + +#edd-header-wrapper { + display: flex; + justify-content: space-between; + padding: 0 20px; + align-items: center; +} + +#edd-header img { + display: block; + max-width: 300px; + margin: 0; +} + +.edd-header-page-title-wrap { + font-size: 1.75em; + margin-top: -5px; + margin-right: auto; + padding-left: 7px; +} + +.edd-header-separator { + margin-top: -2px; + opacity: 0.25; +} + +.edd-header-page-title { + font-weight: 400; + font-size: 1em; + line-height: 1.3em; + display: inline; +} + +.edd-header-page-title-wrap .button { + margin-left: 5px; +} + +.no-js #edd-header-actions { + display: none; +} + +#edd-header .edd-round { + position: relative; + background-color: #f3f4f5; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + margin-left: 10px; + cursor: pointer; + transition: background-color .2s ease; +} + +button.edd-round { + border: none; +} + +#edd-header button.edd-round:hover { + background-color: #e5e5e5; +} + +button.edd-round:focus, +button.edd-round:active { + outline: 2px solid #0c5d95; +} + +#edd-header .edd-number { + position: absolute; + background-color: #df2a4a; + width: 16px; + height: 16px; + font-weight: 600; + font-size: 10px; + color: #fff; + top: -8px; + left: 50%; + transform: translateX(-50%); + margin: 0; + -webkit-animation: bounce 2s 5; + animation: bounce 2s 5; +} + +#edd-header .edd-number.edd-hidden { + display: none !important; +} + +#edd-header .edd-round svg { + width: 20px; + height: 20px; +} + +@media screen and (max-width: 840px) { + .edd-header-separator{ + display: none; + } + + #edd-header img { + display: none; + } +} + + +.edd_datepicker { + height: 29px; +} + +.edd-from-to-wrapper input { + width: 105px; + margin: 0; + position: relative; + z-index: 1; +} + +.edd-from-to-wrapper input[name*="start"], +.edd-from-to-wrapper input[name="filter_from"] { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.edd-from-to-wrapper input[name*="end"], +.edd-from-to-wrapper input[name="filter_to"] { + margin-left: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.edd-from-to-wrapper input:focus { + z-index: 2; + position: relative; +} + +.edd-settings-sub-nav { + margin: 0 0px 10px 0; + width: 100%; + border-bottom: 1px solid #ccc; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); +} + +.edd-settings-sub-nav a { + padding: 13px; + display: block; +} + +.edd-settings-sub-nav a.current { + border-bottom: 4px solid #000; + padding-bottom: 9px; +} + +.admin-color-fresh .edd-settings-sub-nav a.current { + border-bottom-color: #00a0d2; +} + +/* Blue */ +.admin-color-blue .edd-settings-sub-nav a.current { + border-bottom-color: #096484; +} + +/* Coffee */ +.admin-color-coffee .edd-settings-sub-nav a.current { + border-bottom-color: #c7a589; +} + +/* Ectoplasm */ +.admin-color-ectoplasm .edd-settings-sub-nav a.current { + border-bottom-color: #a3b745; +} + +/* Midnight */ +.admin-color-midnight .edd-settings-sub-nav a.current { + border-bottom-color: #e14d43; +} + +/* Ocean */ +.admin-color-ocean .edd-settings-sub-nav a.current { + border-bottom-color: #627c83; +} + +/* Sunrise */ +.admin-color-sunrise .edd-settings-sub-nav a.current { + border-bottom-color: #be3631; +} + +/* Light */ +.admin-color-light .edd-settings-sub-nav a.current { + border-bottom-color: #888; +} + +/* bbPress Color Schemes */ + +/* Evergreen */ +.admin-color-evergreen .edd-settings-sub-nav a.current { + border-bottom-color: #36533f; +} + +/* Mint */ +.admin-color-mint .edd-settings-sub-nav a.current { + border-bottom-color: #4f6d59; +} + +.download_page_edd-settings .edd-check-wrapper { + clear: both; +} + +.download_page_edd-settings .form-table tr>th>strong, +.download_page_edd-settings .form-table tr>th>h3 { + font-size: 1.2em; + font-weight: 600; + margin: 0 auto; +} + +.edd-sortable-list { + margin: 0; + width: 300px; + position: relative; +} + +.edd-sortable-list li { + margin: 0; + padding: 0; + position: relative; + height: 28px; + cursor: move; +} + +.edd-sortable-list li label * { + vertical-align: middle; +} + +.edd-sortable-list li label:after { + display: block; + width: 17px; + height: 17px; + position: absolute; + right: 6px; + top: 0px; + color: #aaa; + font-family: dashicons; + font-size: 17px; + content: '\f228'; + cursor: move; +} + +.form-table .edd-sortable-list li label { + display: block; + height: 28px; + padding: 0; + margin: 0; +} + +.edd-sortable-list .payment-icon { + width: 32px; + height: 24px; + position: relative; + top: -2px; + margin-right: 5px; +} + +/* Tooltips */ +.edd-help-tip { + cursor: help; + margin-top: -2px; + font-size: 24px; + color: grey; +} + +.edd-ui-tooltip { + position: absolute; + background: #333 !important; + border-width: 1px !important; + border-radius: 3px !important; + box-shadow: 1px 1px 2px 1px rgba(214, 214, 214, 0.5) !important; + color: #dedede !important; + max-width: 300px !important; + padding: 7px !important; + text-rendering: optimizeLegibility; + text-shadow: none !important; + z-index: 9999 !important; +} + +/* =Payment Icon Styling +-------------------------------------------------------------- */ + +.download_page_edd-settings .edd-settings-payment-icon-wrapper { + margin-top: 5px; +} + +.download_page_edd-settings .edd-settings-payment-icon-wrapper input { + margin-top: 1px; +} + +.download_page_edd-settings .form-table .edd-settings-payment-icon-wrapper input[type="checkbox"]+label { + margin: 0; + display: inline-block; +} + +.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-icon-image { + margin-right: 5px; + width: 32px; + display: inline-block; + vertical-align: middle; +} + +.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-option-name { + vertical-align: middle; +} + + +/* =Tax Settings Style +-------------------------------------------------------------- */ +.download_page_edd-settings .taxrates th, +.download_page_edd-settings .taxrates td { + padding: 8px 10px; +} + +.download_page_edd-settings .taxrates td { + line-height: 1.5em; + vertical-align: top; + margin: 0; +} + +.download_page_edd-settings .taxrates .regular-text { + width: 100%; +} + +/* Insert Download +-------------------------------------------------------------- */ + +#TB_window { + overflow: hidden; +} + +#TB_title { + padding: 5px; +} + +#TB_ajaxContent { + width: calc(100% - 30px) !important; + padding: 15px; + margin: 0; + height: calc(100% - 118px) !important; +} + +#TB_ajaxWindowTitle { + font-size: 18px; + font-weight: 600; + line-height: 30px; +} + +#TB_closeWindowButton { + right: 6px; + top: 6px; +} + +#choose-download-wrapper { + width: 100%; +} + +#choose-download-wrapper .wrap { + overflow-y: scroll; + margin: 0; + padding: 0; + height: calc(100% - 50px); +} + +#choose-download-wrapper .submit-wrapper { + position: absolute; + width: 100%; + bottom: 0; + padding: 0; + margin: 0 0 0 -15px; + text-align: right; +} + +#choose-download-wrapper .submit-wrapper div { + background-color: #fafafa; + padding: 15px; + border-top: 1px solid #ddd; +} + +/* Media Buttons Styles +-------------------------------------------------------------- */ + +.wp-media-buttons .button.edd-thickbox { + padding-left: 0; +} + +.wp-media-buttons .button.edd-email-tags-inserter .dashicons { + margin-top: -2px; +} + +/* Add/View Order Styles +-------------------------------------------------------------- */ + +/** Mimic WordPress 5.0 block-editor header region styles. */ +.download_page_edd-payment-history .edit-post-editor-regions__header { + flex-shrink: 0; + height: auto; + border-bottom: 1px solid #e2e4e7; + z-index: 30; + position: -webkit-sticky; + position: sticky; + top: 32px; + + /** EDD-specific */ + margin-left: -20px; +} + +@media screen and (max-width: 782px) { + + .download_page_edd-payment-history .edit-post-editor-regions__header { + position: initial; + top: 46px; + } +} + +.download_page_edd-payment-history .edit-post-header { + height: 56px; + padding: 4px 2px; + background: #fff; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + + /** EDD-specific */ + max-width: 100%; + box-sizing: border-box; + padding-left: 20px; + padding-right: 20px; +} + +@media screen and (max-width: 782px) { + + .download_page_edd-payment-history .edit-post-header { + padding-left: 10px; + padding-right: 10px; + } +} + +@media (min-width: 280px) { + + .download_page_edd-payment-history .edit-post-header { + flex-wrap: nowrap; + } +} + +.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar { + order: 0; +} + +.download_page_edd-payment-history .edit-post-header .edit-post-header__settings { + order: 1; +} + +.download_page_edd-payment-history .edit-post-header #publishing-action, +.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar, +.download_page_edd-payment-history .edit-post-header .edit-post-header__settings { + display: flex; + align-items: center; +} + +.download_page_edd-payment-history .edit-post-header #publishing-action .spinner { + margin: 0 5px 0 0; +} + +.download_page_edd-payment-history .edit-post-header .button-primary { + margin: 2px; + height: 34px; + line-height: 32px; + font-size: 13px; +} + +#edd-order-items .hndle { + display: flex; + align-items: center; + justify-content: space-between; +} + +#edd-order-items .hndle .edd-toggle { + font-weight: normal; +} + +.edd-add-order-item td { + vertical-align: middle; +} + +.edd-add-order-item input { + width: 80%; +} + +.edd-add-order-item input[readonly] { + color: #555; + background: none; + border: 1px solid transparent; + box-shadow: none; +} + +.order-customer-info .customer-details-wrap { + margin: 15px 0; + align-items: center; +} + +.order-customer-info .customer-details-wrap .spinner { + margin: 0; +} + +.order-customer-info .customer-details { + display: flex; + flex-direction: column; +} + +.order-customer-info .customer-details .customer-since { + color: #666; + display: block; + margin: 4px 0 6px; +} + +.order-customer-info .customer-details>span { + margin-bottom: 5px; +} + +.edd-order-add-download-select .spinner { + display: none; +} + +/** Overview */ +table.edd-order-overview-summary { + border-width: 0; + table-layout: fixed; +} + +table.edd-order-overview-summary--refund { + border-width: 0; +} + +@media screen and (min-width: 782px) { + + .edd-order-overview .column-right { + text-align: right; + } +} + +.edd-ml-auto { + margin-left: auto !important; +} + +@media screen and (min-width: 782px) { + + .edd-ml-lg-auto { + margin-left: auto !important; + } +} + +.edd-ml-auto+.edd-ml-auto { + margin-left: 10px !important; +} + +/** Items */ +.edd-order-overview-summary__items-name { + align-self: flex-start; +} + +.edd-order-overview-summary__items> :nth-child(odd) { + background-color: #f9f9f9; +} + +@media screen and (min-width: 782px) { + + .edd-order-overview-summary__items tr:last-child th, + .edd-order-overview-summary__items tr:last-child td { + border-bottom: 1px solid #e5e5e5; + } +} + +@media screen and (max-width: 782px) { + + .edd-order-overview-summary .row-actions>*, + .edd-order-overview-summary__items-name .row-actions { + display: block !important; + } + + .edd-order-overview-summary .row-actions>*:not(:first-child):before { + display: none; + } +} + +.edd-order-overview-summary th:not(.column-primary) { + width: 100px; +} + +.edd-order-overview-summary .row-actions>*:not(:first-child):before { + color: #999; + content: " | "; +} + +.edd-order-overview-summary .row-actions .text { + color: #555; +} + +.edd-order-overview-summary .removable { + display: flex; + align-items: center; + position: relative; +} + +.edd-order-overview-summary .removable .delete { + display: inline-block; + margin-right: 10px; + margin-left: -8px; + padding: 10px; + border-right: 1px solid #e5e5e5; + color: #a00; +} + +.edd-order-overview-summary .removable .delete:hover { + color: #dc3232; +} + +/** Adjustments */ +.edd-order-overview-summary__adjustments .column-primary { + font-weight: 600; +} + +.edd-order-overview-summary__adjustments td small { + font-weight: normal; +} + +/** Totals */ +.edd-order-overview-summary__subtotal .column-primary, +.edd-order-overview-summary__tax tr:first-of-type .column-primary, +.edd-order-overview-summary__total .column-primary { + font-weight: 600; +} + +.edd-order-overview-summary__subtotal td, +.edd-order-overview-summary__adjustments td, +.edd-order-overview-summary__tax td, +.edd-order-overview-summary__total td { + vertical-align: middle; +} + +.edd-order-overview-summary__tax td small, +.edd-order-overview-summary__total td small { + font-weight: normal; +} + +.edd-order-overview-summary__total .total { + color: #017d5c; + display: inline-block; +} + +.edd-order-overview-summary__total .total.is-negative { + color: #a00; +} + +@media screen and (min-width: 783px) { + + .edd-order-overview-summary__adjustments .removable .delete { + margin-left: -50px; + } + + .edd-order-overview-summary__total .total { + font-size: 150%; + padding-top: 5px; + padding-bottom: 5px; + } +} + +.edd-order-overview-summary__total tr:last-child th, +.edd-order-overview-summary__total tr:last-child td:not(:first-of-type) { + border-top: 1px solid #e5e5e5; +} + +.edd-order-overview-summary__total .notice { + margin: -1px; +} + +.edd-order-overview-summary__total .notice p { + font-weight: normal; + margin: 0.5em 0; +} + +/** Refunds */ +.edd-order-overview-summary__refunds .column-primary { + font-weight: 600; +} + +.edd-order-overview-summary__refunds td small { + font-weight: normal; +} + +.edd-order-overview-summary__refunds tr:first-child td { + border-top: 1px solid #e5e5e5; +} + +/** Actions */ +#edd-order-overview-actions.inside { + border-top: 1px solid #ccd0d4; + margin-top: 0; + display: flex; + align-items: center; + flex-wrap: wrap; + justify-content: space-between; +} + +#edd-order-overview-actions.inside:empty { + padding: 0; + border-top: 0; +} + +#edd-order-overview-actions.inside>div { + display: flex; + align-items: center; +} + +#edd-order-overview-actions .edd-order-overview-actions__notice { + flex-basis: 100%; + margin-top: 15px; +} + +.edd-order-overview-actions .button { + width: 100%; + margin-bottom: 12px; +} + +.edd-order-overview-actions .button:last-of-type { + margin-bottom: 0; +} + +@media screen and (min-width: 782px) { + .edd-order-overview-actions .button { + width: auto; + margin-left: 12px; + margin-bottom: 0; + } + + .edd-order-overview-actions .button:first-of-type { + margin-left: auto; + } +} + +.edd-order-overview-actions__locked { + font-style: italic; + opacity: 0.80; +} + +@media screen and (max-width: 782px) { + + .edd-order-overview-actions__locked { + margin-bottom: 12px; + } +} + +.edd-order-overview-actions__refund .dashicons { + margin-right: 8px; +} + +/** Dialog */ +.edd-dialog .ui-button-icon-only { + font-size: 0; +} + +.download_page_edd-payment-history .ui-dialog, +.download_page_edd-payment-history .ui-dialog-content { + overflow: visible; +} + +.edd-order-overview-modal form>p { + margin-top: 0; +} + +.edd-order-overview-modal fieldset legend, +.edd-order-overview-modal form label { + display: block; + margin-bottom: 4px; +} + +.edd-order-overview-modal fieldset { + margin-bottom: calc(1em - 3px); +} + +.edd-order-overview-modal fieldset>p { + margin: 2px 0 3px; +} + +.edd-order-overview-modal form .submit { + margin: 0 -16px -16px; + padding: 16px; + background: #fcfcfc; + border-top: 1px solid #dfdfdf; + display: flex; + align-items: center; +} + +.edd-order-overview-modal form .submit .spinner { + margin: 0; +} + +.edd-order-overview-add-item [for="auto-calculate"] { + display: flex; + align-items: center; +} + +.edd-order-overview-add-item [for="auto-calculate"] input[type="checkbox"] { + margin-top: 0; +} + +.edd-order-overview-add-item [for="auto-calculate"] .label { + line-height: 1.15; + margin-left: 8px; +} + +.edd-order-overview-add-item [for="auto-calculate"] .label small { + margin-top: 4px; + display: block; + opacity: 0.75; +} + +.edd-order-overview-add-adjustment .notice, +.edd-order-overview-add-item .notice { + margin: 0 0 1rem; +} + +.edd-order-overview-add-adjustment #description, +.edd-order-overview-add-discount select { + width: 100%; +} + +.edd-order-overview-error { + font-style: italic; + color: #a00; + display: block; + margin: 4px 0; +} + +.edd-order-copy-download-link textarea { + width: 100%; +} + +.edd-order-resend-email-chooser legend { + font-weight: bold; + margin-bottom: 4px; +} + +.edd-order-resend-email-chooser p { + margin: 4px 0; +} + +/* Note Styles +-------------------------------------------------------------- */ + +.edd-notes .edd-note { + padding: 10px; + background-color: #ffffee; + border: 1px solid #cccc00; + width: 100%; + position: relative; + margin-bottom: 10px; + box-sizing: border-box; + overflow: hidden; +} + +.edd-notes .edd-note.deleting { + opacity: 0.5; +} + +.edd-notes .edd-note__header { + display: flex; + align-items: center; +} + +.edd-add-note .spinner { + float: none; + display: inline-block; + margin: 0; +} + +.edd-notes .edd-note time { + font-size: 11px; + color: #aaa; +} + +.edd-notes .edd-note .edd-note-author { + margin-right: 5px; +} + +.edd-notes .edd-note .edd-delete-note { + color: #a00; + font-weight: bold; + text-decoration: none; + margin-left: auto; +} + +.edd-notes .edd-note .edd-delete-note:hover { + color: #888; +} + +.edd-notes .edd-note p:last-child { + margin-bottom: 0; +} + +.edd-notes .edd-no-notes { + margin: 4px 0 10px 0; +} + +textarea[name="edd-note"] { + width: 100%; + min-height: 70px; + margin-top: 0; +} + +.edd-notes-wrapper { + width: 80%; +} + +.edd-note-pagination { + float: right; + margin: -35px 5px 15px 5px; +} + +.edd-note-pagination a, +.edd-note-pagination span.page-numbers { + padding: 5px 8px; + margin: 2px; + text-decoration: none; +} + +.edd-note-pagination a { + border: 1px solid #e5e5e5; + background: #fcfcfc; +} + +.edd-note-pagination a:last-child, +.edd-note-pagination span.page-numbers:last-child { + margin-right: 0; +} + +/* Discount Code Styles +-------------------------------------------------------------- */ + +#edd-products { + height: 100px; + min-width: 200px; +} + +#edd-add-discount input[type="text"], +#edd-edit-discount input[type="text"] { + width: 300px; +} + +#edd-add-discount .edd-discount-datetime input, +#edd-edit-discount .edd-discount-datetime input { + vertical-align: middle; +} + +#edd-add-discount input[type="text"].edd_datepicker, +#edd-edit-discount input[type="text"].edd_datepicker { + display: inline-block; + width: 183px; +} + +#edd-edit-discount textarea { + height: 100px; +} + +.edd-amount-type-wrapper { + position: relative; + display: flex; +} + +.edd-amount-type-wrapper select { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + width: auto !important; +} + +.edd-amount-type-wrapper #edd-amount { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + margin-right: -2px; + padding: 0 8px; + width: unset; + max-width: 125px; +} + +.edd-amount-type-wrapper input:focus { + z-index: 2; +} + +/* List Tables +-------------------------------------------------------------- */ +.post-type-download .tablenav.top .edd-select { + margin-right: 6px; +} + +.wp-list-table.customers .column-primary strong, +.wp-list-table.emails .column-primary strong, +.wp-list-table.addresses .column-primary strong, +.wp-list-table.discounts .column-primary strong, +.wp-list-table.orders .column-primary strong, +.wp-list-table.orderitems .column-primary strong, +.wp-list-table.orderadjustments .column-primary strong { + font-size: 14px; +} + +.wp-list-table.emails .column-customer .avatar, +.wp-list-table.customers .column-primary .avatar { + float: left; + margin-right: 10px; + margin-top: 1px; + border-radius: 5px; +} + +.wp-list-table.orders div.order-list-email { + font-size: .85em; + color: #888888; +} + +.wp-list-table.orders th.column-amount { + width: 100px; +} + + +/* Row Actions +-------------------------------------------------------------- */ + +.wp-list-table .row-actions span.activate a { + color: green; +} + +.wp-list-table .row-actions span.refund a { + color: #836fff; +} + +.wp-list-table .row-actions span.cancel a { + color: #cc8c00; +} + +.wp-list-table .row-actions span.cancel a:hover, +.wp-list-table .row-actions span.refund a:hover { + opacity: 0.8; +} + +.wp-list-table .type-download .row-actions { + color: #999; +} + +/* Nav Tab Styles +-------------------------------------------------------------- */ + +.no-js.edit-tags-php.post-type-download .wp-heading-inline { + position: absolute; + top: 0; +} + +.no-js.edit-tags-php.post-type-download .nav-tab-wrapper { + margin-top: 50px; +} + +.edit-tags-php.post-type-download .wrap .nav-tab-wrapper .page-title-action, +.download_page_edd-customers .wrap .nav-tab-wrapper .page-title-action, +.download_page_edd-discounts .wrap .nav-tab-wrapper .page-title-action, +.download_page_edd-payment-history .wrap .nav-tab-wrapper .page-title-action { + top: 3px; + margin-left: 10px; + line-height: 24px; +} +/* Payment History Styles +-------------------------------------------------------------- */ + +#edd-payments-filter ul.subsubsub { + margin-bottom: 8px; +} + +tr.status-refunded td { + background: #cecece; + border-top-color: #ccc; +} + +marquee { + padding: 0; + margin: 0; +} + +@media handheld, +only screen and (max-width: 640px) { + + .wp-list-table.downloads th { + width: auto !important; + } + +} + +#edd-download-link-textarea { + width: 100%; +} + +/* Metabox Styles +-------------------------------------------------------------- */ + +.edd_files_name_label { + width: 225px; + float: left; +} + +.edd_files_url_label { + width: 220px; + float: left; +} + +#postbox-container-1 .edd_files_name_label { + width: 80px; +} + +#postbox-container-1 .edd_files_url_label { + width: 80px; +} + +#edd_product_prices .inside, +#edd_product_files .inside { + margin-bottom: 0; +} +textarea#edd-payment-note { + width: 100%; + height: 4em; + margin: 0; +} + +#edd-order-items .row .edd-purchased-files-list-wrapper .download { + line-height: 1.4; +} + +#edd-order-items .edd-purchased-files-list-wrapper .edd-purchased-option { + color: #666; +} + +input[class*="edd-price-field"] { + max-width: 125px; +} + +[class*="item_"] [class*="edd-payment-details-download-"][type="number"].small-text, +#edd-order-download-quantity[type="number"].small-text, +#edd-order-download-tax[type="text"].small-text { + height: 25px; +} + +.item_price .edd-payment-details-download-quantity[type="number"].small-text, +#edd-order-download-quantity[type="number"].small-text { + width: 55px; +} + +.item_tax .edd-payment-details-download-item-tax[type="number"].small-text, +#edd-order-download-tax[type="text"].small-text { + width: 80%; + max-width: 125px; +} + +#edd_product_notes_field { + display: block; + margin: 12px 0 0; + height: 4em; + width: 100%; +} + +/* Payment Details +-------------------------------------------------------------- */ + +.edd-metabox-title-action { + margin: 0; + float: right; + padding: 4px 8px; + position: relative; + top: -1px; + text-decoration: none; + border: none; + border: 1px solid #ccc; + border-radius: 2px; + background: #f7f7f7; + text-shadow: none; + font-weight: 600; + font-size: 10px; + line-height: normal; + color: #0073aa; + cursor: pointer; + outline: 0; +} + +.edd-metabox-title-action:hover { + border-color: #008EC2; + background: #00a0d2; + color: #fff; +} + +.edd-edit-purchase-element .tablenav { + padding: 2px 10px 8px 10px; +} + +.edd-edit-purchase-element .edd-order-children-wrapper { + margin: 0 -1px; +} + +.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 table { + border-top: none; + border-bottom: none; +} + +.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 .tablenav { + display: none; +} + +.edd-edit-purchase-element[class*="columns-"] ul li { + padding-right: 1%; +} + +#edd-edit-order-form .columns-4 .column:nth-child(2n+1), +#edd-edit-order-form .columns-5 .column:nth-child(3n+1), +#edd-edit-order-form .column:nth-child(2n+1) { + margin-right: 0; +} + +#edd-edit-order-form input.large-text { + width: 90%; +} + +.edd-edit-purchase-element ul li.item_price { + width: 15%; +} + +.edd-edit-purchase-element ul li.item_price.item_quantity { + width: 25%; +} + +.edd-edit-purchase-element ul li.item_tax { + width: 15%; +} + +.edd-edit-purchase-element ul li.price { + width: 20%; +} + +.edd-admin-box-inside { + border-bottom: 1px solid #f1f1f1; + clear: both; + padding: 12px; + margin: 0; + word-wrap: break-word; +} + +.edd-admin-box-inside--row { + display: flex; + flex-wrap: wrap; + word-break: break-all; + justify-content: space-between; + align-items: center; +} + +.edd-admin-box-inside>p { + margin: 8px 3px; +} + +.edd-admin-box-inside .strong { + font-weight: 600; +} + +.edd-admin-box div:not(.edd-admin-box-inside--row) .label { + display: block; + margin-bottom: 4px; + margin-right: 0; +} + +.edd-admin-box .label--has-tip { + display: flex; + align-items: center; +} + +.edd-admin-box .label--has-tip .edd-help-tip { + margin-top: 0; + font-size: 20px; +} + +.edd-admin-box div:not(.edd-admin-box-inside--row) .label--has-checkbox { + margin-bottom: 0; +} + +.edd-payment-fees .fee-label { + color: #666; + font-weight: normal; +} + +.edd-admin-box .right { + float: right; +} + +#edd-order-refunds-list { + padding-left: 25px; +} + +#poststuff .edd-order-data .inside { + margin: 0; + padding: 0; +} + +.edd-order-data .edd-select-chosen { + width: 130px !important; +} + +.edd-order-data input.edd_datepicker { + width: 180px; +} + +.edd-order-data input[type="number"].edd-payment-time-hour, +.edd-order-data input[type="number"].edd-payment-time-min { + width: 50px; +} + +.edd-order-data .edd-tax-rate { + color: #9c9c9c; + font-style: italic; + padding: 5px; +} + +#edd_general_logs p { + margin: 0; + padding: 0; +} + +.edd-admin-box-inside span.label { + margin-right: 10px; +} + +#edd-order-resend-receipt .inside { + margin-top: 11px; +} + +.edd-order-resend-receipt-header { + font-size: 14px; + line-height: 1.4; +} + +.edd-admin-box-inside:last-child { + border-bottom: 0; +} + +#edd-edit-order-form .data-payment-key { + word-break: break-all; +} + +.edd-order-update-box #major-publishing-actions .button-secondary { + margin-right: 10px; +} + +.edd-order-update-box .button-primary { + margin-right: 0; +} + +.edd-edit-purchase-element .edd-select-chosen { + width: 196px; +} + +.edd-edit-purchase-element ul { + clear: both; + display: block; +} + +#edd-customer-details .actions { + float: right; +} + +.order-data-address h3 { + margin: 0 0 10px 0; +} + +.order-data-address #edd-order-address-state-wrap, +.order-data-address #edd-order-address-country-wrap { + display: inline-block; + width: 50%; + max-width: 300px; +} + +.edd-order-data input.small-text { + margin: 0; +} + +.edd-order-data input.med-text { + margin: 0; + width: 100px; +} + +.edd-edit-purchase-element ul li { + display: block; + line-height: 1.4; + position: relative; + margin: 0; + vertical-align: middle; + font-size: 13px; +} + +.edd-edit-purchase-element .row { + padding: 12px; +} + +.edd-edit-purchase-element .row:not(:last-child) { + border-bottom: 1px solid #eee; +} + +.edd-edit-purchase-element .row:nth-child(odd):not(.header) { + background-color: #f9f9f9; +} + +.edd-edit-purchase-element .row.header { + padding: 6px 12px; + font-weight: 600; + vertical-align: top; +} + +.edd-edit-purchase-element ul { + margin: 0 0 15px; +} + +.edd-edit-purchase-element ul:last-of-type { + margin-bottom: 0; +} + +#edd-order-data .data span { + color: #666; + font-weight: 600; +} + +.edd-edit-purchase-element .inside { + padding: 12px; +} + +.edd-edit-purchase-element .edd-purchased-download-title { + font-size: 14px; + font-weight: 500; +} + +.edd-edit-purchase-element .edd-purchased-download-title .deleted { + color: #777; +} + +.edd-edit-purchase-element .edd-purchased-download-actions { + color: #777; + line-height: 1.4; +} + +.edd-edit-purchase-element .edd-purchased-download-actions .edd-purchased-download-actions-label { + font-weight: 500; +} + +.edd-edit-purchase-element .edd-purchased-download-actions a { + color: #777; + font-size: 12px; +} + +.edd-edit-purchase-element .edd-purchased-download-actions a:hover { + color: #444; +} + +.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download { + color: #a00; +} + +.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download:hover { + color: #f00; +} + +.edd-add-download-to-purchase, +.edd-add-adjustment-to-purchase { + padding: 15px; + border-top: 1px solid #e5e5e5; + background-color: #f5f5f5; +} + +.edd-add-download-to-purchase .chosen-container, +.edd-add-adjustment-to-purchase .chosen-container { + width: 90% !important; + max-width: 220px !important; +} + +.edd-add-download-to-purchase .spinner, +.edd-add-adjustment-to-purchase .spinner { + margin: 0; + float: none; +} + +.edd-add-download-to-purchase .edd-add-order-quantity { + width: 40px; + height: 29px; + vertical-align: middle; +} + +.edd-add-download-to-purchase .edd-add-order-item-button, +.edd-add-adjustment-to-purchase .edd-add-adjustment-button, +.edd-add-adjustment-to-purchase input[type="text"] { + height: 29px; +} + +@media screen and (max-width: 1284px) { + + .edd-edit-purchase-element .edd-purchased-download-title { + font-size: 16px; + } + + .edd-edit-purchase-element ul li.item_price { + width: 22%; + } + + .edd-edit-purchase-element ul li.item_price.item_quantity { + width: 35%; + } + + .edd-edit-purchase-element ul li.item_tax { + width: 25%; + } + + .edd-edit-purchase-element ul li.price { + width: 20%; + } + + .edd-edit-purchase-element .edd-purchased-download-actions { + padding-top: 10px; + } +} + +@media screen and (max-width: 1024px) { + + .edd-edit-purchase-element ul li.item_price.item_quantity { + width: 40%; + } + + .edd-edit-purchase-element ul li.price { + width: 24%; + } + + .edd-edit-purchase-element .edd-purchased-download-actions { + padding-top: 15px; + } + + .edd-edit-purchase-element .edd-purchased-download-actions, + .edd-edit-purchase-element .edd-purchased-download-actions a { + font-size: 14px; + } + +} + +@media screen and (max-width: 782px) { + + .edd-edit-purchase-element ul li.item_price, + .edd-edit-purchase-element ul li.item_price.item_quantity { + padding-bottom: 10px; + } + + .edd-edit-purchase-element ul li.item_price.item_quantity { + width: 35%; + } + + .edd-edit-purchase-element ul li.item_tax, + .edd-edit-purchase-element ul li.price { + width: 20%; + padding-bottom: 10px; + } + + .edd-price-currency, + .edd-payment-details-download-amount { + font-size: 16px; + } + + .order-data-column input[type="email"] { + padding: 6px 10px; + } + + .edd-refund-submit-line-total td:last-of-type { + flex: 0 0 120px; + } + + #edd-item-tables-wrapper .addresses tbody tr { + display: grid; + } + + #edd-item-tables-wrapper .addresses tbody td:not(.no-items) { + padding-left: 35%; + } +} + +@media screen and (max-width: 600px) { + + .edd-edit-purchase-element ul li.item_price, + .edd-edit-purchase-element ul li.item_price.item_quantity, + .edd-edit-purchase-element ul li.item_tax { + width: 100%; + padding-bottom: 20px; + } + + .edd-edit-purchase-element ul li.price, + .edd-edit-purchase-element .edd-add-download-to-purchase ul li.item_tax { + width: 100%; + padding-bottom: 0; + } + + .edd-edit-purchase-element .edd-add-download-to-purchase-actions { + padding-top: 15px; + } +} + +/** Stats */ +#edd_product_stats .label { + display: inline-block; +} + +#edd_product_stats .product-sales-stats:before, +#edd_product_stats .product-earnings-stats:before { + color: #82878c; + font: normal 20px/1 'dashicons'; + display: inline-block; + padding: 0 2px 0 0; + position: relative; + top: 0; + left: -1px; + speak: none; + text-decoration: none !important; + vertical-align: top; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#edd_product_stats .product-sales-stats:before { + content: '\f174'; +} + +#edd_product_stats .product-earnings-stats:before { + content: '\f239'; +} + +/* Dashboard Page Styles +-------------------------------------------------------------- */ + +body.dashboard_page_edd-upgrades.js .postbox .hndle { + cursor: default; +} + +/* Dashboard Widget Styles +-------------------------------------------------------------- */ + +.edd_dashboard_widget table thead td { + border-bottom: 1px solid #ececec; + color: #777; +} + +.edd_dashboard_widget .table_left { + float: left; + width: 45%; +} + +.edd_dashboard_widget .table_right { + float: right; + width: 45%; +} + +.edd_dashboard_widget .inside { + font-size: 12px; +} + +.edd_dashboard_widget td { + padding: 3px 0; +} + +.edd_dashboard_widget .b, +.edd_dashboard_widget .t { + line-height: 1.5; + vertical-align: middle; +} + +.edd_dashboard_widget .b { + padding-right: 6px; + width: auto; +} + +.edd_dashboard_widget .t { + font-size: 12px; + padding-right: 12px; + color: #777; + width: 100%; +} + +.edd_dashboard_widget .label_heading { + border-top: 1px solid #ececec; + color: #8f8f8f; + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; + font-weight: normal; + display: block; + padding-top: 10px; + margin: 0 0 8px 12px; +} + +.edd_dashboard_widget .edd_dashboard_widget_subheading { + border-top: 1px solid #ececec; + color: #8f8f8f; + font-size: 14px; + padding-top: 10px; + margin: 1em 0 0 0; +} + +.edd_dashboard_widget .edd_dashboard_widget_subheading+.table { + margin: 8px 0 0 0; +} + +.edd_dashboard_widget .edd_price_label { + background: rgb(0, 118, 156); + border-radius: 3px; + color: white; + font-size: 10px; + padding: 2px 4px; + margin-right: 2px; +} + +.edd_dashboard_widget table { + width: 100%; + margin-left: 0; + margin-bottom: 1em; +} + +td.edd_order_label { + width: 80%; +} + +td.edd_order_price { + text-align: right; +} + +@media handheld, +only screen and (max-width: 1000px) { + + .edd_dashboard_widget .edd-recent-email { + display: none; + } + +} + +/* Reports Styles +-------------------------------------------------------------- */ + +/* Force a scrollbar when on the reports page (https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6718) */ +body.download_page_edd-reports { + overflow-y: scroll; +} + +.edd-chip { + font-size: 10px; + font-weight: bold; + text-transform: uppercase; + line-height: 1; + padding: 3px; + border-radius: 3px; + color: #fff; + background-color: #444; +} + +/* Keeping this rule for Software Licensing Reports */ +.edd-reports-wrapper .postbox h2, +.edd-reports-wrapper .postbox h3 { + font-size: 1.3em +} + +#edd-dashboard-widgets-wrap .metabox-holder { + padding-top: 0; +} + +.edd-reports-wrapper .postbox .edd-select { + max-width: 200px; + vertical-align: baseline; + margin-right: 4px; + margin-bottom: 16px; +} + +.download_page_edd-reports #edd-item-wrapper { + margin: 0; +} + +#edd-dashboard-widgets-wrap .postbox h2, +#edd-dashboard-widgets-wrap .postbox h3 { + cursor: default; +} + +.edd-date-range-options .edd_datepicker { + width: 105px; +} + +.edd-report-wrap { + clear: both; +} + +.edd-report-wrap h3 { + clear: both; + margin: 0 0 20px; +} + +.edd-reports-chart, +.edd-reports-table { + margin-bottom: 20px; +} + +.edd-admin--has-grid { + display: grid; + display: -ms-grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-gap: 20px; +} + +.edd-admin--has-grid .postbox { + margin-bottom: 0; +} + +.edd-admin--has-grid .edd-from-to-wrapper { + display: flex; + margin-bottom: 16px; + width: 100%; +} + +.edd-admin--has-grid .edd-from-to-wrapper input { + width: 100%; +} + +.edd-admin--has-grid .edd-from-to-wrapper span { + flex-grow: 1; +} + +.edd-admin--has-grid form { + display: flex; + flex-direction: column; + flex-wrap: wrap; + position: relative; +} + +fieldset.edd-to-and-from-container { + display: flex; + gap: 8px; + + select { + flex: 0 0 calc(50% - 6px); + } +} + +span.edd-to-and-from--separator { + line-height: normal; + align-self: center; + margin-bottom: 16px; +} + +.edd-admin--has-grid .postbox .edd-select { + max-width: 100%; + margin-right: 0; +} + +.edd-admin--has-grid .button.updating-message:before, +.edd-admin--has-grid .button.updated-message:before { + vertical-align: text-bottom; + margin: 0 5px 0 0; +} + +.edd-import-export-form .edd-progress { + background: #ddd; + border-radius: 15px; + height: 15px; + flex-basis: 100%; +} + +.edd-import-export-form .edd-progress div { + background: #ccc; + border-radius: 15px; + height: 100%; + width: 0; +} + +.edd-import-export-form .notice-wrap { + background-color: #f4f4f4; + border-style: solid; + border-width: 1px 0; + border-color: #eae9e9; + padding: 12px; + overflow: auto; + margin: 20px -12px -23px; + position: relative; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; +} + +.notice-wrap div.notice { + margin: 0; +} + +.admin-color-fresh .edd-import-export-form .edd-progress div { + background: #0073aa; +} + +.admin-color-light .edd-import-export-form .edd-progress div { + background: #888; +} + +.admin-color-blue .edd-import-export-form .edd-progress div { + background: #096484; +} + +.admin-color-coffee .edd-import-export-form .edd-progress div { + background: #c7a589; +} + +.admin-color-ectoplasm .edd-import-export-form .edd-progress div { + background: #a3b745; +} + +.admin-color-midnight .edd-import-export-form .edd-progress div { + background: #e14d43; +} + +.admin-color-sunrise .edd-import-export-form .edd-progress div { + background: #dd823b; +} + +.graph-option-section { + float: left; +} + +.edd-report-filters-title span { + display: block; + padding: 20px; +} + +#edd-graphs-filter form { + padding: 20px; +} + +#edd-graphs-filter label { + vertical-align: inherit; +} + +#edd-graphs-filter .graph-option-section { + display: inline-block; + line-height: 2em; + margin: 0 5px 0 0; + padding: 0; +} + +.download_page_edd-reports .section-content #post-body-content { + float: none; +} + +.download_page_edd-reports .section-content select[name="range"] { + display: none; +} + +.edd-mix-totals { + background-color: #fff; + border: 1px solid #e5e5e5; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); + padding: 10px; +} + +.edd-mix-chart { + display: inline-block; + width: 49%; + vertical-align: top; +} + +.edd-graph-notes { + color: #9c9c9c; +} + +.edd-graph-notes span { + display: block; +} + +.edd-pie-graph .legend { + display: none; +} + +.edd-pie-legend { + overflow: auto; + margin-top: 10px; +} + +.edd-legend-item-wrapper { + color: #333; + display: inline-block; + font-size: 8pt; + padding: 2px 5px 0px 5px; + width: 48%; + height: 20px; +} + +.edd-legend-color { + border: 1px solid #cfcfcf; + display: inline-block; + margin-right: 5px; + width: 20px; + height: 15px; +} + +.edd-pie-legend-item { + display: inline-block; + vertical-align: top; + width: 80%; +} + +#edd-reports-tiles-wrap .metabox-holder { + padding: 0; +} + +#edd-reports-tiles-wrap #dashboard-widgets { + overflow: auto; +} + +#edd-reports-tiles-wrap #dashboard-widgets .postbox-container { + width: 33.3%; +} + +/** Hide legacy report empty navigations */ +.download_page_edd-reports .section-content .tablenav.top { + display: none; +} + +#edd_tax_rates { + margin: 1em 0 0; +} + +[id*="edd-recapture-"].button { + font-size: 16px; + height: auto; + padding: 8px 14px; + margin: 6px 0 0; +} + +[id*="edd-recapture-"].button .dashicons { + line-height: 29px; + margin-right: 8px; +} + +[id*="edd-recapture-"].button .edd-loading, +[id*="edd-recapture-"].button .edd-loading:after { + border-radius: 50%; + display: inline-block; + width: 14px; + height: 14px; +} + +[id*="edd-recapture-"].button .edd-loading { + position: relative; + top: 3px; + margin-left: 4px; + box-shadow: 0 0 2px rgba(0, 0, 0, .2); +} + +[id*="edd-recapture-"].button .edd-loading { + -webkit-animation: edd-spinning 1.1s infinite linear; + animation: edd-spinning 1.1s infinite linear; + border-top: 2px solid rgba(255, 255, 255, 0.5); + border-right: 2px solid rgba(255, 255, 255, 0.5); + border-bottom: 2px solid rgba(255, 255, 255, 0.5); + border-left: 2px solid #fff; + font-size: 14px; + filter: alpha(opacity=0); + -ms-transform: translateZ(0); + transform: translateZ(0); +} + +#edd-recapture-disconnect.button .edd-loading.dark { + border-top-color: rgba(0, 0, 0, 0.2); + border-right-color: rgba(0, 0, 0, 0.2); + border-bottom-color: rgba(0, 0, 0, 0.2); + border-left-color: #666; + box-shadow: none; +} + +.recapture-notice { + position: relative; +} + +@-webkit-keyframes edd-spinning { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes edd-spinning { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +#edd-chartjs-tooltip { + position: absolute; + background-color: #fff; + -webkit-border-radius: 7px; + -moz-border-radius: 7px; + border-radius: 7px; + -webkit-transition: all .1s ease; + transition: all .1s ease; + pointer-events: none; + -webkit-transform: translate(-50%, 0); + transform: translate(-50%, 0); + font-size: 12px; + box-shadow: 0 0 0 1px rgba(89, 94, 100, .1), 0 15px 35px 0 rgba(89, 94, 100, .1), 0 5px 15px 0 rgba(0, 0, 0, .12); + min-width: 120px; + opacity: 0; +} + +.edd-chartjs-tooltip-key { + display: inline-block; + width: 10px; + height: 10px; + margin-right: 5px; +} + +/* Upgrades page styles +-------------------------------------------------------------- */ + +/* Settings page styles +-------------------------------------------------------------- */ + +#edd-send-test-summary-save-changes-notice .notice p { + font-size: 13px; +} + +#edd-send-test-summary-save-changes-notice, #edd-send-test-summary-notice { + display: flex; + margin-top: 5px; +} + +/* Global Graph Styles +-------------------------------------------------------------- */ + +.edd-graph .y1Axis { + color: rgb(237, 194, 64) !important; +} + +.edd-graph .y2Axis { + color: rgb(175, 216, 248) !important; +} + +/* API Table Styles +-------------------------------------------------------------- */ + +.wp-list-table.apikeys input.code { + width: 100%; + font-size: 10px; + cursor: text; + background: #fff; + border: 1px solid #ddd; + box-shadow: none; + color: #555; +} + + +/* Toggle Styles +-------------------------------------------------------------- */ + +.edd-toggle { + position: relative; + display: inline-block; + overflow: visible; +} + +.edd-toggle input[type="checkbox"] { + display: inline-block; + vertical-align: middle; + position: relative; + margin: 0; + padding: 0; + width: 42px; + min-width: 42px; + height: 24px; + background-color: #ccc; + -webkit-transition: background 0.2s ease; + transition: background 0.2s ease; + border-radius: 34px; + box-shadow: none; + border: none; +} + +.edd-toggle .label { + display: inline-block; + vertical-align: middle; + white-space: nowrap; +} + +.edd-toggle input[type="checkbox"]:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: white; + -webkit-transition: 0.1s transform ease; + transition: 0.1s transform ease; + border-radius: 50%; +} + +@media only screen and (max-width: $break-medium) { + .edd-toggle input[type="checkbox"]:checked:before { + margin: -.1875rem 0 0 -.25rem; + } +} + +.edd-toggle input[type="checkbox"]:checked { + background-color: #007cba; + background-color: var( --wp-admin-theme-color ); +} + +.edd-toggle input[type="checkbox"]:active, +.edd-toggle input[type="checkbox"]:focus { + outline: 0; + box-shadow: 0 0 0 1px #fff, 0 0 0 3px #7e8993; +} + +.edd-toggle input[type="checkbox"]:checked:active, +.edd-toggle input[type="checkbox"]:checked:focus { + box-shadow: 0 0 0 1px #fff, 0 0 0 3px #007cba; + box-shadow: 0 0 0 1px #fff, 0 0 0 3px var( --wp-admin-theme-color ); +} + +.edd-toggle input[type="checkbox"]:checked:before { + -webkit-transform: translateX(22px); + -ms-transform: translateX(22px); + transform: translateX(22px); +} + +.edd-toggle input+.label, +.edd-toggle .label+input { + margin-left: 5px; +} + +/* List Table Styles +-------------------------------------------------------------- */ + +.download_page_edd-tools .tablenav .actions { + overflow: visible; +} + +.edd_user_search_wrap { + position: relative; + overflow: visible; +} + +.edd_user_search_wrap .spinner { + position: absolute; + margin: 0; + padding: 0; + right: 4px; + top: -2px; +} + +.edd_user_search_wrap.loading .spinner { + visibility: visible; +} + +.edd_user_search_results { + position: absolute; + left: 0; + top: 20px; +} + +.edd_user_search_results a.edd-ajax-user-cancel { + position: absolute; + right: 6px; + top: 2px; +} + +.edd_user_search_results ul { + background: #fafafa; + border: 1px solid #dfdfdf; + overflow-y: scroll; + padding: 0; + margin: 0; + height: 150px; + width: 185px; + box-shadow: 0 3px 5px rgba(0, 0, 0, 0.1); +} + +.edd_user_search_results li { + margin: 0; +} + +.edd_user_search_results li a { + display: block; + text-decoration: none; + padding: 6px 10px; +} + +.edd_user_search_results li a:hover { + background: #f5f5f5; +} + +.edd_user_search_results li.no-users { + text-align: center; + vertical-align: middle; + display: block; + line-height: 150px; + color: #bbb; + text-transform: uppercase; + font-size: 11px; +} + +@media screen and (max-width: 1100px) { + + .edd-mix-chart { + display: block; + width: 100%; + } +} + +@media screen and (max-width: 782px) { + + .license-lifetime-notice, + .license-expiration-date-notice, + .license-null { + padding-left: 0; + } +} + +@media screen and (max-width: 600px) { + #edd-edit-order-form input.large-text { + width: 100%; + } +} + +/* Customer Styles +-------------------------------------------------------------- */ + +#edd-item-wrapper { + background: #fff; + border: 1px solid $wp-border; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); + position: relative; + margin-top: 15px; + display: flex; +} + +#edd-item-wrapper.full-width { + max-width: 100%; +} + +#edd-item-wrapper:after { + content: ""; + display: block; + clear: both; + visibility: hidden; + font-size: 0; + height: 0; +} + +.edd-sections-wrap { + clear: both; + width: 100%; +} + +.edd-sections-wrap .section-wrap { + background-color: #fff; + display: inline-block; + z-index: 2; +} + +.js .edd-sections-wrap .edd-vertical-sections:not(.meta-box) .section-wrap>div { + min-height: 500px; + height: 100%; +} + +.edd-sections-wrap .section-wrap .customer-section:not(:last-child) { + border-bottom: 1px solid #eee; +} + +.edd-sections-wrap .section-wrap .customer-section table { + margin-bottom: 20px; +} + +.edd-sections-wrap .section-wrap { + border-left: 1px solid #e5e5e5; +} + +.edd-sections-wrap .section-wrap .section-content { + >* { + padding: 20px; + } + + h2 { + margin: 0; + padding-bottom: 0; + } +} + +.edd-sections-wrap .section-wrap .avatar-wrap { + float: left; + padding-right: 10px; + text-align: center; +} + +.edd-sections-wrap .section-wrap img.avatar { + border-radius: 5px; +} + +.edd-sections-wrap .section-wrap .customer-id { + position: absolute; + right: 0; + top: 0; + padding: 10px; + background-color: #fafafa; + border: 1px solid #eee; + border-bottom-left-radius: 20%; + border-top: none; + border-right: none; + font-family: monospace; + font-size: 18px; + font-weight: 600; +} + +.edd-item-info.customer-info input[type="text"], +.edd-item-info.customer-info input[type="password"], +.edd-item-info.customer-info select { + width: 200px; + height: auto; + box-shadow: none; + transition: none; + border: 1px solid #ddd; + margin: -5px 0 4px -2px; + font-size: 13px; + padding: 2px 4px; +} + +.edd-sections-wrap .section-wrap .customer-main-wrapper { + float: left; +} + +.edd-sections-wrap .section-wrap .customer-main-wrapper input[name="customerinfo[name]"] { + font-size: 24px; +} + +.edd-sections-wrap .section-wrap .customer-address-wrapper { + float: right; + margin-top: -3px; + margin-right: 50px; + width: 202px; +} + +.edd-sections-wrap .section-wrap .info-wrapper { + min-height: 125px; + overflow: visible; +} + +.edd-sections-wrap .section-wrap .customer-address span[data-key="address"], +.edd-sections-wrap .section-wrap .customer-address span[data-key="address2"], +.edd-sections-wrap .section-wrap .customer-address span[data-key="country"] { + display: block; +} + +.edd-sections-wrap .section-wrap a.delete { + color: #ff0000; + margin-right: 5px; + text-decoration: none; +} + +.customer-info { + min-height: 185px; +} + +.customer-info .customer-name { + font-size: 24px; + font-weight: 600; +} + +.customer-info .customer-name.editable { + margin-bottom: 6px; +} + +.customer-edit-link a { + font-weight: normal; + text-decoration: none; +} + +.disconnect-user a { + color: #aaa; + font-size: 20px; +} + +#customer-edit-actions { + padding: 3px; + line-height: 28px; + text-align: center; +} + +#customer-edit-actions .button-secondary { + margin-right: 5px; +} + +#customer-edit-actions .cancel { + padding: 5px; +} + +.edd-sections-wrap .section-wrap .row-title { + width: 30%; +} + +.edd-sections-wrap .section-wrap .editable { + display: block; + padding: 3px; +} + +.edd-sections-wrap .section-wrap div.edit-item { + margin-left: -4px; + margin-top: -20px; +} + +.edd-sections-wrap .section-wrap .customer-address.edit-item { + margin-top: 3px; +} + +.edd-sections-wrap .section-wrap span.edit-item { + display: none; +} + +.edd-sections-wrap .section-wrap .edit-item input { + font-size: 13px; +} + +.edd-sections-wrap .section-wrap .customer-name.edit-item input { + margin-top: -5px; +} + +.edd-sections-wrap .section-wrap .edd_user_search_results { + left: -2px; + top: 18px; +} + +.edd-sections-wrap .section-wrap .edd_user_search_results ul { + width: 198px; +} + +#edd-item-stats-wrapper { + margin: 0 auto; + text-align: center; +} + +#edd-item-stats-wrapper ul { + display: flex; + margin: 0; +} + +#edd-item-stats-wrapper li { + font-size: 14px; + margin-bottom: 0; + width: 50%; +} + +#edd-item-stats-wrapper a { + text-decoration: none; +} + +#edd-item-stats-wrapper .dashicons { + color: #888; + margin-top: -2px; +} + +#edd-item-tables-wrapper table { + width: 100%; +} + +#edd-item-tables-wrapper .no-items { + text-align: left; +} + +#edd-item-tables-wrapper .emails .add-customer-email-row { + background-color: #f4f4f4; + border-top: 1px solid #e5e5e5; +} + +#edd-item-tables-wrapper .add-customer-email-wrapper { + display: flex; + flex-wrap: wrap; + align-items: center; + margin: 12px 0; +} + +#edd-item-tables-wrapper .edd-form-group { + margin-bottom: 0; +} + +#edd-item-tables-wrapper .edd-make-email-primary { + flex-grow: 1; + margin-left: 12px; +} + +#edd-item-tables-wrapper .emails .spinner { + float: none; + margin: 0 10px; + align-self: center; +} + +#edd-item-tables-wrapper .notice-error { + background-color: #fff5f5; +} + +#edd-item-notes-wrapper { + min-height: 50px; +} + +.customer-note-input { + margin-bottom: 5px; + width: 100%; +} + +.customer-note-wrapper { + border-bottom: 1px solid #f9f9f9; + min-height: 38px; + padding: 7px 0 7px 7px; +} + +.customer-note-wrapper span { + display: block; +} + +.note-content-wrap { + padding-top: 7px; +} + +.edd-sections-wrap .section-wrap .notice-container { + padding-left: 20px; + padding-right: 20px; + margin-left: -20px; + margin-right: -20px; +} + +@media screen and (max-width: 810px) and (min-width: 656px) { + .customer-info .customer-name { + font-size: 16px; + } + + .edd-sections-wrap .section-wrap .widefat td, + .widefat th { + max-width: 100% !important; + display: table-cell; + } +} + +@media screen and (max-width: 781px) { + + #edd-item-tab-wrapper, + /** [1] */ + .edd-sections-wrap .section-wrap { + margin: 0; + width: 100%; + } + + #edd-item-tab-wrapper-list .dashicons { + /** [1] */ + font-size: 18px; + } + + .edd-item-has-tabs .edd-sections-wrap .section-wrap { + border-top: 1px solid #e5e5e5; + border-left: 0; + margin-top: -1px; + } +} + +@media screen and (max-width: 656px) { + .edd-item-info.customer-info { + position: relative; + } + + .edd-sections-wrap .section-wrap .customer-address-wrapper { + float: none; + position: absolute; + top: 84px; + left: 165px; + max-width: 200px; + } + + .edd-sections-wrap .section-wrap .customer-main-wrapper { + float: none; + position: absolute; + left: 165px; + } + + .customer-info .customer-name { + font-size: 16px; + } + + .edd-sections-wrap .section-wrap #edd-item-stats-wrapper { + padding-left: 0; + padding-right: 0; + } + + .edd-sections-wrap .section-wrap .customer-section { + margin-bottom: 0; + } + + .edd-sections-wrap .section-wrap .widefat td.no-items, + .edd-sections-wrap .section-wrap .widefat td.column-primary, + .edd-sections-wrap .section-wrap .widefat th.column-primary { + width: 100px !important; + display: table-cell; + overflow: hidden; + text-align: left; + } + + .edd-sections-wrap .section-wrap .customer-id { + display: none; + } + + #edd-item-tables-wrapper .emails td.column-primary { + padding-right: 10px; + width: 100% !important; + } + + #edd-item-tables-wrapper .edd-form-group { + margin: 0 0 16px 0; + } +} + +@media screen and (max-width: 480px) { + #edd-item-tab-wrapper-list li { + width: 50%; + } + + #edd-item-tab-wrapper-list li:nth-child(3n+3) { + border-width: 0 1px 1px 0; + } + + #edd-item-tab-wrapper-list li:nth-child(even) { + border-width: 0 0 1px 0; + } + + .download_page_edd-reports .button { + text-align: center; + } + + #edd-payment-date-filters span { + display: block; + } + + #edd-payment-date-filters span>input { + float: right; + } + + #edd-add-discount select[multiple] option, + #edd-edit-discount select[multiple] option { + height: 20px; + } + + .download_page_edd-tools .inside input[type="text"], + .download_page_edd-tools .inside select, + .download_page_edd-tools .inside input[type="submit"], + .download_page_edd-settings .inside input[type="button"], + .download_page_edd-reports .inside input[type="text"], + .download_page_edd-reports .inside select, + .download_page_edd-reports .inside input[type="submit"], + .download_page_edd-reports .inside .button { + width: 100%; + } + + #edd-add-discount select[multiple], + #edd-edit-discount select[multiple], + .download_page_edd-tools select[multiple] { + height: 200px !important; + } + + .download_page_edd-settings input[type="checkbox"] { + margin: 2px 0; + } + + .post-type-download input[type="checkbox"] { + margin-left: 2px; + } + +} + +/* System Info page styles +-------------------------------------------------------------- */ + +.inside .edd-tools-textarea { + background: #32373c; + color: rgba(240, 245, 250, 0.7); + font-size: 12px; + font-family: Menlo, Monaco, monospace; + display: block; + overflow: auto; + white-space: pre; + width: 100%; + height: 450px; + padding: 10px; + outline: none; +} + +#system-info-textarea::selection { + background: #555; + color: #fff; +} + +#edd-system-info .edd-inline-button { + margin-left: 5px; +} + +/* Tools Styles +-------------------------------------------------------------- */ + +.recount-stats-controls form { + display: inline; +} + +.edd-recount-stats-descriptions span { + display: none; + line-height: 24px; +} + +#edd-debug-log .edd-inline-button { + margin-left: 5px; +} + +/* Tools Styles +-------------------------------------------------------------- */ + +.edd-vertical-sections { + overflow: visible; + display: block; + display: flex; +} + +#edd-item-tab-wrapper, +/** [1] */ +.edd-vertical-sections .section-nav { + position: relative; + width: 20%; + line-height: 1em; + margin: 0 -1px 0 0; + padding: 0; + background-color: #f5f5f5; + border-right: 1px solid #e5e5e5; + box-sizing: border-box; + max-width: 200px; +} + +#edd-item-tab-wrapper-list { + /** [1] */ + margin: 0; +} + +#edd-item-tab-wrapper li, +/** [1] */ +.edd-vertical-sections .section-nav li { + display: block; + position: relative; + margin: 0; + padding: 0; + background-color: #fcfcfc; +} + +.edd-vertical-sections .section-title:last-of-type { + margin-bottom: 24px; +} + +#edd-item-tab-wrapper li a, +/** [1] */ +#edd-item-tab-wrapper li>.edd-item-tab-label-wrap, +/** [1] */ +.edd-vertical-sections .section-nav li a { + display: flex; + margin: 0; + padding: 9px; + text-decoration: none; + border-bottom: 1px solid #e5e5e5; + box-shadow: none; + position: relative; + align-items: center; +} + +#edd-item-tab-wrapper li a:focus, +#edd-item-tab-wrapper li a:hover, +.edd-vertical-sections .section-nav li a:hover, +.edd-vertical-sections .section-nav li a:focus { + box-shadow: inset 5px 0; + outline: 0; + transition: all .25s; +} + +.edd-vertical-sections .section-nav .section-title--is-active a:after { + content: ''; + width: 1px; + height: 100%; + background: #fff; + position: absolute; + right: 0; + top: 0; + bottom: 0; + z-index: 3; +} + +#edd-item-tab-wrapper li>.edd-item-tab-label-wrap { + /** [1] */ + background-color: #fff; +} + +.edd-vertical-sections .section-nav li a>.dashicons, +.edd-vertical-sections .section-nav li a>span { + display: inline-block; +} + +.edd-vertical-sections .section-nav li a>span { + max-width: 76%; +} + +.edd-vertical-sections .section-nav li a .dashicons { + line-height: 20px; + margin-right: 3px; + color: #888; +} + +.edd-vertical-sections .section-nav .section-title--is-active a { + font-weight: bold; + color: #555; + background-color: #fff; + border-right: none; + margin-right: -1px; +} + +.edd-vertical-sections.use-js .section-content, +.no-js .edd-vertical-sections.use-js .section-nav, +.no-js .edd-vertical-sections.use-js.edd-item-header-small { + display: none; +} + +.no-js .edd-vertical-sections.use-js .section-content { + display: block; +} + +/* Fresh */ +.admin-color-fresh .edd-vertical-sections .section-nav li a:hover, +.admin-color-fresh .edd-vertical-sections .section-nav li a:focus, +.admin-color-fresh .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #0073aa; +} + +/* Blue */ +.admin-color-blue .edd-vertical-sections .section-nav li a:hover, +.admin-color-blue .edd-vertical-sections .section-nav li a:focus, +.admin-color-blue .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #096484; +} + +/* Coffee */ +.admin-color-coffee .edd-vertical-sections .section-nav li a:hover, +.admin-color-coffee .edd-vertical-sections .section-nav li a:focus, +.admin-color-coffee .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #c7a589; +} + +/* Ectoplasm */ +.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:hover, +.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:focus, +.admin-color-ectoplasm .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #a3b745; +} + +/* Midnight */ +.admin-color-midnight .edd-vertical-sections .section-nav li a:hover, +.admin-color-midnight .edd-vertical-sections .section-nav li a:focus, +.admin-color-midnight .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #e14d43; +} + +/* Ocean */ +.admin-color-ocean .edd-vertical-sections .section-nav li a:hover, +.admin-color-ocean .edd-vertical-sections .section-nav li a:focus, +.admin-color-ocean .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #627c83; +} + +/* Sunrise */ +.admin-color-sunrise .edd-vertical-sections .section-nav li a:hover, +.admin-color-sunrise .edd-vertical-sections .section-nav li a:focus, +.admin-color-sunrise .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #be3631; +} + +/* Light */ +.admin-color-light .edd-vertical-sections .section-nav li a:hover, +.admin-color-light .edd-vertical-sections .section-nav li a:focus, +.admin-color-light .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #888; +} + +/* bbPress Color Schemes */ + +/* Evergreen */ +.admin-color-evergreen .edd-vertical-sections .section-nav li a:hover, +.admin-color-evergreen .edd-vertical-sections .section-nav li a:focus, +.admin-color-evergreen .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #36533f; +} + +/* Mint */ +.admin-color-mint .edd-vertical-sections .section-nav li a:hover, +.admin-color-mint .edd-vertical-sections .section-nav li a:focus, +.admin-color-mint .edd-vertical-sections .section-nav .section-title--is-active a { + box-shadow: inset 5px 0 #4f6d59; +} + +.edd-vertical-sections .section-nav .section-title--is-active .dashicons { + color: #555; +} + +@media only screen and (max-width: 782px) { + + #edd-item-tab-wrapper, + /** [1] */ + .edd-vertical-sections .section-nav { + width: 48px; + } + + .edd-vertical-sections .section-nav li a { + justify-content: center; + } + + .edd-vertical-sections .section-nav li a .dashicons { + width: 24px; + height: 24px; + font-size: 24px; + line-height: 24px; + margin: 0; + } + + .section-nav li .dashicons::before { + width: 24px; + height: 24px; + } + + #edd-item-tab-wrapper .edd-item-tab-label, + /** [1] */ + .section-nav li .label { + overflow: hidden; + position: absolute; + top: -1000em; + left: -1000em; + width: 1px; + height: 1px; + } +} + +/* Content wrapper */ + +#edd-item-card-wrapper, +/** [1] */ +.edd-vertical-sections .section-wrap { + width: 80%; +} + +#edd-item-card-wrapper .item-section { + /** [1] */ + background: #fff; + overflow: hidden; + box-sizing: border-box; +} + +*:not(#edd-item-tab-wrapper)+#edd-item-card-wrapper .item-section { + /** [1] */ + margin: 25px 0; + padding: 20px; + border: 1px solid #e5e5e5; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); +} + +#edd-item-tab-wrapper+#edd-item-card-wrapper { + /** [1] */ + padding: 20px; + border-left: 1px solid #e5e5e5; + box-sizing: border-box; +} + +@media only screen and (min-width: 1200px) { + + #edd-item-card-wrapper, + /** [1] */ + #edd-graphs-filter, + .edd-vertical-sections:not(.meta-box) .section-wrap { + width: calc(100% - 200px); + } +} + +@media only screen and (max-width: 782px) { + + #edd-item-card-wrapper, + /** [1] */ + #edd-graphs-filter, + .edd-vertical-sections .section-wrap { + width: calc(100% - 48px); + } +} + +#edd-debug-log .edd-inline-button { + margin-left: 5px; +} + +/* Promotional element styles +-------------------------------------------------------------- */ + +/* Settings sidebar */ +.edd-settings-sidebar { + padding-top: 27px; +} + +.edd-settings-sidebar-content { + background-color: #fff; + text-align: center; + border: 1px solid #ddd; + box-sizing: border-box; + max-width: 300px; +} + +.edd-settings-sidebar-content p { + font-size: 14px; + line-height: 1.5; + margin-top: 0; +} + +/* Settings sidebar header section */ +.edd-sidebar-header-section { + background-color: #35495c; + line-height: 1; + padding: 26px 20px 24px; + border-bottom: 3px dashed #fafafa; +} + +/* Settings sidebar description section */ +.edd-sidebar-description-section { + background-color: #fafafa; + padding: 16px 20px; + border-bottom: 1px solid #ddd; +} + +.edd-sidebar-description-section .edd-sidebar-description { + margin: 0; +} + +/* Settings sidebar coupon section */ +.edd-sidebar-coupon-section { + font-size: 14px; + padding: 16px 20px; +} + +.edd-sidebar-coupon-section label { + display: block; + line-height: 1.4; + margin-bottom: 6px; +} + +.edd-sidebar-coupon-section label strong { + color: #253b51; + font-weight: 700; +} + +.edd-sidebar-coupon-section input { + background: #f4f7fa; + font-size: 22px; + font-weight: 600; + text-align: center; + padding: 10px; + border: 2px dashed #2794da; + border-radius: 4px; + margin-bottom: 16px; + box-shadow: none; + width: 100%; +} + +.edd-sidebar-coupon-section input:focus { + border: 2px dashed #2794da; + box-shadow: none; +} + +.edd-settings-sidebar-content .edd-coupon-note { + color: #6c7883; + font-size: 13px; + font-style: italic; + margin: 0; +} + +.edd-settings-sidebar-content .edd-coupon-note a { + color: #253b51; +} + +.edd-settings-sidebar-content .edd-coupon-note a:hover { + text-decoration: none; +} + +/* Settings sidebar footer section */ +.edd-sidebar-footer-section { + background-color: #fafafa; + padding: 16px 20px; + border-top: 1px solid #ddd; +} + +.edd-sidebar-footer-section .edd-cta-button { + display: block; + background-color: #2794da; + color: #fff; + text-decoration: none; + font-size: 20px; + font-weight: 700; + text-transform: uppercase; + padding: 17px 10px; + border: none; + border-radius: 4px; + width: 100%; + box-sizing: border-box; + box-shadow: none; + transition: background-color .2s; +} + +.edd-sidebar-footer-section .edd-cta-button:hover { + background-color: #2386c5; +} + +/* Settings sidebar responsive behavior */ +@media all and (min-width: 1080px) { + + .edd-has-sidebar .edd-settings-content { + float: left; + width: 67%; + } + + .edd-has-sidebar .edd-settings-sidebar { + float: right; + width: 31%; + } +} + +@media all and (min-width: 1240px) { + + .edd-has-sidebar .edd-settings-content { + width: 74%; + } + + .edd-has-sidebar .edd-settings-sidebar { + width: 23%; + } +} + +/* Settings - Move sidebar below content only on Taxes tab */ +.taxes-tab .edd-has-sidebar .edd-settings-content, +.taxes-tab .edd-has-sidebar .edd-settings-sidebar { + float: none; + width: 100%; +} + +/* Extensions (add-ons) page promotional element */ +.bfcm-promo-img-container { + background-color: #35495c; + width: 100%; + height: 160px; +} + +.bfcm-code { + color: #2794da; + font-weight: 700; +} + +.sale-ends { + position: absolute; + bottom: 9px; + right: 14px; + display: inline-block; + color: #6c7883; + font-size: 12px; + text-align: right; + font-style: italic; + width: 150px; +} diff --git a/assets/css/admin/tax-rates/_add.scss b/assets/css/admin/tax-rates/_add.scss new file mode 100644 index 00000000000..c3d334c2a97 --- /dev/null +++ b/assets/css/admin/tax-rates/_add.scss @@ -0,0 +1,34 @@ + +.edd-tax-rate-table-add { + th select, + th input[type="text"], + th input[type="number"] { + width: 100%; + margin: 0; + padding: 4px; + } + + #tax_rate_region_global { + margin-right: 4px; + margin-bottom: 8px; + } + + + @media screen and (max-width: $break-medium) { + display: block; + + th { + display: block; + } + + .screen-reader-text { + display: block; + width: unset; + clip: unset; + height: unset; + clip-path: unset; + margin: 0 0 12px; + position: relative; + } + } +} diff --git a/assets/css/admin/tax-rates/_base.scss b/assets/css/admin/tax-rates/_base.scss new file mode 100644 index 00000000000..1f8ff17bb20 --- /dev/null +++ b/assets/css/admin/tax-rates/_base.scss @@ -0,0 +1,110 @@ + +#edd-admin-tax-rates { + margin: 1em 0 0; + + table { + border-collapse: collapse; + } + + .tablenav.top { + display: flex; + justify-content: space-between; + } + + .edd-admin-tax-rates__tablenav--left { + display: inline-flex; + } + + th:not(.check-column) { + padding: 15px 10px; + width: unset; + } + + .chosen-container { + width: 100% !important; + } + + tbody tr:not(:last-of-type) { + border-bottom: 1px solid $gray-200; + } + + tfoot.add-new th { + font-weight: normal; + padding: 12px 8px 10px 8px; + } + + /** + * [1] Due to the inability to reset the child views the "empty" view + * can only be appended to the parent. This means duplicates may be added. + * + * This can be removed once changes are immediately reflected with Backbone.sync() + */ + .edd-tax-rate-row--is-empty + .edd-tax-rate-row--is-empty, /* [1] */ + .edd-tax-rate-row--inactive { + display: none; + } + + .has-inactive .edd-tax-rate-row--inactive { + display: table-row; + } + + .edd-tax-rate-row--is-empty td { + background-color: $wp-alternate; + } + + .edd-tax-rate-row--inactive td { + color: $wp-inactive; + background-color: $wp-alternate; + } + + .edd-tax-rate-table-add { + background-color: $wp-alternate; + } + + @media screen and (max-width: 782px) { + thead th:not(.edd-tax-rates-table-rate), + tfoot:not(.add-new) th:not(.edd-tax-rates-table-rate) { + display: none; + } + + thead tr, + tfoot:not(.add-new) tr, + .edd-tax-rate-row { + display: grid; + grid-template-columns: 2.5em 1fr; + grid-template-rows: 1fr; + grid-gap: 0 16px; + } + + th.edd-tax-rates-table-rate { + padding-left: 12px; + } + + .edd-tax-rates-table-checkbox { + grid-row: 1 / 5; + } + + tbody td { + padding-left: 35% !important; + } + + td:before { + content: attr(data-colname); + display: block; + width: 32%; + position: absolute; + } + + .tablenav.top { + flex-wrap: wrap; + } + + .edd-admin-tax-rates__tablenav--left { + margin-bottom: 16px; + } + + .edd-admin-tax-rates__tablenav--left select { + margin-right: 6px; + } + } +} diff --git a/assets/css/admin/tax-rates/style.scss b/assets/css/admin/tax-rates/style.scss new file mode 100644 index 00000000000..d0191f97ec0 --- /dev/null +++ b/assets/css/admin/tax-rates/style.scss @@ -0,0 +1,9 @@ +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/variables"; +@import "~@wordpress/base-styles/mixins"; +@import "~@wordpress/base-styles/breakpoints"; +@import "~@wordpress/base-styles/animations"; +@import "../../variables/colors"; + +@import "base"; +@import "add"; diff --git a/assets/css/admin/upgrades/_v3.scss b/assets/css/admin/upgrades/_v3.scss new file mode 100644 index 00000000000..93538691ea2 --- /dev/null +++ b/assets/css/admin/upgrades/_v3.scss @@ -0,0 +1,22 @@ +#edd-migration-progress { + .dashicons-minus { + color: $gray-600; + } + .dashicons-yes { + color: green; + } + .dashicons-update:before { + animation: rotation 2s infinite linear; + display: block; + } +} + +#edd-v3-migration-remove-legacy-data-submit-wrap { + display: flex; + align-items: center; + gap: 6px; + + .button { + margin: 0; + } +} diff --git a/assets/css/chosen-rtl.min.css b/assets/css/chosen-rtl.min.css new file mode 100644 index 00000000000..506a4636f61 --- /dev/null +++ b/assets/css/chosen-rtl.min.css @@ -0,0 +1,11 @@ +/*! +Chosen, a Select Box Enhancer for jQuery and Prototype +by Patrick Filler for Harvest, http://getharvest.com + +Version 1.8.5 +Full source at https://github.com/harvesthq/chosen +Copyright (c) 2011-2018 Harvest http://getharvest.com + +MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md +This file is generated by `grunt build`, do not edit it by hand. +*/.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:13px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chosen-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1010;width:100%;border:1px solid #aaa;border-top:0;background:#fff;-webkit-box-shadow:0 4px 5px rgba(0,0,0,.15);box-shadow:0 4px 5px rgba(0,0,0,.15);display:none}.chosen-container.chosen-with-drop .chosen-drop{display:block}.chosen-container a{cursor:pointer}.chosen-container .chosen-single .group-name,.chosen-container .search-choice .group-name{margin-left:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-weight:400;color:#999}.chosen-container .chosen-single .group-name:after,.chosen-container .search-choice .group-name:after{content:":";padding-right:2px;vertical-align:top}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:0 8px 0 0;height:25px;border:1px solid #aaa;border-radius:5px;background-color:#fff;background:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#fff),color-stop(50%,#f6f6f6),color-stop(52%,#eee),to(#f4f4f4));background:linear-gradient(#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background-clip:padding-box;-webkit-box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-single input[type=text]{cursor:pointer;opacity:0;position:absolute;width:0}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span{display:block;overflow:hidden;margin-left:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-left:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:6px;left:26px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-single .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single.chosen-disabled .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single .chosen-single div{position:absolute;top:0;left:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url(chosen-sprite.png) no-repeat 0 2px}.chosen-container-single .chosen-search{position:relative;z-index:1010;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{margin:1px 0;padding:4px 5px 4px 20px;width:100%;height:auto;outline:0;border:1px solid #aaa;background:url(chosen-sprite.png) no-repeat 0 -20px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-single .chosen-drop{margin-top:-1px;border-radius:0 0 4px 4px;background-clip:padding-box}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;opacity:0;pointer-events:none}.chosen-container .chosen-results{color:#444;position:relative;overflow-x:hidden;overflow-y:auto;margin:0 0 4px 4px;padding:0 4px 0 0;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;word-wrap:break-word;-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#3875d7),color-stop(90%,#2a62bc));background-image:linear-gradient(#3875d7 20%,#2a62bc 90%);color:#fff}.chosen-container .chosen-results li.no-results{color:#777;display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-right:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;margin:0;padding:0 5px;width:100%;height:auto;border:1px solid #aaa;background-color:#fff;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(1%,#eee),color-stop(15%,#fff));background-image:linear-gradient(#eee 1%,#fff 15%);cursor:text}.chosen-container-multi .chosen-choices li{float:right;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:1px 0;padding:0;height:25px;outline:0;border:0!important;background:0 0!important;-webkit-box-shadow:none;box-shadow:none;color:#999;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0;width:25px}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 0 3px 5px;padding:3px 5px 3px 20px;border:1px solid #aaa;max-width:100%;border-radius:3px;background-color:#eee;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),to(#eee));background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-size:100% 19px;background-repeat:repeat-x;background-clip:padding-box;-webkit-box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);color:#333;line-height:13px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice span{word-wrap:break-word}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;left:3px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{background-position:-42px -10px}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-left:5px;border:1px solid #ccc;background-color:#e4e4e4;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),to(#eee));background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{margin:0;padding:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border:1px solid #5897fb;-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#eee),color-stop(80%,#fff));background-image:linear-gradient(#eee 20%,#fff 80%);-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset}.chosen-container-active.chosen-with-drop .chosen-single div{border-right:none;background:0 0}.chosen-container-active.chosen-with-drop .chosen-single div b{background-position:-18px 2px}.chosen-container-active .chosen-choices{border:1px solid #5897fb;-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#222!important}.chosen-disabled{opacity:.5!important;cursor:default}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close{cursor:default}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-resolution:144dpi),only screen and (min-resolution:1.5dppx){.chosen-container .chosen-results-scroll-down span,.chosen-container .chosen-results-scroll-up span,.chosen-container-multi .chosen-choices .search-choice .search-choice-close,.chosen-container-single .chosen-search input[type=text],.chosen-container-single .chosen-single abbr,.chosen-container-single .chosen-single div b{background-image:url(chosen-sprite@2x.png)!important;background-size:52px 37px!important;background-repeat:no-repeat!important}} \ No newline at end of file diff --git a/assets/css/chosen.min.css b/assets/css/chosen.min.css new file mode 100644 index 00000000000..fb044bf2d7f --- /dev/null +++ b/assets/css/chosen.min.css @@ -0,0 +1,8 @@ +/*! +Chosen, a Select Box Enhancer for jQuery and Prototype +Version 2.1.0 +Full source at https://github.com/jjj/chosen +Copyright (c) 2011-2020 JJJ +MIT License, https://github.com/jjj/chosen/blob/master/LICENSE.md +This file is generated by `grunt build`, do not edit it by hand. +*/.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:14px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chosen-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1011;width:100%;border:1px solid #aaa;border-top:0;border-radius:0 0 4px 4px;background:#fff;clip:rect(0,0,0,0);-webkit-clip-path:inset(100% 100%);clip-path:inset(100% 100%);margin-top:-1px;background-clip:padding-box}.chosen-container.chosen-with-drop .chosen-drop{clip:auto;-webkit-clip-path:none;clip-path:none}.chosen-container.chosen-dropup .chosen-choices,.chosen-container.chosen-dropup .chosen-single{z-index:1010}.chosen-container.chosen-dropup .chosen-drop{top:auto;bottom:100%;border:solid #aaa;border-width:1px 1px 0 1px;border-radius:4px 4px 0 0}.chosen-container a{cursor:pointer}.chosen-container .chosen-single .group-name,.chosen-container .search-choice .group-name{margin-right:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-weight:400;color:#999}.chosen-container .chosen-single .group-name:after,.chosen-container .search-choice .group-name:after{content:":";padding-left:2px;vertical-align:top}.chosen-container .search-choice-close{position:absolute;right:3px;top:0;bottom:0;margin:auto;border:none;cursor:pointer;display:block;width:15px;height:15px;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 0 top 50%;background-size:15px 15px}.chosen-container .search-choice-close:active,.chosen-container .search-choice-close:hover{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 0 top 50%;background-size:15px 15px}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:2.5px 0 2.5px 7px;border:1px solid #aaa;border-radius:4px;background-color:#fff;background-clip:padding-box;color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span,.chosen-container-single .chosen-single-with-deselect.chosen-default span{display:block;overflow:hidden;margin-right:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:42px}.chosen-container-single .chosen-single .search-choice-close{right:26px}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 5px top 52%;background-size:15px 15px}.chosen-container-single .chosen-search{position:relative;z-index:1011;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{margin:0;padding:5px 20px 5px 5px;width:100%;height:auto;outline:0;border:1px solid #ccc;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 5px top 50%;background-size:15px 15px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:4px}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;clip:rect(0,0,0,0);-webkit-clip-path:inset(100% 100%);clip-path:inset(100% 100%)}.chosen-container-single.chosen-container-single-nosearch.chosen-dropup .chosen-results{padding-top:4px}.chosen-container-single.chosen-container-single-nosearch.chosen-dropup .chosen-single{z-index:1010}.chosen-container-single .chosen-drop .result-selected{background-color:#eee}.chosen-container .chosen-results{color:#444;position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;word-wrap:break-word;-webkit-touch-callout:none;border-radius:4px}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;color:#fff}.chosen-container .chosen-results li.no-results{color:#777;display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;margin:0;padding:0 3px;width:100%;height:auto;border-radius:4px;border:1px solid #aaa;background-color:#fff;cursor:text}.chosen-container-multi .chosen-choices li{float:left;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:0 3px;padding:6.5px 0;outline:0;border:none;background:0 0;-webkit-box-shadow:none;box-shadow:none;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0;width:25px}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 5px 3px 0;padding:4px 20px 4px 5px;border:1px solid #aaa;border-radius:3px;max-width:100%;background-color:#eee;color:#333;line-height:12px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice span{font-size:95%;word-wrap:break-word}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;border:1px solid #ccc;background-color:#e4e4e4;color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-multi.chosen-dropup .chosen-results{padding-top:4px}.chosen-container-multi.chosen-dropup .chosen-single{z-index:1010}.chosen-container-active .chosen-single{outline:#00f}.chosen-container-active.chosen-with-drop .chosen-choices,.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;border-bottom-right-radius:0;border-bottom-left-radius:0}.chosen-container-active.chosen-with-drop .chosen-single div b{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 5px top 52%;background-size:15px 15px}.chosen-container-active.chosen-with-drop.chosen-dropup .chosen-choices,.chosen-container-active.chosen-with-drop.chosen-dropup .chosen-single{border-radius:0 0 4px 4px}.chosen-container-active .chosen-choices{border:1px solid #5897fb}.chosen-disabled{opacity:.5;cursor:default}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .search-choice-close{cursor:default}.chosen-rtl{text-align:right}.chosen-rtl .chosen-single{padding:2px 7px 2px 0}.chosen-rtl .chosen-single span{margin-right:0;margin-left:26px;direction:rtl}.chosen-rtl .chosen-single-with-deselect span{margin-left:42px}.chosen-rtl .chosen-single div{right:auto;left:0}.chosen-rtl .chosen-single .search-choice-close{right:auto;left:26px}.chosen-rtl .chosen-choices li{float:right}.chosen-rtl .chosen-choices li.search-field input[type=text]{direction:rtl}.chosen-rtl .chosen-choices li.search-choice{margin:3px 0 3px 5px;padding:3px 5px 3px 20px}.chosen-rtl .chosen-choices li.search-choice .search-choice-close{right:auto;left:3px}.chosen-rtl.chosen-container-single .chosen-results{margin:0 0 4px 4px;padding:0 4px 0 0}.chosen-rtl .chosen-results li.group-option{padding-right:15px;padding-left:0}.chosen-rtl .chosen-search input[type=text]{padding:5px 5px 5px 20px;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat left 5px top 55%;background-size:15px 15px;direction:rtl}.chosen-rtl.chosen-container-single .chosen-single div b{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat left 5px top 52%;background-size:15px 15px}.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat left 5px top 52%;background-size:15px 15px} \ No newline at end of file diff --git a/assets/css/edd-admin-chosen-rtl.min.css b/assets/css/edd-admin-chosen-rtl.min.css new file mode 100644 index 00000000000..502b3bffdb4 --- /dev/null +++ b/assets/css/edd-admin-chosen-rtl.min.css @@ -0,0 +1 @@ +.chosen-container{font-size:14px}.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-multi .search-field,.chosen-container-single .chosen-single{background:#fff url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E) no-repeat left 5px top 55%;background-size:16px 16px;border:1px solid #7e8993;box-shadow:0 0 0 transparent;color:#32373c}.chosen-container-multi .chosen-choices,.chosen-container-single .chosen-single{border-radius:4px;border-color:#7e8993}.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-multi.chosen-with-drop .chosen-choices{box-shadow:0 1px 1px rgba(0,0,0,.04)}.chosen-container-multi.chosen-container-active.chosen-dropup .chosen-choices,.chosen-container-single.chosen-container-active.chosen-with-drop.chosen-dropup .chosen-single{border-radius:0 0 4px 4px;border-width:0 1px;z-index:1011}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1010;width:100%;border-radius:0 0 4px 4px;border-width:0 1px;border-color:transparent;background:#fff;outline:2px solid transparent}.chosen-container-single .chosen-single div b{background-image:none}.chosen-container-single .chosen-search:after{display:block;position:absolute;left:6px;top:50%;font-family:dashicons;font-size:17px;content:"";transform:translateY(-50%)}.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single{border-radius:4px 4px 0 0}.chosen-container-single .chosen-single div{width:26px}.chosen-container-single .chosen-default{color:#32373c}.chosen-container-active .chosen-single{border-color:transparent;outline:2px solid transparent}.chosen-container-single .chosen-search input[type=text]{margin:1px 0;padding:4px 5px 4px 20px;width:100%!important;height:auto;outline:0;border:1px solid #7e8993;border-radius:4px;line-height:normal;box-shadow:inset 0 1px 2px rgba(0,0,0,.07)}.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-single .chosen-single{min-height:30px}@media screen and (max-width:782px){.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-single .chosen-single{font-size:16px;line-height:1.625;min-height:40px;padding:5px 8px}}.chosen-container-multi .search-field{border:none}.chosen-container-multi.chosen-with-drop .chosen-choices{border-bottom-color:transparent}.chosen-container-multi .chosen-choices li.search-field{min-width:100px!important;width:100%}.chosen-container-multi .chosen-choices li.search-field input[type=text]{color:#32373c;font-family:unset;height:unset;margin:0;padding:3px 10px;width:100%!important}.chosen-container-multi .spinner{position:absolute;top:4px;left:-4px}.chosen-container-multi .chosen-choices li.search-choice{margin:3px 0 3px 5px;padding:5px 5px 5px 22px;border:1px solid #7e8993;max-width:100%;border-radius:4px;background:#f4f4f4;box-shadow:none;color:#32373c;cursor:default}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;left:3px;display:block;width:15px;height:15px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:before{height:15px;width:15px;position:absolute;top:0;left:0;color:#32373c;font-family:dashicons;content:"";font-size:15px;line-height:1}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover:before{color:#537994}.chosen-container .chosen-results{color:#537994;position:relative;overflow-x:hidden;overflow-y:auto;margin:0 0 4px 4px;padding:0 4px 0 0;max-height:240px}.chosen-container .chosen-results li.highlighted{background-image:none;border-radius:4px;color:#fff}:root{--wp-admin-theme-color:#007cba;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-20:#005a87}:root body.admin-color-light{--wp-admin-theme-color:#0085ba;--wp-admin-theme-color-darker-10:#0073a1;--wp-admin-theme-color-darker-20:#006187}:root body.admin-color-modern{--wp-admin-theme-color:#3858e9;--wp-admin-theme-color-darker-10:#2145e6;--wp-admin-theme-color-darker-20:#183ad6}:root body.admin-color-blue{--wp-admin-theme-color:#096484;--wp-admin-theme-color-darker-10:#07526c;--wp-admin-theme-color-darker-20:#064054}:root body.admin-color-coffee{--wp-admin-theme-color:#46403c;--wp-admin-theme-color-darker-10:#383330;--wp-admin-theme-color-darker-20:#2b2724}:root body.admin-color-ectoplasm{--wp-admin-theme-color:#523f6d;--wp-admin-theme-color-darker-10:#46365d;--wp-admin-theme-color-darker-20:#3a2c4d}:root body.admin-color-midnight{--wp-admin-theme-color:#e14d43;--wp-admin-theme-color-darker-10:#dd382d;--wp-admin-theme-color-darker-20:#d02c21}:root body.admin-color-ocean{--wp-admin-theme-color:#627c83;--wp-admin-theme-color-darker-10:#576e74;--wp-admin-theme-color-darker-20:#4c6066}:root body.admin-color-sunrise{--wp-admin-theme-color:#dd823b;--wp-admin-theme-color-darker-10:#d97426;--wp-admin-theme-color-darker-20:#c36922}:root body.admin-color-evergreen{--wp-admin-theme-color:#36533f;--wp-admin-theme-color-darker-10:#2c4433;--wp-admin-theme-color-darker-20:#223428}:root body.admin-color-mint{--wp-admin-theme-color:#4f6d59;--wp-admin-theme-color-darker-10:#445e4d;--wp-admin-theme-color-darker-20:#3a4f41}.chosen-container.chosen-container-active .chosen-choices,.chosen-container.chosen-container-active .chosen-single,.chosen-with-drop.chosen-dropup .chosen-drop{border-color:#007cba;border-color:var(--wp-admin-theme-color);box-shadow:0 0 0 1px #007cba;box-shadow:0 0 0 1px var(--wp-admin-theme-color)}.chosen-container-active.chosen-dropup .chosen-choices,.chosen-container .chosen-drop,.chosen-with-drop.chosen-dropup .chosen-single{border-color:#007cba;border-color:var(--wp-admin-theme-color);box-shadow:0 1px 0 1px #007cba;box-shadow:0 1px 0 1px var(--wp-admin-theme-color)}.chosen-container .chosen-results li.highlighted{background-color:#007cba;background-color:var(--wp-admin-theme-color)}.edd-select-chosen{width:100%;max-width:300px}.edd-select-chosen.edd-customer-select{width:100%!important}.edd-select-chosen.edd-time{width:55px;max-width:55px}@media screen and (max-width:782px){.edd-select-chosen.edd-time{width:70px;max-width:70px}} \ No newline at end of file diff --git a/assets/css/edd-admin-chosen.min.css b/assets/css/edd-admin-chosen.min.css new file mode 100644 index 00000000000..ecfa687b3b4 --- /dev/null +++ b/assets/css/edd-admin-chosen.min.css @@ -0,0 +1 @@ +.chosen-container{font-size:14px}.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-multi .search-field,.chosen-container-single .chosen-single{background:#fff url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E) no-repeat right 5px top 55%;background-size:16px 16px;border:1px solid #7e8993;box-shadow:0 0 0 transparent;color:#32373c}.chosen-container-multi .chosen-choices,.chosen-container-single .chosen-single{border-radius:4px;border-color:#7e8993}.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-multi.chosen-with-drop .chosen-choices{box-shadow:0 1px 1px rgba(0,0,0,.04)}.chosen-container-multi.chosen-container-active.chosen-dropup .chosen-choices,.chosen-container-single.chosen-container-active.chosen-with-drop.chosen-dropup .chosen-single{border-radius:0 0 4px 4px;border-width:0 1px;z-index:1011}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1010;width:100%;border-radius:0 0 4px 4px;border-width:0 1px;border-color:transparent;background:#fff;outline:2px solid transparent}.chosen-container-single .chosen-single div b{background-image:none}.chosen-container-single .chosen-search:after{display:block;position:absolute;right:6px;top:50%;font-family:dashicons;font-size:17px;content:"";transform:translateY(-50%)}.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single{border-radius:4px 4px 0 0}.chosen-container-single .chosen-single div{width:26px}.chosen-container-single .chosen-default{color:#32373c}.chosen-container-active .chosen-single{border-color:transparent;outline:2px solid transparent}.chosen-container-single .chosen-search input[type=text]{margin:1px 0;padding:4px 20px 4px 5px;width:100%!important;height:auto;outline:0;border:1px solid #7e8993;border-radius:4px;line-height:normal;box-shadow:inset 0 1px 2px rgba(0,0,0,.07)}.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-single .chosen-single{min-height:30px}@media screen and (max-width:782px){.chosen-container-single.chosen-container-active.chosen-with-drop .chosen-single,.chosen-container-single .chosen-single{font-size:16px;line-height:1.625;min-height:40px;padding:5px 8px}}.chosen-container-multi .search-field{border:none}.chosen-container-multi.chosen-with-drop .chosen-choices{border-bottom-color:transparent}.chosen-container-multi .chosen-choices li.search-field{min-width:100px!important;width:100%}.chosen-container-multi .chosen-choices li.search-field input[type=text]{color:#32373c;font-family:unset;height:unset;margin:0;padding:3px 10px;width:100%!important}.chosen-container-multi .spinner{position:absolute;top:4px;right:-4px}.chosen-container-multi .chosen-choices li.search-choice{margin:3px 5px 3px 0;padding:5px 22px 5px 5px;border:1px solid #7e8993;max-width:100%;border-radius:4px;background:#f4f4f4;box-shadow:none;color:#32373c;cursor:default}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;right:3px;display:block;width:15px;height:15px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:before{height:15px;width:15px;position:absolute;top:0;right:0;color:#32373c;font-family:dashicons;content:"";font-size:15px;line-height:1}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover:before{color:#537994}.chosen-container .chosen-results{color:#537994;position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px}.chosen-container .chosen-results li.highlighted{background-image:none;border-radius:4px;color:#fff}:root{--wp-admin-theme-color:#007cba;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-20:#005a87}:root body.admin-color-light{--wp-admin-theme-color:#0085ba;--wp-admin-theme-color-darker-10:#0073a1;--wp-admin-theme-color-darker-20:#006187}:root body.admin-color-modern{--wp-admin-theme-color:#3858e9;--wp-admin-theme-color-darker-10:#2145e6;--wp-admin-theme-color-darker-20:#183ad6}:root body.admin-color-blue{--wp-admin-theme-color:#096484;--wp-admin-theme-color-darker-10:#07526c;--wp-admin-theme-color-darker-20:#064054}:root body.admin-color-coffee{--wp-admin-theme-color:#46403c;--wp-admin-theme-color-darker-10:#383330;--wp-admin-theme-color-darker-20:#2b2724}:root body.admin-color-ectoplasm{--wp-admin-theme-color:#523f6d;--wp-admin-theme-color-darker-10:#46365d;--wp-admin-theme-color-darker-20:#3a2c4d}:root body.admin-color-midnight{--wp-admin-theme-color:#e14d43;--wp-admin-theme-color-darker-10:#dd382d;--wp-admin-theme-color-darker-20:#d02c21}:root body.admin-color-ocean{--wp-admin-theme-color:#627c83;--wp-admin-theme-color-darker-10:#576e74;--wp-admin-theme-color-darker-20:#4c6066}:root body.admin-color-sunrise{--wp-admin-theme-color:#dd823b;--wp-admin-theme-color-darker-10:#d97426;--wp-admin-theme-color-darker-20:#c36922}:root body.admin-color-evergreen{--wp-admin-theme-color:#36533f;--wp-admin-theme-color-darker-10:#2c4433;--wp-admin-theme-color-darker-20:#223428}:root body.admin-color-mint{--wp-admin-theme-color:#4f6d59;--wp-admin-theme-color-darker-10:#445e4d;--wp-admin-theme-color-darker-20:#3a4f41}.chosen-container.chosen-container-active .chosen-choices,.chosen-container.chosen-container-active .chosen-single,.chosen-with-drop.chosen-dropup .chosen-drop{border-color:#007cba;border-color:var(--wp-admin-theme-color);box-shadow:0 0 0 1px #007cba;box-shadow:0 0 0 1px var(--wp-admin-theme-color)}.chosen-container-active.chosen-dropup .chosen-choices,.chosen-container .chosen-drop,.chosen-with-drop.chosen-dropup .chosen-single{border-color:#007cba;border-color:var(--wp-admin-theme-color);box-shadow:0 1px 0 1px #007cba;box-shadow:0 1px 0 1px var(--wp-admin-theme-color)}.chosen-container .chosen-results li.highlighted{background-color:#007cba;background-color:var(--wp-admin-theme-color)}.edd-select-chosen{width:100%;max-width:300px}.edd-select-chosen.edd-customer-select{width:100%!important}.edd-select-chosen.edd-time{width:55px;max-width:55px}@media screen and (max-width:782px){.edd-select-chosen.edd-time{width:70px;max-width:70px}} \ No newline at end of file diff --git a/assets/css/edd-admin-datepicker-rtl.min.css b/assets/css/edd-admin-datepicker-rtl.min.css new file mode 100644 index 00000000000..7151c1aa42b --- /dev/null +++ b/assets/css/edd-admin-datepicker-rtl.min.css @@ -0,0 +1 @@ +.edd-datepicker{padding:0;margin:0;border-radius:0;background-color:#fff;border:1px solid #dfdfdf;border-top:none;box-shadow:0 3px 6px rgba(0,0,0,.075);min-width:17em;width:auto;z-index:1000!important}.edd-datepicker *{padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;border-radius:0}.edd-datepicker table{font-size:13px;margin:0;border:none;border-collapse:collapse}.edd-datepicker .ui-datepicker-header,.edd-datepicker .ui-widget-header{background-image:none;border:none;color:#fff;font-weight:400;padding:.2em 0}.edd-datepicker .ui-datepicker-header .ui-state-hover{background:transparent;border-color:transparent;cursor:pointer}.edd-datepicker .ui-datepicker-title{margin:0;padding:10px 0;color:#fff;font-size:14px;line-height:14px;text-align:center}.edd-datepicker .ui-datepicker-next,.edd-datepicker .ui-datepicker-prev{position:relative;top:0;height:34px;width:34px}.edd-datepicker .ui-state-hover.ui-datepicker-next,.edd-datepicker .ui-state-hover.ui-datepicker-prev{border:none}.edd-datepicker .ui-datepicker-prev,.edd-datepicker .ui-datepicker-prev-hover{right:0}.edd-datepicker .ui-datepicker-next,.edd-datepicker .ui-datepicker-next-hover{left:0}.edd-datepicker .ui-datepicker-next span,.edd-datepicker .ui-datepicker-prev span{display:none}.edd-datepicker .ui-datepicker-prev{float:right}.edd-datepicker .ui-datepicker-next{float:left}.edd-datepicker .ui-datepicker-next:before,.edd-datepicker .ui-datepicker-prev:before{font:normal 20px/34px dashicons;padding-right:7px;color:#fff;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;width:34px;height:34px}.edd-datepicker .ui-datepicker-prev:before{content:""}.edd-datepicker .ui-datepicker-next:before{content:""}.edd-datepicker .ui-datepicker-next-hover:before,.edd-datepicker .ui-datepicker-prev-hover:before{opacity:.7}.edd-datepicker select.ui-datepicker-month,.edd-datepicker select.ui-datepicker-year{width:33%}.edd-datepicker thead{color:#fff;font-weight:600}.edd-datepicker th{padding:10px}.edd-datepicker td{padding:0;border:1px solid #f4f4f4}.edd-datepicker td.ui-datepicker-other-month{border:transparent}.edd-datepicker tr:first-of-type td{border-top:1px solid #f0f0f0}.edd-datepicker td.ui-datepicker-week-end{background-color:#f4f4f4;border:1px solid #f0f0f0}.edd-datepicker td.ui-datepicker-today{background-color:#f0f0c0}.edd-datepicker td.ui-datepicker-current-day{background:#bd8}.edd-datepicker td .ui-state-default{background:transparent;border:none;text-align:center;text-decoration:none;width:auto;display:block;padding:5px 10px;font-weight:400;color:#444}.edd-datepicker td.ui-state-disabled .ui-state-default{opacity:.5}.edd-datepicker .ui-datepicker-header,.edd-datepicker .ui-widget-header{background:#00a0d2}.edd-datepicker thead{background:#32373c}.edd-datepicker td .ui-state-hover{background:#0073aa;color:#fff}.admin-color-fresh .edd-datepicker .ui-datepicker-header,.admin-color-fresh .edd-datepicker .ui-widget-header{background:#00a0d2}.admin-color-fresh .edd-datepicker thead{background:#32373c}.admin-color-fresh .edd-datepicker td .ui-state-hover{background:#0073aa;color:#fff}.admin-color-blue .edd-datepicker .ui-datepicker-header,.admin-color-blue .edd-datepicker .ui-widget-header{background:#52accc}.admin-color-blue .edd-datepicker thead{background:#4796b3}.admin-color-blue .edd-datepicker td .ui-state-hover{background:#096484;color:#fff}.admin-color-coffee .edd-datepicker .ui-datepicker-header,.admin-color-coffee .edd-datepicker .ui-widget-header{background:#59524c}.admin-color-coffee .edd-datepicker thead{background:#46403c}.admin-color-coffee .edd-datepicker td .ui-state-hover{background:#c7a589;color:#fff}.admin-color-ectoplasm .edd-datepicker .ui-datepicker-header,.admin-color-ectoplasm .edd-datepicker .ui-widget-header{background:#523f6d}.admin-color-ectoplasm .edd-datepicker thead{background:#413256}.admin-color-ectoplasm .edd-datepicker td .ui-state-hover{background:#a3b745;color:#fff}.admin-color-midnight .edd-datepicker .ui-datepicker-header,.admin-color-midnight .edd-datepicker .ui-widget-header{background:#363b3f}.admin-color-midnight .edd-datepicker thead{background:#26292c}.admin-color-midnight .edd-datepicker td .ui-state-hover{background:#e14d43;color:#fff}.admin-color-ocean .edd-datepicker .ui-datepicker-header,.admin-color-ocean .edd-datepicker .ui-widget-header{background:#738e96}.admin-color-ocean .edd-datepicker thead{background:#627c83}.admin-color-ocean .edd-datepicker td .ui-state-hover{background:#9ebaa0;color:#fff}.admin-color-sunrise .edd-datepicker .ui-datepicker-header,.admin-color-sunrise .edd-datepicker .ui-datepicker-header .ui-state-hover,.admin-color-sunrise .edd-datepicker .ui-widget-header{background:#cf4944}.admin-color-sunrise .edd-datepicker th{border-color:#be3631;background:#be3631}.admin-color-sunrise .edd-datepicker td .ui-state-hover{background:#dd823b;color:#fff}.admin-color-light .edd-datepicker .ui-datepicker-header,.admin-color-light .edd-datepicker .ui-widget-header{background:#e5e5e5}.admin-color-light .edd-datepicker thead{background:#888}.admin-color-light .edd-datepicker .ui-datepicker-next:before,.admin-color-light .edd-datepicker .ui-datepicker-prev:before,.admin-color-light .edd-datepicker .ui-datepicker-title,.admin-color-light .edd-datepicker td .ui-state-default{color:#555}.admin-color-light .edd-datepicker td .ui-state-hover{background:#e5e5e5}.admin-color-bbp-evergreen .edd-datepicker .ui-datepicker-header,.admin-color-bbp-evergreen .edd-datepicker .ui-widget-header{background:#56b274}.admin-color-bbp-evergreen .edd-datepicker thead{background:#36533f}.admin-color-bbp-evergreen .edd-datepicker td .ui-state-hover{background:#446950;color:#fff}.admin-color-bbp-mint .edd-datepicker .ui-datepicker-header,.admin-color-bbp-mint .edd-datepicker .ui-widget-header{background:#4ca26a}.admin-color-bbp-mint .edd-datepicker thead{background:#4f6d59}.admin-color-bbp-mint .edd-datepicker td .ui-state-hover{background:#5fb37c;color:#fff} \ No newline at end of file diff --git a/assets/css/edd-admin-datepicker.min.css b/assets/css/edd-admin-datepicker.min.css new file mode 100644 index 00000000000..0b357d49975 --- /dev/null +++ b/assets/css/edd-admin-datepicker.min.css @@ -0,0 +1 @@ +.edd-datepicker{padding:0;margin:0;border-radius:0;background-color:#fff;border:1px solid #dfdfdf;border-top:none;box-shadow:0 3px 6px rgba(0,0,0,.075);min-width:17em;width:auto;z-index:1000!important}.edd-datepicker *{padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;border-radius:0}.edd-datepicker table{font-size:13px;margin:0;border:none;border-collapse:collapse}.edd-datepicker .ui-datepicker-header,.edd-datepicker .ui-widget-header{background-image:none;border:none;color:#fff;font-weight:400;padding:.2em 0}.edd-datepicker .ui-datepicker-header .ui-state-hover{background:transparent;border-color:transparent;cursor:pointer}.edd-datepicker .ui-datepicker-title{margin:0;padding:10px 0;color:#fff;font-size:14px;line-height:14px;text-align:center}.edd-datepicker .ui-datepicker-next,.edd-datepicker .ui-datepicker-prev{position:relative;top:0;height:34px;width:34px}.edd-datepicker .ui-state-hover.ui-datepicker-next,.edd-datepicker .ui-state-hover.ui-datepicker-prev{border:none}.edd-datepicker .ui-datepicker-prev,.edd-datepicker .ui-datepicker-prev-hover{left:0}.edd-datepicker .ui-datepicker-next,.edd-datepicker .ui-datepicker-next-hover{right:0}.edd-datepicker .ui-datepicker-next span,.edd-datepicker .ui-datepicker-prev span{display:none}.edd-datepicker .ui-datepicker-prev{float:left}.edd-datepicker .ui-datepicker-next{float:right}.edd-datepicker .ui-datepicker-next:before,.edd-datepicker .ui-datepicker-prev:before{font:normal 20px/34px dashicons;padding-left:7px;color:#fff;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;width:34px;height:34px}.edd-datepicker .ui-datepicker-prev:before{content:""}.edd-datepicker .ui-datepicker-next:before{content:""}.edd-datepicker .ui-datepicker-next-hover:before,.edd-datepicker .ui-datepicker-prev-hover:before{opacity:.7}.edd-datepicker select.ui-datepicker-month,.edd-datepicker select.ui-datepicker-year{width:33%}.edd-datepicker thead{color:#fff;font-weight:600}.edd-datepicker th{padding:10px}.edd-datepicker td{padding:0;border:1px solid #f4f4f4}.edd-datepicker td.ui-datepicker-other-month{border:transparent}.edd-datepicker tr:first-of-type td{border-top:1px solid #f0f0f0}.edd-datepicker td.ui-datepicker-week-end{background-color:#f4f4f4;border:1px solid #f0f0f0}.edd-datepicker td.ui-datepicker-today{background-color:#f0f0c0}.edd-datepicker td.ui-datepicker-current-day{background:#bd8}.edd-datepicker td .ui-state-default{background:transparent;border:none;text-align:center;text-decoration:none;width:auto;display:block;padding:5px 10px;font-weight:400;color:#444}.edd-datepicker td.ui-state-disabled .ui-state-default{opacity:.5}.edd-datepicker .ui-datepicker-header,.edd-datepicker .ui-widget-header{background:#00a0d2}.edd-datepicker thead{background:#32373c}.edd-datepicker td .ui-state-hover{background:#0073aa;color:#fff}.admin-color-fresh .edd-datepicker .ui-datepicker-header,.admin-color-fresh .edd-datepicker .ui-widget-header{background:#00a0d2}.admin-color-fresh .edd-datepicker thead{background:#32373c}.admin-color-fresh .edd-datepicker td .ui-state-hover{background:#0073aa;color:#fff}.admin-color-blue .edd-datepicker .ui-datepicker-header,.admin-color-blue .edd-datepicker .ui-widget-header{background:#52accc}.admin-color-blue .edd-datepicker thead{background:#4796b3}.admin-color-blue .edd-datepicker td .ui-state-hover{background:#096484;color:#fff}.admin-color-coffee .edd-datepicker .ui-datepicker-header,.admin-color-coffee .edd-datepicker .ui-widget-header{background:#59524c}.admin-color-coffee .edd-datepicker thead{background:#46403c}.admin-color-coffee .edd-datepicker td .ui-state-hover{background:#c7a589;color:#fff}.admin-color-ectoplasm .edd-datepicker .ui-datepicker-header,.admin-color-ectoplasm .edd-datepicker .ui-widget-header{background:#523f6d}.admin-color-ectoplasm .edd-datepicker thead{background:#413256}.admin-color-ectoplasm .edd-datepicker td .ui-state-hover{background:#a3b745;color:#fff}.admin-color-midnight .edd-datepicker .ui-datepicker-header,.admin-color-midnight .edd-datepicker .ui-widget-header{background:#363b3f}.admin-color-midnight .edd-datepicker thead{background:#26292c}.admin-color-midnight .edd-datepicker td .ui-state-hover{background:#e14d43;color:#fff}.admin-color-ocean .edd-datepicker .ui-datepicker-header,.admin-color-ocean .edd-datepicker .ui-widget-header{background:#738e96}.admin-color-ocean .edd-datepicker thead{background:#627c83}.admin-color-ocean .edd-datepicker td .ui-state-hover{background:#9ebaa0;color:#fff}.admin-color-sunrise .edd-datepicker .ui-datepicker-header,.admin-color-sunrise .edd-datepicker .ui-datepicker-header .ui-state-hover,.admin-color-sunrise .edd-datepicker .ui-widget-header{background:#cf4944}.admin-color-sunrise .edd-datepicker th{border-color:#be3631;background:#be3631}.admin-color-sunrise .edd-datepicker td .ui-state-hover{background:#dd823b;color:#fff}.admin-color-light .edd-datepicker .ui-datepicker-header,.admin-color-light .edd-datepicker .ui-widget-header{background:#e5e5e5}.admin-color-light .edd-datepicker thead{background:#888}.admin-color-light .edd-datepicker .ui-datepicker-next:before,.admin-color-light .edd-datepicker .ui-datepicker-prev:before,.admin-color-light .edd-datepicker .ui-datepicker-title,.admin-color-light .edd-datepicker td .ui-state-default{color:#555}.admin-color-light .edd-datepicker td .ui-state-hover{background:#e5e5e5}.admin-color-bbp-evergreen .edd-datepicker .ui-datepicker-header,.admin-color-bbp-evergreen .edd-datepicker .ui-widget-header{background:#56b274}.admin-color-bbp-evergreen .edd-datepicker thead{background:#36533f}.admin-color-bbp-evergreen .edd-datepicker td .ui-state-hover{background:#446950;color:#fff}.admin-color-bbp-mint .edd-datepicker .ui-datepicker-header,.admin-color-bbp-mint .edd-datepicker .ui-widget-header{background:#4ca26a}.admin-color-bbp-mint .edd-datepicker thead{background:#4f6d59}.admin-color-bbp-mint .edd-datepicker td .ui-state-hover{background:#5fb37c;color:#fff} \ No newline at end of file diff --git a/assets/css/edd-admin-email-tags-rtl.min.css b/assets/css/edd-admin-email-tags-rtl.min.css new file mode 100644 index 00000000000..30fcdedc6cb --- /dev/null +++ b/assets/css/edd-admin-email-tags-rtl.min.css @@ -0,0 +1 @@ +.edd-email-tags-filter{margin:-15px -15px 15px;padding:15px;border-bottom:1px solid #ddd;background:#f3f3f3}.edd-email-tags-filter-search{padding:10px;width:100%;font-size:20px}.edd-email-tags-list{margin:0}.edd-email-tags-list-item{margin-bottom:15px}.edd-email-tags-list-item::last-child{margin-bottom:0}.edd-email-tags-list-button{cursor:pointer;color:#666;text-align:right;padding:.8rem;width:100%;background:none;border:1px solid #ddd;border-radius:3px}.edd-email-tags-list-button:hover{background:#fafafa}.edd-email-tags-list-button strong{color:#444;font-size:14px;margin-bottom:8px}.edd-email-tags-list-button code{margin-right:10px}.edd-email-tags-list-button span{display:block;margin-top:10px} \ No newline at end of file diff --git a/assets/css/edd-admin-email-tags.min.css b/assets/css/edd-admin-email-tags.min.css new file mode 100644 index 00000000000..018479f4a76 --- /dev/null +++ b/assets/css/edd-admin-email-tags.min.css @@ -0,0 +1 @@ +.edd-email-tags-filter{margin:-15px -15px 15px;padding:15px;border-bottom:1px solid #ddd;background:#f3f3f3}.edd-email-tags-filter-search{padding:10px;width:100%;font-size:20px}.edd-email-tags-list{margin:0}.edd-email-tags-list-item{margin-bottom:15px}.edd-email-tags-list-item::last-child{margin-bottom:0}.edd-email-tags-list-button{cursor:pointer;color:#666;text-align:left;padding:.8rem;width:100%;background:none;border:1px solid #ddd;border-radius:3px}.edd-email-tags-list-button:hover{background:#fafafa}.edd-email-tags-list-button strong{color:#444;font-size:14px;margin-bottom:8px}.edd-email-tags-list-button code{margin-left:10px}.edd-email-tags-list-button span{display:block;margin-top:10px} \ No newline at end of file diff --git a/assets/css/edd-admin-extension-manager-rtl.min.css b/assets/css/edd-admin-extension-manager-rtl.min.css new file mode 100644 index 00000000000..1e5a349aed0 --- /dev/null +++ b/assets/css/edd-admin-extension-manager-rtl.min.css @@ -0,0 +1 @@ +.edd-extension-manager__bar{justify-content:space-between;flex-wrap:wrap}.edd-extension-manager__bar,.edd-extension-manager__bar-heading{display:flex;align-items:center;gap:1em}.edd-extension-manager__bar input[type=search]{background-color:#fff;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBjbGFzcz0idy02IGgtNiI+PHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMjEgMjFsLTUuMTk3LTUuMTk3bTAgMEE3LjUgNy41IDAgMTA1LjE5NiA1LjE5NmE3LjUgNy41IDAgMDAxMC42MDcgMTAuNjA3eiIvPjwvc3ZnPg==);background-size:1em;background-repeat:no-repeat;background-position:3%}.edd-extension-manager__bar input[type=search]:active,.edd-extension-manager__bar input[type=search]:focus,.edd-extension-manager__bar input[type=search]:hover{background-image:none}.edd-extension-manager__body{display:-ms-grid;display:grid;gap:1.5em;-ms-grid-rows:auto 1fr;grid-template-rows:auto 1fr;flex-grow:1}.edd-extension-manager__body img{max-width:100%}.edd-extension-manager__body .notice{max-width:320px}.edd-extension-manager__title{line-height:1.4;margin:0 0 1em 1em}.edd-extension-manager__title a{color:#32373c}.button.edd-extension-manager__action-upgrade{background-color:#008a20;color:#fff}.button.edd-extension-manager__action-upgrade:active,.button.edd-extension-manager__action-upgrade:hover{background-color:#005714;color:#fff}.edd-extension-manager__card--installer .button{display:flex;justify-content:center;align-items:center;gap:5px;margin:0}.edd-extension-manager__card--installer .button:before{margin:0}.edd-extension-manager__card--installer .button.edd-button__install{color:#32373c;border-color:#7e8993}.edd-extension-manager__card--installer .button.edd-button__install:before{content:" ";display:block;width:1em;height:1em;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjExMiAxdjUuMDc2aDEuODUydi43NEw4IDkuNzgyIDUuMDM2IDYuODE3di0uNzQxaDEuODUzVjFoMi4yMjN6bS01LjkyOCA5LjE1MnYxLjg1Mmg5LjYzM3YtMS44NTJoMi45NjR2NC44MTZILjIxOXYtNC44MTdoMi45NjV6IiBmaWxsPSIjOTk5Ii8+PC9zdmc+);background-size:1em}.edd-extension-manager__card--installer .button.edd-button__install.edd-updating:before{background:none;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #f9f9f9;border-bottom-color:#7e8993;border-radius:100%;content:"";width:12px;height:12px;transform:translate3d(50%,-50%,0);will-change:transform}.edd-extension-manager__control{display:flex;align-items:center;gap:.5em;min-height:30px}.edd-extension-manager__control .edd-button__toggle{position:relative;margin:0;padding:0;width:36px;height:20px;min-height:unset;background-color:#c3c4c7;transition:background .2s ease;border-radius:30px;box-shadow:none;border:none}.edd-extension-manager__control .edd-button__toggle:after{position:absolute;content:"";height:14px;width:14px;right:3px;bottom:3px;background-color:#fff;transition:transform .1s ease;border-radius:50%}.edd-plugin__active .edd-extension-manager__control .edd-button__toggle:after{transform:translateX(-16px)}.edd-extension-manager__control .edd-button__toggle:active,.edd-extension-manager__control .edd-button__toggle:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}.edd-extension-manager__control .edd-button__toggle:hover{background-color:#7e8993}.edd-extension-manager__control .edd-button__toggle:disabled{background-color:#7e8993!important}.edd-extension-manager__control .edd-button__toggle:disabled:before{position:absolute;top:3px;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:10px;height:10px;transform:translate3d(50%,-50%,0);will-change:transform}.edd-extension-manager__control .edd-button__toggle:disabled:after{display:none}.edd-plugin__active .edd-extension-manager__control .edd-button__toggle{background-color:var(--wp-admin-theme-color)}.edd-plugin__active .edd-extension-manager__control .edd-button__toggle :active,.edd-plugin__active .edd-extension-manager__control .edd-button__toggle :focus{box-shadow:0 0 0 1px #fff,0 0 0 3px var(--wp-admin-theme-color)}@media screen and (max-width:782px){.edd-extension-manager__control{min-height:40px}}a.button.edd-extension-manager__button-settings{display:none;position:absolute;top:1em;left:1em;min-height:unset;height:1.5em;width:1.5em;padding:1em;border:none;background-color:#f9f9f9}a.button.edd-extension-manager__button-settings,a.button.edd-extension-manager__button-settings:active,a.button.edd-extension-manager__button-settings:hover{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yOSA2LjI5M2wtMS41MTUuODc1YTUuODczIDUuODczIDAgMDEwIDEuNjY0bDEuNTE1Ljg3NS0yLjE2NiAzLjc1My0xLjUxNi0uODc1YTUuODI3IDUuODI3IDAgMDEtMS40NDEuODMzdjEuNzQ5SDUuODM0di0xLjc1YTUuODI1IDUuODI1IDAgMDEtMS40NDEtLjgzMmwtMS41MTYuODc1TC43MSA5LjcwN2wxLjUxNi0uODc1YTUuODc4IDUuODc4IDAgMDEwLTEuNjY0TC43MSA2LjI5MyAyLjg3NyAyLjU0bDEuNTE2Ljg3NmE1LjgyNyA1LjgyNyAwIDAxMS40NC0uODMzVi44MzNoNC4zMzR2MS43NWE1LjgzIDUuODMgMCAwMTEuNDQuODMzbDEuNTE3LS44NzYgMi4xNjYgMy43NTN6TTggMTAuMzMzYTIuMzMzIDIuMzMzIDAgMTAwLTQuNjY2IDIuMzMzIDIuMzMzIDAgMDAwIDQuNjY2eiIgZmlsbD0iIzZCNzI4MCIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTUuMjkgNi4yOTNsLTEuNTE1Ljg3NWE1Ljg3MyA1Ljg3MyAwIDAxMCAxLjY2NGwxLjUxNS44NzUtMi4xNjYgMy43NTMtMS41MTYtLjg3NWE1LjgyNyA1LjgyNyAwIDAxLTEuNDQxLjgzM3YxLjc0OUg1LjgzNHYtMS43NWE1LjgyNSA1LjgyNSAwIDAxLTEuNDQxLS44MzJsLTEuNTE2Ljg3NUwuNzEgOS43MDdsMS41MTYtLjg3NWE1Ljg3OCA1Ljg3OCAwIDAxMC0xLjY2NEwuNzEgNi4yOTMgMi44NzcgMi41NGwxLjUxNi44NzZhNS44MjcgNS44MjcgMCAwMTEuNDQtLjgzM1YuODMzaDQuMzM0djEuNzVhNS44MyA1LjgzIDAgMDExLjQ0LjgzM2wxLjUxNy0uODc2IDIuMTY2IDMuNzUzek04IDEwLjMzM2EyLjMzMyAyLjMzMyAwIDEwMC00LjY2NiAyLjMzMyAyLjMzMyAwIDAwMCA0LjY2NnoiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjUiLz48L3N2Zz4=);background-size:1.25em;background-repeat:no-repeat;background-position:50%}.edd-plugin__active a.button.edd-extension-manager__button-settings{display:block}.edd-extension-manager__card{background-color:#fff;padding:2em;margin:0;display:flex;flex-direction:column;justify-content:space-between}.edd-extension-manager__card.edd-hidden{display:none}.inside .edd-extension-manager__card{padding:0}.edd-extension-manager__features ul{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}.edd-extension-manager__features .dashicons-yes{color:#008a20;margin-left:.25em}.edd-extension-manager__wrap{max-width:1440px}.edd-extension-manager__card-group{transition:all .5s}@supports(grid-area:auto){.edd-extension-manager__card-group{display:grid;grid-template-columns:auto;gap:1em;margin-top:24px}}.edd-extension-manager__group{display:-ms-grid;display:grid}.edd-extension-manager__unlock{margin-top:4em}.edd-extension-manager__icon{border:1px solid #e0e0e0;border-radius:4px;width:78px;height:78px}.edd-extension-manager__icon img{border-radius:12px;display:block;margin:0;padding:9px;width:60px}.edd-extension-manager__image img{display:block;margin:0 auto;max-width:500px;width:100%}.edd-extension-manager__card--installer{border:1px solid #dcdcde;border-radius:3px;padding:0}.edd-extension-manager__card--installer>div{padding:2em}.edd-extension-manager__card--installer>div:not(:last-child){border-bottom:1px solid #e0e0e0}.edd-extension-manager__card--installer .notice{margin:0 -1em!important}.edd-extension-manager__card--installer .notice:not(:last-child){margin-bottom:1em}.edd-extension-manager__card--installer .edd-extension-manager__body{-ms-grid-columns:80px 1fr;grid-template-columns:80px 1fr;-ms-grid-rows:unset;grid-template-rows:unset;position:relative}.edd-extension-manager__card--installer .edd-extension-manager__body p:last-child{margin-bottom:0}.edd-extension-manager__card--installer .edd-extension-manager__actions{background:#f9f9f9;display:flex;justify-content:space-between;align-items:center;padding:1em 2em}.edd-extension-manager__card--installer .edd-extension-manager__actions>:only-child{margin-right:auto}.edd-extension-manager__card--installer .edd-extension-manager__status{font-weight:600}.edd-extension-manager__key-notice{background:#fff;border:1px solid #d63638;border-radius:4px;padding:1em}.edd-extension-manager__key-notice p:first-child{margin-top:0}.edd-extension-manager__step{-ms-grid-row:1;grid-area:1/-1;margin:0}.edd-extension-manager__step:not(:first-of-type){display:none}.edd-extension-manager__step .button{display:table;margin:0 auto;text-align:center;white-space:normal}td .edd-extension-manager__step .button{display:inline-block}.edd-extension-manager__card--horizontal{margin:24px 0;max-width:700px}.edd-extension-manager__card--detailed{background-color:transparent;max-width:700px}.edd-extension-manager__card--detailed-2col{background-color:transparent;max-width:900px}.edd-extension-manager__card--detailed-2col .edd-extension-manager__title,.edd-extension-manager__card--detailed .edd-extension-manager__title{border-bottom:1px solid #ccc;padding-bottom:1em}@media screen and (min-width:600px){.edd-extension-manager__card-group{grid-template-columns:repeat(auto-fill,minmax(340px,1fr))}.edd-extension-manager__card--horizontal .edd-extension-manager__body{-ms-grid-columns:minmax(0,300px) 1fr;grid-template-columns:minmax(0,300px) 1fr;-ms-grid-rows:1fr auto;grid-template-rows:1fr auto;grid-auto-flow:column}.edd-extension-manager__card--horizontal .edd-extension-manager__image{-ms-grid-row:1;-ms-grid-row-span:3;grid-row:1/4}.edd-extension-manager__card--horizontal .edd-extension-manager__description,.edd-extension-manager__features{-ms-grid-row-align:center;align-self:center}}@media screen and (min-width:783px){.edd-extension-manager__card--detailed-2col .edd-extension-manager__body{-ms-grid-columns:minmax(0,375px) 1fr;grid-template-columns:minmax(0,375px) 1fr;grid-auto-flow:column}} \ No newline at end of file diff --git a/assets/css/edd-admin-extension-manager.min.css b/assets/css/edd-admin-extension-manager.min.css new file mode 100644 index 00000000000..461982d9d62 --- /dev/null +++ b/assets/css/edd-admin-extension-manager.min.css @@ -0,0 +1 @@ +.edd-extension-manager__bar{justify-content:space-between;flex-wrap:wrap}.edd-extension-manager__bar,.edd-extension-manager__bar-heading{display:flex;align-items:center;gap:1em}.edd-extension-manager__bar input[type=search]{background-color:#fff;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBjbGFzcz0idy02IGgtNiI+PHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMjEgMjFsLTUuMTk3LTUuMTk3bTAgMEE3LjUgNy41IDAgMTA1LjE5NiA1LjE5NmE3LjUgNy41IDAgMDAxMC42MDcgMTAuNjA3eiIvPjwvc3ZnPg==);background-size:1em;background-repeat:no-repeat;background-position:97%}.edd-extension-manager__bar input[type=search]:active,.edd-extension-manager__bar input[type=search]:focus,.edd-extension-manager__bar input[type=search]:hover{background-image:none}.edd-extension-manager__body{display:-ms-grid;display:grid;gap:1.5em;-ms-grid-rows:auto 1fr;grid-template-rows:auto 1fr;flex-grow:1}.edd-extension-manager__body img{max-width:100%}.edd-extension-manager__body .notice{max-width:320px}.edd-extension-manager__title{line-height:1.4;margin:0 1em 1em 0}.edd-extension-manager__title a{color:#32373c}.button.edd-extension-manager__action-upgrade{background-color:#008a20;color:#fff}.button.edd-extension-manager__action-upgrade:active,.button.edd-extension-manager__action-upgrade:hover{background-color:#005714;color:#fff}.edd-extension-manager__card--installer .button{display:flex;justify-content:center;align-items:center;gap:5px;margin:0}.edd-extension-manager__card--installer .button:before{margin:0}.edd-extension-manager__card--installer .button.edd-button__install{color:#32373c;border-color:#7e8993}.edd-extension-manager__card--installer .button.edd-button__install:before{content:" ";display:block;width:1em;height:1em;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjExMiAxdjUuMDc2aDEuODUydi43NEw4IDkuNzgyIDUuMDM2IDYuODE3di0uNzQxaDEuODUzVjFoMi4yMjN6bS01LjkyOCA5LjE1MnYxLjg1Mmg5LjYzM3YtMS44NTJoMi45NjR2NC44MTZILjIxOXYtNC44MTdoMi45NjV6IiBmaWxsPSIjOTk5Ii8+PC9zdmc+);background-size:1em}.edd-extension-manager__card--installer .button.edd-button__install.edd-updating:before{background:none;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #f9f9f9;border-bottom-color:#7e8993;border-radius:100%;content:"";width:12px;height:12px;transform:translate3d(-50%,-50%,0);will-change:transform}.edd-extension-manager__control{display:flex;align-items:center;gap:.5em;min-height:30px}.edd-extension-manager__control .edd-button__toggle{position:relative;margin:0;padding:0;width:36px;height:20px;min-height:unset;background-color:#c3c4c7;transition:background .2s ease;border-radius:30px;box-shadow:none;border:none}.edd-extension-manager__control .edd-button__toggle:after{position:absolute;content:"";height:14px;width:14px;left:3px;bottom:3px;background-color:#fff;transition:transform .1s ease;border-radius:50%}.edd-plugin__active .edd-extension-manager__control .edd-button__toggle:after{transform:translateX(16px)}.edd-extension-manager__control .edd-button__toggle:active,.edd-extension-manager__control .edd-button__toggle:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}.edd-extension-manager__control .edd-button__toggle:hover{background-color:#7e8993}.edd-extension-manager__control .edd-button__toggle:disabled{background-color:#7e8993!important}.edd-extension-manager__control .edd-button__toggle:disabled:before{position:absolute;top:3px;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:10px;height:10px;transform:translate3d(-50%,-50%,0);will-change:transform}.edd-extension-manager__control .edd-button__toggle:disabled:after{display:none}.edd-plugin__active .edd-extension-manager__control .edd-button__toggle{background-color:var(--wp-admin-theme-color)}.edd-plugin__active .edd-extension-manager__control .edd-button__toggle :active,.edd-plugin__active .edd-extension-manager__control .edd-button__toggle :focus{box-shadow:0 0 0 1px #fff,0 0 0 3px var(--wp-admin-theme-color)}@media screen and (max-width:782px){.edd-extension-manager__control{min-height:40px}}a.button.edd-extension-manager__button-settings{display:none;position:absolute;top:1em;right:1em;min-height:unset;height:1.5em;width:1.5em;padding:1em;border:none;background-color:#f9f9f9}a.button.edd-extension-manager__button-settings,a.button.edd-extension-manager__button-settings:active,a.button.edd-extension-manager__button-settings:hover{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yOSA2LjI5M2wtMS41MTUuODc1YTUuODczIDUuODczIDAgMDEwIDEuNjY0bDEuNTE1Ljg3NS0yLjE2NiAzLjc1My0xLjUxNi0uODc1YTUuODI3IDUuODI3IDAgMDEtMS40NDEuODMzdjEuNzQ5SDUuODM0di0xLjc1YTUuODI1IDUuODI1IDAgMDEtMS40NDEtLjgzMmwtMS41MTYuODc1TC43MSA5LjcwN2wxLjUxNi0uODc1YTUuODc4IDUuODc4IDAgMDEwLTEuNjY0TC43MSA2LjI5MyAyLjg3NyAyLjU0bDEuNTE2Ljg3NmE1LjgyNyA1LjgyNyAwIDAxMS40NC0uODMzVi44MzNoNC4zMzR2MS43NWE1LjgzIDUuODMgMCAwMTEuNDQuODMzbDEuNTE3LS44NzYgMi4xNjYgMy43NTN6TTggMTAuMzMzYTIuMzMzIDIuMzMzIDAgMTAwLTQuNjY2IDIuMzMzIDIuMzMzIDAgMDAwIDQuNjY2eiIgZmlsbD0iIzZCNzI4MCIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTUuMjkgNi4yOTNsLTEuNTE1Ljg3NWE1Ljg3MyA1Ljg3MyAwIDAxMCAxLjY2NGwxLjUxNS44NzUtMi4xNjYgMy43NTMtMS41MTYtLjg3NWE1LjgyNyA1LjgyNyAwIDAxLTEuNDQxLjgzM3YxLjc0OUg1LjgzNHYtMS43NWE1LjgyNSA1LjgyNSAwIDAxLTEuNDQxLS44MzJsLTEuNTE2Ljg3NUwuNzEgOS43MDdsMS41MTYtLjg3NWE1Ljg3OCA1Ljg3OCAwIDAxMC0xLjY2NEwuNzEgNi4yOTMgMi44NzcgMi41NGwxLjUxNi44NzZhNS44MjcgNS44MjcgMCAwMTEuNDQtLjgzM1YuODMzaDQuMzM0djEuNzVhNS44MyA1LjgzIDAgMDExLjQ0LjgzM2wxLjUxNy0uODc2IDIuMTY2IDMuNzUzek04IDEwLjMzM2EyLjMzMyAyLjMzMyAwIDEwMC00LjY2NiAyLjMzMyAyLjMzMyAwIDAwMCA0LjY2NnoiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjUiLz48L3N2Zz4=);background-size:1.25em;background-repeat:no-repeat;background-position:50%}.edd-plugin__active a.button.edd-extension-manager__button-settings{display:block}.edd-extension-manager__card{background-color:#fff;padding:2em;margin:0;display:flex;flex-direction:column;justify-content:space-between}.edd-extension-manager__card.edd-hidden{display:none}.inside .edd-extension-manager__card{padding:0}.edd-extension-manager__features ul{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}.edd-extension-manager__features .dashicons-yes{color:#008a20;margin-right:.25em}.edd-extension-manager__wrap{max-width:1440px}.edd-extension-manager__card-group{transition:all .5s}@supports(grid-area:auto){.edd-extension-manager__card-group{display:grid;grid-template-columns:auto;gap:1em;margin-top:24px}}.edd-extension-manager__group{display:-ms-grid;display:grid}.edd-extension-manager__unlock{margin-top:4em}.edd-extension-manager__icon{border:1px solid #e0e0e0;border-radius:4px;width:78px;height:78px}.edd-extension-manager__icon img{border-radius:12px;display:block;margin:0;padding:9px;width:60px}.edd-extension-manager__image img{display:block;margin:0 auto;max-width:500px;width:100%}.edd-extension-manager__card--installer{border:1px solid #dcdcde;border-radius:3px;padding:0}.edd-extension-manager__card--installer>div{padding:2em}.edd-extension-manager__card--installer>div:not(:last-child){border-bottom:1px solid #e0e0e0}.edd-extension-manager__card--installer .notice{margin:0 -1em!important}.edd-extension-manager__card--installer .notice:not(:last-child){margin-bottom:1em}.edd-extension-manager__card--installer .edd-extension-manager__body{-ms-grid-columns:80px 1fr;grid-template-columns:80px 1fr;-ms-grid-rows:unset;grid-template-rows:unset;position:relative}.edd-extension-manager__card--installer .edd-extension-manager__body p:last-child{margin-bottom:0}.edd-extension-manager__card--installer .edd-extension-manager__actions{background:#f9f9f9;display:flex;justify-content:space-between;align-items:center;padding:1em 2em}.edd-extension-manager__card--installer .edd-extension-manager__actions>:only-child{margin-left:auto}.edd-extension-manager__card--installer .edd-extension-manager__status{font-weight:600}.edd-extension-manager__key-notice{background:#fff;border:1px solid #d63638;border-radius:4px;padding:1em}.edd-extension-manager__key-notice p:first-child{margin-top:0}.edd-extension-manager__step{-ms-grid-row:1;grid-area:1/-1;margin:0}.edd-extension-manager__step:not(:first-of-type){display:none}.edd-extension-manager__step .button{display:table;margin:0 auto;text-align:center;white-space:normal}td .edd-extension-manager__step .button{display:inline-block}.edd-extension-manager__card--horizontal{margin:24px 0;max-width:700px}.edd-extension-manager__card--detailed{background-color:transparent;max-width:700px}.edd-extension-manager__card--detailed-2col{background-color:transparent;max-width:900px}.edd-extension-manager__card--detailed-2col .edd-extension-manager__title,.edd-extension-manager__card--detailed .edd-extension-manager__title{border-bottom:1px solid #ccc;padding-bottom:1em}@media screen and (min-width:600px){.edd-extension-manager__card-group{grid-template-columns:repeat(auto-fill,minmax(340px,1fr))}.edd-extension-manager__card--horizontal .edd-extension-manager__body{-ms-grid-columns:minmax(0,300px) 1fr;grid-template-columns:minmax(0,300px) 1fr;-ms-grid-rows:1fr auto;grid-template-rows:1fr auto;grid-auto-flow:column}.edd-extension-manager__card--horizontal .edd-extension-manager__image{-ms-grid-row:1;-ms-grid-row-span:3;grid-row:1/4}.edd-extension-manager__card--horizontal .edd-extension-manager__description,.edd-extension-manager__features{-ms-grid-row-align:center;align-self:center}}@media screen and (min-width:783px){.edd-extension-manager__card--detailed-2col .edd-extension-manager__body{-ms-grid-columns:minmax(0,375px) 1fr;grid-template-columns:minmax(0,375px) 1fr;grid-auto-flow:column}} \ No newline at end of file diff --git a/assets/css/edd-admin-menu-rtl.min.css b/assets/css/edd-admin-menu-rtl.min.css new file mode 100644 index 00000000000..e919591e398 --- /dev/null +++ b/assets/css/edd-admin-menu-rtl.min.css @@ -0,0 +1 @@ +#menu-posts-download .wp-submenu{display:flex;flex-wrap:wrap}#menu-posts-download .wp-submenu li{width:100%}#menu-posts-download a[href^="edit.php?post_type=download"]:focus,#menu-posts-download a[href^="edit.php?post_type=download"]:hover{box-shadow:inset -4px 0 0 0 currentColor;transition:box-shadow .1s linear}#menu-posts-download li>a[href^="post-new.php?post_type=download"]{display:none}#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-discount"]:after,#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-reports"]:after,#menu-posts-download li:not(:last-child) a[href^="post-new.php?post_type=download"]:after,#menu-posts-download li:nth-last-child(2) a:after{border-bottom:1px solid hsla(0,0%,100%,.2);display:block;float:right;margin:13px -15px 8px;content:"";width:calc(100% + 26px)}@media screen and (max-width:782px){#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-discount"]:after,#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-reports"]:after,#menu-posts-download li:not(:last-child) a[href^="post-new.php?post_type=download"]:after,#menu-posts-download li:nth-last-child(2) a:after{margin:20px -20px 8px;width:calc(100% + 30px)}}#adminmenu #menu-posts-download ul.wp-submenu-wrap li{clear:both}#adminmenu #menu-posts-download a.wp-has-current-submenu:after{display:none}ul#adminmenu #menu-posts-download ul.wp-submenu li.current a:before{left:0;content:" ";height:0;width:0;position:absolute;pointer-events:none;border:8px solid transparent;border-left-color:#f6f7f7;margin-top:2px}a.edd-onboarding__menu-item{background:#dd823b!important;color:#fff!important;font-weight:600}a.edd-onboarding__menu-item:hover{color:#000!important}a.edd-sidebar__upgrade-pro,a.edd-sidebar__upgrade-pro:hover{background-color:#1da867!important;color:#fff!important;font-weight:600} \ No newline at end of file diff --git a/assets/css/edd-admin-menu.min.css b/assets/css/edd-admin-menu.min.css new file mode 100644 index 00000000000..127c61d4dc3 --- /dev/null +++ b/assets/css/edd-admin-menu.min.css @@ -0,0 +1 @@ +#menu-posts-download .wp-submenu{display:flex;flex-wrap:wrap}#menu-posts-download .wp-submenu li{width:100%}#menu-posts-download a[href^="edit.php?post_type=download"]:focus,#menu-posts-download a[href^="edit.php?post_type=download"]:hover{box-shadow:inset 4px 0 0 0 currentColor;transition:box-shadow .1s linear}#menu-posts-download li>a[href^="post-new.php?post_type=download"]{display:none}#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-discount"]:after,#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-reports"]:after,#menu-posts-download li:not(:last-child) a[href^="post-new.php?post_type=download"]:after,#menu-posts-download li:nth-last-child(2) a:after{border-bottom:1px solid hsla(0,0%,100%,.2);display:block;float:left;margin:13px -15px 8px;content:"";width:calc(100% + 26px)}@media screen and (max-width:782px){#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-discount"]:after,#menu-posts-download li:not(:last-child) a[href^="edit.php?post_type=download&page=edd-reports"]:after,#menu-posts-download li:not(:last-child) a[href^="post-new.php?post_type=download"]:after,#menu-posts-download li:nth-last-child(2) a:after{margin:20px -20px 8px;width:calc(100% + 30px)}}#adminmenu #menu-posts-download ul.wp-submenu-wrap li{clear:both}#adminmenu #menu-posts-download a.wp-has-current-submenu:after{display:none}ul#adminmenu #menu-posts-download ul.wp-submenu li.current a:before{right:0;content:" ";height:0;width:0;position:absolute;pointer-events:none;border:8px solid transparent;border-right-color:#f6f7f7;margin-top:2px}a.edd-onboarding__menu-item{background:#dd823b!important;color:#fff!important;font-weight:600}a.edd-onboarding__menu-item:hover{color:#000!important}a.edd-sidebar__upgrade-pro,a.edd-sidebar__upgrade-pro:hover{background-color:#1da867!important;color:#fff!important;font-weight:600} \ No newline at end of file diff --git a/assets/css/edd-admin-onboarding-rtl.min.css b/assets/css/edd-admin-onboarding-rtl.min.css new file mode 100644 index 00000000000..5232aacbbde --- /dev/null +++ b/assets/css/edd-admin-onboarding-rtl.min.css @@ -0,0 +1 @@ +:root{--wp-admin-theme-color:#007cba;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-20:#005a87}:root body.admin-color-light{--wp-admin-theme-color:#0085ba;--wp-admin-theme-color-darker-10:#0073a1;--wp-admin-theme-color-darker-20:#006187}:root body.admin-color-modern{--wp-admin-theme-color:#3858e9;--wp-admin-theme-color-darker-10:#2145e6;--wp-admin-theme-color-darker-20:#183ad6}:root body.admin-color-blue{--wp-admin-theme-color:#096484;--wp-admin-theme-color-darker-10:#07526c;--wp-admin-theme-color-darker-20:#064054}:root body.admin-color-coffee{--wp-admin-theme-color:#46403c;--wp-admin-theme-color-darker-10:#383330;--wp-admin-theme-color-darker-20:#2b2724}:root body.admin-color-ectoplasm{--wp-admin-theme-color:#523f6d;--wp-admin-theme-color-darker-10:#46365d;--wp-admin-theme-color-darker-20:#3a2c4d}:root body.admin-color-midnight{--wp-admin-theme-color:#e14d43;--wp-admin-theme-color-darker-10:#dd382d;--wp-admin-theme-color-darker-20:#d02c21}:root body.admin-color-ocean{--wp-admin-theme-color:#627c83;--wp-admin-theme-color-darker-10:#576e74;--wp-admin-theme-color-darker-20:#4c6066}:root body.admin-color-sunrise{--wp-admin-theme-color:#dd823b;--wp-admin-theme-color-darker-10:#d97426;--wp-admin-theme-color-darker-20:#c36922}:root body.admin-color-evergreen{--wp-admin-theme-color:#36533f;--wp-admin-theme-color-darker-10:#2c4433;--wp-admin-theme-color-darker-20:#223428}:root body.admin-color-mint{--wp-admin-theme-color:#4f6d59;--wp-admin-theme-color-darker-10:#445e4d;--wp-admin-theme-color-darker-20:#3a4f41}.edd-onboarding{margin-top:80px}.edd-onboarding__logo img{display:block;width:300px;margin:0 auto 25px}.edd-onboarding__wrapper{max-width:1000px;margin:0 auto;position:relative}@media only screen and (max-width:1280px){.edd-onboarding__wrapper{max-width:850px}}.edd-onboarding__loading{z-index:99;position:fixed;right:0;top:0;width:100%;height:100%;padding-right:80px;padding-top:8px;display:flex;gap:20px;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center}.edd-onboarding__loading:before{position:absolute;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:35px;height:35px;transform:translate3d(50%,-50%,0);will-change:transform}.edd-onboarding__loading .edd-onboarding__loading-status{display:block;text-align:center;color:#000;flex-basis:100%;margin-top:80px}@media only screen and (max-width:600px){.edd-onboarding__loading{padding-right:0}}.edd-onboarding__loading-in-progress .edd-onboarding__single-step,.edd-onboarding__loading-in-progress .edd-onboarding__welcome-screen{position:relative}.edd-onboarding__loading-in-progress .edd-onboarding__single-step:before,.edd-onboarding__loading-in-progress .edd-onboarding__welcome-screen:before{content:"";position:absolute;right:0;top:0;width:100%;height:100%;background:hsla(0,0%,100%,.85);z-index:95}.edd-onboarding__steps{margin-top:25px}.edd-onboarding__steps ul{display:flex;gap:15px;position:relative}.edd-onboarding__steps ul:before{position:absolute;top:16px;right:50%;transform:translateX(50%);width:80%;height:2px;background:#dedfe0;content:"";z-index:-1}.edd-onboarding__steps ul li{flex:1;text-align:center}.edd-onboarding__steps ul li a{display:block;padding:5px 10px;color:#8a8e92;text-align:center;font-size:12px;text-decoration:none}.edd-onboarding__steps ul li a span{color:#fff;width:25px;height:25px;line-height:25px;font-size:12px;font-weight:400;border-radius:50%;background:#c2c4c6;display:inline-block;text-align:center;margin-bottom:10px;position:relative;box-shadow:none}.edd-onboarding__steps ul li a span:before{left:-8px}.edd-onboarding__steps ul li a span:after,.edd-onboarding__steps ul li a span:before{position:absolute;top:50%;transform:translateY(-50%);width:8px;height:10px;background:#f0f0f1;content:"";z-index:-1}.edd-onboarding__steps ul li a span:after{right:-8px}.edd-onboarding__steps ul li.active-step a,.edd-onboarding__steps ul li.active-step a small{color:#007cba;color:var(--wp-admin-theme-color);font-weight:500}.edd-onboarding__steps ul li.active-step a span{background:#007cba;background:var(--wp-admin-theme-color);box-shadow:0 0 4px 1px rgba(#007cba,.3);box-shadow:0 0 4px 1px rgba(var(--wp-admin-theme-color),.3)}.edd-onboarding__steps ul li.completed-step a span{width:25px;height:25px;line-height:25px;font-size:14px;background:#00ba37;box-shadow:none}.edd-onboarding__steps__name{color:#646970;display:block;font-size:11px}@media only screen and (max-width:600px){.edd-onboarding__steps ul li.completed-step a span,.edd-onboarding__steps ul li a span{width:20px;height:20px;line-height:20px}.edd-onboarding__steps__name{font-size:10px}}.edd-onboarding__current-step{position:relative}.edd-onboarding__single-step{background:#fff;border:1px solid #dedfe0;border-radius:3px;position:relative}.edd-onboarding__single-step-inner{padding:70px 140px 40px}.edd-onboarding__single-step-inner.equal{padding:70px 140px}@media only screen and (max-width:960px){.edd-onboarding__single-step-inner{padding:35px 70px 20px}.edd-onboarding__single-step-inner.equal{padding:35px 70px}}@media only screen and (max-width:600px){.edd-onboarding__single-step-inner{padding:17px 35px 10px}.edd-onboarding__single-step-inner.equal{padding:17px 35px}}.edd-onboarding__steps-indicator{opacity:.6;display:block}h1.edd-onboarding__single-step-title{font-size:24px;color:#141b38;font-weight:600}.edd-onboarding__single-step-subtitle{font-size:16px;line-height:22px;color:#141b38;font-weight:400;max-width:90%}.edd-onboarding__welcome-screen{width:100%;height:100%;background:#fff;display:flex;align-items:center}.edd-onboarding__welcome-screen h1{line-height:2rem}.edd-onboarding__welcome-screen-inner{padding:100px 80px;text-align:center}.edd-onboarding__testimonials-wrapper{display:-ms-grid;display:grid;gap:1em}.edd-onboarding__testimonial{display:flex;font-size:1rem;text-align:right;justify-content:space-between;gap:2em;align-items:center}.edd-onboarding__testimonial:not(:last-of-type){border-bottom:1px solid #dedfe0;padding-bottom:2em}.edd-onboarding__testimonial-content{flex-grow:1}.edd-onboarding__testimonial-content>span.big{font-weight:600;font-size:15px;font-style:italic}.edd-onboarding__testimonial-avatar{width:75px;height:75px;border-radius:50%;display:block}.edd-onboarding__testimonial-info{display:flex;flex-direction:column;gap:.25em}.edd-onboarding__testimonial-info>.testimonial-name{font-weight:600}.edd-onboarding__testimonial-info>.testimonial-company{font-size:10px;font-style:italic}.edd-onboarding__testimonial-info>.testimonial-stars>span{color:#ffbb38;font-size:12px;height:12px;width:12px}.edd-onboarding__welcome-screen-get-started{color:#fff!important;background:#00ba37!important;border-color:#00ba37!important;margin:1em auto!important}.edd-onboarding__welcome-screen-get-started:hover{color:#fff!important;background:#008a20!important}.edd-onboarding__plugins-list{border-top:1px solid hsla(0,0%,92.9%,.5)}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin{padding:28px 20px;border-bottom:1px solid hsla(0,0%,92.9%,.5);border-right:1px solid hsla(0,0%,92.9%,.5);border-left:1px solid hsla(0,0%,92.9%,.5);transition:all .25s ease-out}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin h3{margin-top:0;transition:all .25s ease-out}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin p{margin-bottom:0;transition:all .25s ease-out}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-control{width:100px;display:flex;justify-content:flex-end}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-control .checkbox-control{padding:0;margin:0;position:relative}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-control .checkbox-control__indicator{position:relative;top:0}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-external-link{text-decoration:none}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-details label{display:flex;align-items:center;justify-content:space-between;gap:1em}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin.disabled,.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin.disabled:hover{background:rgba(114,178,129,.04)}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin:hover{background:rgba(#007cba,.02);background:rgba(var(--wp-admin-theme-color),.02)}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin:hover .edd-onboarding__plugins-details h3,.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin:hover .edd-onboarding__plugins-details p{color:#007cba;color:var(--wp-admin-theme-color)}.edd-onboarding__single-step-footer{border-top:1px solid #ededed;padding:35px 50px;display:flex;justify-content:space-between;align-items:center}.edd-onboarding__single-step-footer .edd-onboarding__button-back{color:#787c82;text-decoration:none;transition:all .2s ease-in-out;background:none;border:none;cursor:pointer}.edd-onboarding__single-step-footer .edd-onboarding__button-back:hover{color:#007cba;color:var(--wp-admin-theme-color)}.edd-onboarding__single-step-footer .edd-onboarding__button-skip-step{opacity:.6}@media only screen and (max-width:600px){.edd-onboarding__single-step-footer{padding:17px 25px;flex-wrap:wrap;gap:5px}}.edd-onboarding__close-and-exit{text-align:center;margin-top:20px}.edd-onboarding__close-and-exit button.button-link{color:#8a8e92!important;text-decoration:none!important}@media only screen and (max-width:782px){.edd-form-group__control{display:flex;align-items:center;gap:10px}}.edd-onboarding input:not([type=checkbox]):not([type=radio]){border:1px solid #dedfe0!important;border-radius:2px!important;padding:2px 8px!important;width:100%}.edd-onboarding .quicktags-toolbar input.ed_button{width:auto}.edd-onboarding .edd-check-wrapper{display:flex;align-items:center}.wp-core-ui .edd-onboarding select{font-size:14px;line-height:2;border-color:#dedfe0;box-shadow:none;border-radius:2px;padding:0 8px 0 24px;min-height:30px;max-width:25rem;-webkit-appearance:none;background-size:16px 16px;cursor:pointer;vertical-align:middle}.edd-onboarding .form-table th{vertical-align:middle}.edd-onboarding .form-table,.edd-onboarding .form-table td,.edd-onboarding .form-table td p{color:#8a8e92;font-size:13px;line-height:18px}.edd-onboarding .form-table th,.edd-onboarding .form-wrap label{color:#141b38;font-weight:400;text-shadow:none;vertical-align:baseline}.edd-onboarding td[colspan="2"]{padding:0}.edd-onboarding__stripe-features-listing{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));list-style-type:none;margin-right:0;padding-right:0;margin-top:20px}.edd-onboarding__stripe-features-listing li{list-style-type:none;position:relative;padding-right:28px;color:#3c434a;font-size:12px;margin-bottom:10px;margin-left:10px}.edd-onboarding__stripe-features-listing li:before{position:absolute;top:0;right:0;content:"✓";background:#e9e4fe;color:#635bff;width:20px;height:20px;line-height:20px;text-align:center;border-radius:50%;display:inline-block}.edd-onboarding__stripe-content-holder{max-width:75%;margin:25px auto;background:rgba(241,238,250,.3);padding:50px 40px;border-radius:4px;border:1px solid rgba(241,238,250,.5)}.edd-onboarding__stripe-content-holder .edd-onboarding__stripe-content-logo{text-align:center;margin-bottom:25px;border-bottom:1px solid #ededed;padding-bottom:25px}.edd-onboarding__stripe-content-holder .edd-onboarding__stripe-content-logo img{max-width:180px}.edd-onboarding__stripe-content-holder .edd-onboarding__stripe-content-logo span{text-align:center;font-size:13px;line-height:20px;display:block;max-width:300px;margin:0 auto}@media only screen and (max-width:960px){.edd-onboarding__stripe-content-holder{padding:25px 20px;max-width:100%}}.edd-onboarding__button-stripe{display:block;text-align:center;margin-top:20px}#edds-stripe-disconnect-reconnect{margin-top:10px}.edd-onboarding__stripe-features-title{display:block;text-align:center;font-size:16px;margin-bottom:10px;color:#625bff;font-weight:500}.edd-onboarding__stripe-additional-text{text-align:center;font-size:11px;line-height:14px;display:block;max-width:400px;margin:30px auto 0;opacity:.6}.checkbox-control{position:relative;padding-right:30px;margin-bottom:15px;cursor:pointer;font-size:18px}.checkbox-control input{position:absolute;z-index:-1;opacity:0}.checkbox-control__indicator{position:absolute;top:2px;right:0;height:25px;width:25px;background:#f0f0f1;border-radius:3px}.checkbox-control:hover input~.checkbox-control__indicator,.checkbox-control input:focus~.checkbox-control__indicator{background:#eaeaec}.checkbox-control:hover input:not([disabled]):checked~.checkbox-control__indicator,.checkbox-control input:checked:focus~.checkbox-control__indicator,.checkbox-control input:checked~.checkbox-control__indicator{background:#007cba;background:var(--wp-admin-theme-color)}.checkbox-control input:disabled~.checkbox-control__indicator{background:#00ba37;pointer-events:none}.checkbox-control__indicator:after{content:"";position:absolute;display:none}.checkbox-control input:checked~.checkbox-control__indicator:after{display:block}.checkbox-control--checkbox .checkbox-control__indicator:after{right:9px;top:4px;width:5px;height:12px;border:solid #fff;border-width:0 0 2.5px 2.5px;transform:rotate(-40deg)}.checkbox-control--checkbox input:disabled~.checkbox-control__indicator:after{border-color:#fff}.checkbox-control.small-checkbox{padding-right:24px;margin-bottom:10px;font-size:13px}.small-checkbox .checkbox-control__indicator{top:0;right:0;height:17px;width:17px;background:#eaeaec}.checkbox-control.small-checkbox:hover input~.checkbox-control__indicator,.checkbox-control.small-checkbox input:focus~.checkbox-control__indicator{background:#dedfe0}.checkbox-control.small-checkbox:hover input:not([disabled]):checked~.checkbox-control__indicator,.checkbox-control.small-checkbox input:checked:focus~.checkbox-control__indicator,.checkbox-control.small-checkbox input:checked~.checkbox-control__indicator{background:#007cba;background:var(--wp-admin-theme-color)}.checkbox-control.small-checkbox input:disabled~.checkbox-control__indicator{background:#72b281}.checkbox-control--checkbox.small-checkbox .checkbox-control__indicator:after{right:6px;top:2.5px;width:3px;height:8px;border:solid #f6f7f7;border-width:0 0 2px 2px;transform:rotate(-40deg)}.edd-onboarding__get-suggestions-section{margin-top:30px;text-align:center;padding:50px 50px 40px;background:rgba(12,93,149,.04);border-radius:2px;border:1px solid rgba(12,93,149,.06)}.edd-onboarding__get-suggestions-section h3{margin-top:0;line-height:25px}.edd-onboarding__get-suggestions-section .edd-onboarding__get-suggestions-section_label{display:block;margin-bottom:1em}.edd-onboarding__selected-plugins{text-align:center;margin-top:25px}.edd-onboarding__install-success-wrapper{z-index:99;position:fixed;right:0;top:0;width:100%;height:100%;padding-right:80px;padding-top:8px;display:flex;align-items:center;justify-content:center;font-size:21px}.edd-onboarding__install-success-wrapper .edd-onboarding__install-success{display:flex;flex-wrap:wrap;gap:25px;text-align:center}.edd-onboarding__install-success-wrapper .edd-onboarding__install-success span{display:block;flex-basis:100%}.edd-onboarding__install-success-wrapper .edd-onboarding__install-success .emoji{font-size:65px}@media only screen and (max-width:960px){.edd-onboarding__install-success-wrapper{padding-right:0}}.edd-onboarding__product-files-row td,.edd-onboarding__product-pricing-row td{padding:0}.edd-onboarding__product-image-wrapper{display:flex;justify-content:space-between;gap:4px}.edd-onboarding__pricing-option-pill{display:flex}.edd-onboarding__pricing-option-pill button{display:inline-block;flex:1;border:1px solid #ccc;padding:10px 15px;cursor:pointer}.edd-onboarding__pricing-option-pill button:hover:not(.active){background:#dbdcdd}.edd-onboarding__pricing-option-pill .left-option{border-top-right-radius:2px;border-bottom-right-radius:2px}.edd-onboarding__pricing-option-pill .right-option{border-right:none;border-top-left-radius:2px;border-bottom-left-radius:2px}.edd-onboarding__pricing-option-pill .active{background:#007cba;background:var(--wp-admin-theme-color);border-color:#007cba;border-color:var(--wp-admin-theme-color);border-left-color:#ccc;color:#fff}.no-table-row-padding td{padding:0}.edd-onboarding__product-variable-price{display:none}.edd-onboarding__multi-option-toggle,.edd-onboarding__upload-files-toggle{display:flex;align-items:center}.edd-onboarding__multi-option-toggle span,.edd-onboarding__upload-files-toggle span{margin-right:10px}.edd-onboarding__upload-files-toggle span{color:#1d2327;font-size:1.3em;font-weight:600;display:block;margin-top:1em;margin-bottom:1em}.edd-onboarding__pricing-options-label{display:block;color:#141b38;font-weight:400;text-shadow:none;vertical-align:baseline;font-size:14px;margin-top:20px;margin-bottom:20px}.edd-add-repeatable-row{border-top:none;padding-top:8px;margin-bottom:5px}.edd-onboarding__actions{display:flex;gap:1em;justify-content:center;margin-top:2em}.edd-onboarding__actions button.edd-promo-notice-dismiss{margin:0} \ No newline at end of file diff --git a/assets/css/edd-admin-onboarding.min.css b/assets/css/edd-admin-onboarding.min.css new file mode 100644 index 00000000000..41bff714bb6 --- /dev/null +++ b/assets/css/edd-admin-onboarding.min.css @@ -0,0 +1 @@ +:root{--wp-admin-theme-color:#007cba;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-20:#005a87}:root body.admin-color-light{--wp-admin-theme-color:#0085ba;--wp-admin-theme-color-darker-10:#0073a1;--wp-admin-theme-color-darker-20:#006187}:root body.admin-color-modern{--wp-admin-theme-color:#3858e9;--wp-admin-theme-color-darker-10:#2145e6;--wp-admin-theme-color-darker-20:#183ad6}:root body.admin-color-blue{--wp-admin-theme-color:#096484;--wp-admin-theme-color-darker-10:#07526c;--wp-admin-theme-color-darker-20:#064054}:root body.admin-color-coffee{--wp-admin-theme-color:#46403c;--wp-admin-theme-color-darker-10:#383330;--wp-admin-theme-color-darker-20:#2b2724}:root body.admin-color-ectoplasm{--wp-admin-theme-color:#523f6d;--wp-admin-theme-color-darker-10:#46365d;--wp-admin-theme-color-darker-20:#3a2c4d}:root body.admin-color-midnight{--wp-admin-theme-color:#e14d43;--wp-admin-theme-color-darker-10:#dd382d;--wp-admin-theme-color-darker-20:#d02c21}:root body.admin-color-ocean{--wp-admin-theme-color:#627c83;--wp-admin-theme-color-darker-10:#576e74;--wp-admin-theme-color-darker-20:#4c6066}:root body.admin-color-sunrise{--wp-admin-theme-color:#dd823b;--wp-admin-theme-color-darker-10:#d97426;--wp-admin-theme-color-darker-20:#c36922}:root body.admin-color-evergreen{--wp-admin-theme-color:#36533f;--wp-admin-theme-color-darker-10:#2c4433;--wp-admin-theme-color-darker-20:#223428}:root body.admin-color-mint{--wp-admin-theme-color:#4f6d59;--wp-admin-theme-color-darker-10:#445e4d;--wp-admin-theme-color-darker-20:#3a4f41}.edd-onboarding{margin-top:80px}.edd-onboarding__logo img{display:block;width:300px;margin:0 auto 25px}.edd-onboarding__wrapper{max-width:1000px;margin:0 auto;position:relative}@media only screen and (max-width:1280px){.edd-onboarding__wrapper{max-width:850px}}.edd-onboarding__loading{z-index:99;position:fixed;left:0;top:0;width:100%;height:100%;padding-left:80px;padding-top:8px;display:flex;gap:20px;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center}.edd-onboarding__loading:before{position:absolute;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:35px;height:35px;transform:translate3d(-50%,-50%,0);will-change:transform}.edd-onboarding__loading .edd-onboarding__loading-status{display:block;text-align:center;color:#000;flex-basis:100%;margin-top:80px}@media only screen and (max-width:600px){.edd-onboarding__loading{padding-left:0}}.edd-onboarding__loading-in-progress .edd-onboarding__single-step,.edd-onboarding__loading-in-progress .edd-onboarding__welcome-screen{position:relative}.edd-onboarding__loading-in-progress .edd-onboarding__single-step:before,.edd-onboarding__loading-in-progress .edd-onboarding__welcome-screen:before{content:"";position:absolute;left:0;top:0;width:100%;height:100%;background:hsla(0,0%,100%,.85);z-index:95}.edd-onboarding__steps{margin-top:25px}.edd-onboarding__steps ul{display:flex;gap:15px;position:relative}.edd-onboarding__steps ul:before{position:absolute;top:16px;left:50%;transform:translateX(-50%);width:80%;height:2px;background:#dedfe0;content:"";z-index:-1}.edd-onboarding__steps ul li{flex:1;text-align:center}.edd-onboarding__steps ul li a{display:block;padding:5px 10px;color:#8a8e92;text-align:center;font-size:12px;text-decoration:none}.edd-onboarding__steps ul li a span{color:#fff;width:25px;height:25px;line-height:25px;font-size:12px;font-weight:400;border-radius:50%;background:#c2c4c6;display:inline-block;text-align:center;margin-bottom:10px;position:relative;box-shadow:none}.edd-onboarding__steps ul li a span:before{right:-8px}.edd-onboarding__steps ul li a span:after,.edd-onboarding__steps ul li a span:before{position:absolute;top:50%;transform:translateY(-50%);width:8px;height:10px;background:#f0f0f1;content:"";z-index:-1}.edd-onboarding__steps ul li a span:after{left:-8px}.edd-onboarding__steps ul li.active-step a,.edd-onboarding__steps ul li.active-step a small{color:#007cba;color:var(--wp-admin-theme-color);font-weight:500}.edd-onboarding__steps ul li.active-step a span{background:#007cba;background:var(--wp-admin-theme-color);box-shadow:0 0 4px 1px rgba(#007cba,.3);box-shadow:0 0 4px 1px rgba(var(--wp-admin-theme-color),.3)}.edd-onboarding__steps ul li.completed-step a span{width:25px;height:25px;line-height:25px;font-size:14px;background:#00ba37;box-shadow:none}.edd-onboarding__steps__name{color:#646970;display:block;font-size:11px}@media only screen and (max-width:600px){.edd-onboarding__steps ul li.completed-step a span,.edd-onboarding__steps ul li a span{width:20px;height:20px;line-height:20px}.edd-onboarding__steps__name{font-size:10px}}.edd-onboarding__current-step{position:relative}.edd-onboarding__single-step{background:#fff;border:1px solid #dedfe0;border-radius:3px;position:relative}.edd-onboarding__single-step-inner{padding:70px 140px 40px}.edd-onboarding__single-step-inner.equal{padding:70px 140px}@media only screen and (max-width:960px){.edd-onboarding__single-step-inner{padding:35px 70px 20px}.edd-onboarding__single-step-inner.equal{padding:35px 70px}}@media only screen and (max-width:600px){.edd-onboarding__single-step-inner{padding:17px 35px 10px}.edd-onboarding__single-step-inner.equal{padding:17px 35px}}.edd-onboarding__steps-indicator{opacity:.6;display:block}h1.edd-onboarding__single-step-title{font-size:24px;color:#141b38;font-weight:600}.edd-onboarding__single-step-subtitle{font-size:16px;line-height:22px;color:#141b38;font-weight:400;max-width:90%}.edd-onboarding__welcome-screen{width:100%;height:100%;background:#fff;display:flex;align-items:center}.edd-onboarding__welcome-screen h1{line-height:2rem}.edd-onboarding__welcome-screen-inner{padding:100px 80px;text-align:center}.edd-onboarding__testimonials-wrapper{display:-ms-grid;display:grid;gap:1em}.edd-onboarding__testimonial{display:flex;font-size:1rem;text-align:left;justify-content:space-between;gap:2em;align-items:center}.edd-onboarding__testimonial:not(:last-of-type){border-bottom:1px solid #dedfe0;padding-bottom:2em}.edd-onboarding__testimonial-content{flex-grow:1}.edd-onboarding__testimonial-content>span.big{font-weight:600;font-size:15px;font-style:italic}.edd-onboarding__testimonial-avatar{width:75px;height:75px;border-radius:50%;display:block}.edd-onboarding__testimonial-info{display:flex;flex-direction:column;gap:.25em}.edd-onboarding__testimonial-info>.testimonial-name{font-weight:600}.edd-onboarding__testimonial-info>.testimonial-company{font-size:10px;font-style:italic}.edd-onboarding__testimonial-info>.testimonial-stars>span{color:#ffbb38;font-size:12px;height:12px;width:12px}.edd-onboarding__welcome-screen-get-started{color:#fff!important;background:#00ba37!important;border-color:#00ba37!important;margin:1em auto!important}.edd-onboarding__welcome-screen-get-started:hover{color:#fff!important;background:#008a20!important}.edd-onboarding__plugins-list{border-top:1px solid hsla(0,0%,92.9%,.5)}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin{padding:28px 20px;border-bottom:1px solid hsla(0,0%,92.9%,.5);border-left:1px solid hsla(0,0%,92.9%,.5);border-right:1px solid hsla(0,0%,92.9%,.5);transition:all .25s ease-out}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin h3{margin-top:0;transition:all .25s ease-out}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin p{margin-bottom:0;transition:all .25s ease-out}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-control{width:100px;display:flex;justify-content:flex-end}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-control .checkbox-control{padding:0;margin:0;position:relative}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-control .checkbox-control__indicator{position:relative;top:0}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-external-link{text-decoration:none}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin .edd-onboarding__plugins-details label{display:flex;align-items:center;justify-content:space-between;gap:1em}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin.disabled,.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin.disabled:hover{background:rgba(114,178,129,.04)}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin:hover{background:rgba(#007cba,.02);background:rgba(var(--wp-admin-theme-color),.02)}.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin:hover .edd-onboarding__plugins-details h3,.edd-onboarding__plugins-list .edd-onboarding__plugins-plugin:hover .edd-onboarding__plugins-details p{color:#007cba;color:var(--wp-admin-theme-color)}.edd-onboarding__single-step-footer{border-top:1px solid #ededed;padding:35px 50px;display:flex;justify-content:space-between;align-items:center}.edd-onboarding__single-step-footer .edd-onboarding__button-back{color:#787c82;text-decoration:none;transition:all .2s ease-in-out;background:none;border:none;cursor:pointer}.edd-onboarding__single-step-footer .edd-onboarding__button-back:hover{color:#007cba;color:var(--wp-admin-theme-color)}.edd-onboarding__single-step-footer .edd-onboarding__button-skip-step{opacity:.6}@media only screen and (max-width:600px){.edd-onboarding__single-step-footer{padding:17px 25px;flex-wrap:wrap;gap:5px}}.edd-onboarding__close-and-exit{text-align:center;margin-top:20px}.edd-onboarding__close-and-exit button.button-link{color:#8a8e92!important;text-decoration:none!important}@media only screen and (max-width:782px){.edd-form-group__control{display:flex;align-items:center;gap:10px}}.edd-onboarding input:not([type=checkbox]):not([type=radio]){border:1px solid #dedfe0!important;border-radius:2px!important;padding:2px 8px!important;width:100%}.edd-onboarding .quicktags-toolbar input.ed_button{width:auto}.edd-onboarding .edd-check-wrapper{display:flex;align-items:center}.wp-core-ui .edd-onboarding select{font-size:14px;line-height:2;border-color:#dedfe0;box-shadow:none;border-radius:2px;padding:0 24px 0 8px;min-height:30px;max-width:25rem;-webkit-appearance:none;background-size:16px 16px;cursor:pointer;vertical-align:middle}.edd-onboarding .form-table th{vertical-align:middle}.edd-onboarding .form-table,.edd-onboarding .form-table td,.edd-onboarding .form-table td p{color:#8a8e92;font-size:13px;line-height:18px}.edd-onboarding .form-table th,.edd-onboarding .form-wrap label{color:#141b38;font-weight:400;text-shadow:none;vertical-align:baseline}.edd-onboarding td[colspan="2"]{padding:0}.edd-onboarding__stripe-features-listing{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));list-style-type:none;margin-left:0;padding-left:0;margin-top:20px}.edd-onboarding__stripe-features-listing li{list-style-type:none;position:relative;padding-left:28px;color:#3c434a;font-size:12px;margin-bottom:10px;margin-right:10px}.edd-onboarding__stripe-features-listing li:before{position:absolute;top:0;left:0;content:"✓";background:#e9e4fe;color:#635bff;width:20px;height:20px;line-height:20px;text-align:center;border-radius:50%;display:inline-block}.edd-onboarding__stripe-content-holder{max-width:75%;margin:25px auto;background:rgba(241,238,250,.3);padding:50px 40px;border-radius:4px;border:1px solid rgba(241,238,250,.5)}.edd-onboarding__stripe-content-holder .edd-onboarding__stripe-content-logo{text-align:center;margin-bottom:25px;border-bottom:1px solid #ededed;padding-bottom:25px}.edd-onboarding__stripe-content-holder .edd-onboarding__stripe-content-logo img{max-width:180px}.edd-onboarding__stripe-content-holder .edd-onboarding__stripe-content-logo span{text-align:center;font-size:13px;line-height:20px;display:block;max-width:300px;margin:0 auto}@media only screen and (max-width:960px){.edd-onboarding__stripe-content-holder{padding:25px 20px;max-width:100%}}.edd-onboarding__button-stripe{display:block;text-align:center;margin-top:20px}#edds-stripe-disconnect-reconnect{margin-top:10px}.edd-onboarding__stripe-features-title{display:block;text-align:center;font-size:16px;margin-bottom:10px;color:#625bff;font-weight:500}.edd-onboarding__stripe-additional-text{text-align:center;font-size:11px;line-height:14px;display:block;max-width:400px;margin:30px auto 0;opacity:.6}.checkbox-control{position:relative;padding-left:30px;margin-bottom:15px;cursor:pointer;font-size:18px}.checkbox-control input{position:absolute;z-index:-1;opacity:0}.checkbox-control__indicator{position:absolute;top:2px;left:0;height:25px;width:25px;background:#f0f0f1;border-radius:3px}.checkbox-control:hover input~.checkbox-control__indicator,.checkbox-control input:focus~.checkbox-control__indicator{background:#eaeaec}.checkbox-control:hover input:not([disabled]):checked~.checkbox-control__indicator,.checkbox-control input:checked:focus~.checkbox-control__indicator,.checkbox-control input:checked~.checkbox-control__indicator{background:#007cba;background:var(--wp-admin-theme-color)}.checkbox-control input:disabled~.checkbox-control__indicator{background:#00ba37;pointer-events:none}.checkbox-control__indicator:after{content:"";position:absolute;display:none}.checkbox-control input:checked~.checkbox-control__indicator:after{display:block}.checkbox-control--checkbox .checkbox-control__indicator:after{left:9px;top:4px;width:5px;height:12px;border:solid #fff;border-width:0 2.5px 2.5px 0;transform:rotate(40deg)}.checkbox-control--checkbox input:disabled~.checkbox-control__indicator:after{border-color:#fff}.checkbox-control.small-checkbox{padding-left:24px;margin-bottom:10px;font-size:13px}.small-checkbox .checkbox-control__indicator{top:0;left:0;height:17px;width:17px;background:#eaeaec}.checkbox-control.small-checkbox:hover input~.checkbox-control__indicator,.checkbox-control.small-checkbox input:focus~.checkbox-control__indicator{background:#dedfe0}.checkbox-control.small-checkbox:hover input:not([disabled]):checked~.checkbox-control__indicator,.checkbox-control.small-checkbox input:checked:focus~.checkbox-control__indicator,.checkbox-control.small-checkbox input:checked~.checkbox-control__indicator{background:#007cba;background:var(--wp-admin-theme-color)}.checkbox-control.small-checkbox input:disabled~.checkbox-control__indicator{background:#72b281}.checkbox-control--checkbox.small-checkbox .checkbox-control__indicator:after{left:6px;top:2.5px;width:3px;height:8px;border:solid #f6f7f7;border-width:0 2px 2px 0;transform:rotate(40deg)}.edd-onboarding__get-suggestions-section{margin-top:30px;text-align:center;padding:50px 50px 40px;background:rgba(12,93,149,.04);border-radius:2px;border:1px solid rgba(12,93,149,.06)}.edd-onboarding__get-suggestions-section h3{margin-top:0;line-height:25px}.edd-onboarding__get-suggestions-section .edd-onboarding__get-suggestions-section_label{display:block;margin-bottom:1em}.edd-onboarding__selected-plugins{text-align:center;margin-top:25px}.edd-onboarding__install-success-wrapper{z-index:99;position:fixed;left:0;top:0;width:100%;height:100%;padding-left:80px;padding-top:8px;display:flex;align-items:center;justify-content:center;font-size:21px}.edd-onboarding__install-success-wrapper .edd-onboarding__install-success{display:flex;flex-wrap:wrap;gap:25px;text-align:center}.edd-onboarding__install-success-wrapper .edd-onboarding__install-success span{display:block;flex-basis:100%}.edd-onboarding__install-success-wrapper .edd-onboarding__install-success .emoji{font-size:65px}@media only screen and (max-width:960px){.edd-onboarding__install-success-wrapper{padding-left:0}}.edd-onboarding__product-files-row td,.edd-onboarding__product-pricing-row td{padding:0}.edd-onboarding__product-image-wrapper{display:flex;justify-content:space-between;gap:4px}.edd-onboarding__pricing-option-pill{display:flex}.edd-onboarding__pricing-option-pill button{display:inline-block;flex:1;border:1px solid #ccc;padding:10px 15px;cursor:pointer}.edd-onboarding__pricing-option-pill button:hover:not(.active){background:#dbdcdd}.edd-onboarding__pricing-option-pill .left-option{border-top-left-radius:2px;border-bottom-left-radius:2px}.edd-onboarding__pricing-option-pill .right-option{border-left:none;border-top-right-radius:2px;border-bottom-right-radius:2px}.edd-onboarding__pricing-option-pill .active{background:#007cba;background:var(--wp-admin-theme-color);border-color:#007cba;border-color:var(--wp-admin-theme-color);border-right-color:#ccc;color:#fff}.no-table-row-padding td{padding:0}.edd-onboarding__product-variable-price{display:none}.edd-onboarding__multi-option-toggle,.edd-onboarding__upload-files-toggle{display:flex;align-items:center}.edd-onboarding__multi-option-toggle span,.edd-onboarding__upload-files-toggle span{margin-left:10px}.edd-onboarding__upload-files-toggle span{color:#1d2327;font-size:1.3em;font-weight:600;display:block;margin-top:1em;margin-bottom:1em}.edd-onboarding__pricing-options-label{display:block;color:#141b38;font-weight:400;text-shadow:none;vertical-align:baseline;font-size:14px;margin-top:20px;margin-bottom:20px}.edd-add-repeatable-row{border-top:none;padding-top:8px;margin-bottom:5px}.edd-onboarding__actions{display:flex;gap:1em;justify-content:center;margin-top:2em}.edd-onboarding__actions button.edd-promo-notice-dismiss{margin:0} \ No newline at end of file diff --git a/assets/css/edd-admin-pass-handler-rtl.min.css b/assets/css/edd-admin-pass-handler-rtl.min.css new file mode 100644 index 00000000000..5a4a10cd8cf --- /dev/null +++ b/assets/css/edd-admin-pass-handler-rtl.min.css @@ -0,0 +1 @@ +.edd-pass-handler__description{display:-ms-grid;display:grid;gap:1em;margin-bottom:1em}.edd-pass-handler__control{display:flex;gap:4px;flex-wrap:wrap}.edd-pass-handler__control>input{max-width:250px!important}.edd-pass-handler__control+.notice{max-width:400px;margin-top:1em}.edd-pass-handler__control .button{margin:0}.edd-pass-handler__loading{display:flex;align-items:center;gap:.5em}.edd-pass-handler__loading:before{background:none;display:block;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:12px;height:12px;transform:translate3d(50%,-50%,0);will-change:transform}.edd-pass-handler__verifying-wrap{display:flex;position:fixed;right:36px;left:0;top:0;bottom:0;background:rgba(0,0,0,.5);justify-content:center;align-items:center;z-index:1}.edd-pass-handler__verifying-wrap p{background:#fff;border:1px solid #7e8993;border-radius:4px;padding:2em}@media only screen and (min-width:960px){.wp-admin:not(.folded) .edd-pass-handler__verifying-wrap{right:160px}}@media only screen and (max-width:782px){.edd-pass-handler__verifying-wrap{right:0}}.edd-pass-handler__verifying ul#adminmenu #menu-posts-download ul.wp-submenu li.current a:before{border-left-color:#787878}.edd-pass-handler__actions{display:flex;gap:4px} \ No newline at end of file diff --git a/assets/css/edd-admin-pass-handler.min.css b/assets/css/edd-admin-pass-handler.min.css new file mode 100644 index 00000000000..858bef3ddc6 --- /dev/null +++ b/assets/css/edd-admin-pass-handler.min.css @@ -0,0 +1 @@ +.edd-pass-handler__description{display:-ms-grid;display:grid;gap:1em;margin-bottom:1em}.edd-pass-handler__control{display:flex;gap:4px;flex-wrap:wrap}.edd-pass-handler__control>input{max-width:250px!important}.edd-pass-handler__control+.notice{max-width:400px;margin-top:1em}.edd-pass-handler__control .button{margin:0}.edd-pass-handler__loading{display:flex;align-items:center;gap:.5em}.edd-pass-handler__loading:before{background:none;display:block;animation:edd-spinning 1.5s linear infinite;animation-play-state:inherit;border:2px solid #7e8993;border-bottom-color:#f9f9f9;border-radius:100%;content:"";width:12px;height:12px;transform:translate3d(-50%,-50%,0);will-change:transform}.edd-pass-handler__verifying-wrap{display:flex;position:fixed;left:36px;right:0;top:0;bottom:0;background:rgba(0,0,0,.5);justify-content:center;align-items:center;z-index:1}.edd-pass-handler__verifying-wrap p{background:#fff;border:1px solid #7e8993;border-radius:4px;padding:2em}@media only screen and (min-width:960px){.wp-admin:not(.folded) .edd-pass-handler__verifying-wrap{left:160px}}@media only screen and (max-width:782px){.edd-pass-handler__verifying-wrap{left:0}}.edd-pass-handler__verifying ul#adminmenu #menu-posts-download ul.wp-submenu li.current a:before{border-right-color:#787878}.edd-pass-handler__actions{display:flex;gap:4px} \ No newline at end of file diff --git a/assets/css/edd-admin-rtl.min.css b/assets/css/edd-admin-rtl.min.css new file mode 100644 index 00000000000..c92e70a6d10 --- /dev/null +++ b/assets/css/edd-admin-rtl.min.css @@ -0,0 +1 @@ +.edd-custom-price-option-sections-wrap{display:none;border:1px solid #c3c4c7;border-top:0 solid #c3c4c7;box-sizing:border-box;width:100%}.edd-custom-price-option-section{display:block;padding:10px 8px;border-bottom:1px solid hsla(0,0%,87.1%,.3)}.edd-custom-price-option-section-title{display:block;font-weight:600;padding:0 0 10px}.edd-custom-price-option-section-content{display:flex;gap:12px;margin-bottom:6px}.edd-custom-price-option-section:last-child{border-bottom:none}.toggle-custom-price-option-section{color:#787c82}.toggle-custom-price-option-section:hover{color:#537994}#edd_product_settings .edd-product-options__title,#edd_product_settings .inside strong{border-top:1px solid #c3c4c7;border-bottom:1px solid #c3c4c7;background-color:#f9f9f9;display:flex;font-weight:600;margin:0 -12px 16px;padding:8px 12px;justify-content:space-between;align-items:center}#edd_product_settings .edd-product-options-wrapper:first-of-type .edd-product-options__title,#edd_product_settings .inside div:first-child strong{margin-top:-8px}#edd_product_settings .edd-product-options__title .edd-help-tip,#edd_product_settings .inside strong .edd-help-tip{font-size:20px}#edd_product_settings .label--block{display:block;margin:0 0 4px}.edd_repeatable_row.ui-sortable-placeholder{line-height:0;padding:0;margin:0;box-sizing:border-box;border:1px dashed #c3c4c7;visibility:visible!important}.edd-add-repeatable-row{border-top:1px solid #c3c4c7;padding:12px;margin:15px -12px -12px;display:flex;justify-content:flex-end;align-items:center}.edd_repeatable_row input[type=text].large-text{width:100%}.edd_repeatable_upload_wrapper:not(:first-child),.edd_variable_prices_wrapper:not(:first-child){margin-top:12px}.edd_repeatable_row.ui-sortable-helper .edd-repeatable-row-actions .edd-remove-row{display:none}.edd-repeatable-row-actions{color:#787c82}.edd-repeatable-row-actions a{text-decoration:none;width:auto;cursor:pointer}.edd-bundle-products-header,.edd-repeatable-row-header{clear:both;background:#f6f7f7;border:1px solid #c3c4c7;display:flex;justify-content:space-between}.edd-repeatable-row-header{cursor:move}.edd_repeatable_row:hover .edd-repeatable-row-header,.edd_repeatable_row:hover .edd-repeatable-row-standard-fields{border-color:#c3c4c7}.edd-bundled-product-row:after,.edd-bundled-product-row:before,.edd-repeatable-row-header:after,.edd-repeatable-row-header:before{content:"";display:table}.edd-bundled-product-row:after,.edd-repeatable-row-header:after{clear:both}.edd-bundle-products-header,.edd-repeatable-row-title{font-weight:600}.edd-bundle-products-header,.edd-repeatable-row-actions,.edd-repeatable-row-title{padding:8px;box-sizing:border-box}.edd-repeatable-row-actions{flex-grow:1;text-align:left}.edd-bundled-product-row .edd-remove-row,.edd-repeatable-row-actions .edd-remove-row{width:auto;cursor:pointer}.edd-bundled-product-row,.edd-repeatable-row-standard-fields{padding:8px;border:1px solid #c3c4c7;border-top:0 solid #c3c4c7;display:flex;justify-content:space-between;align-items:center;gap:18px}.edd-bundled-product-row .edd-form-group,.edd-repeatable-row-standard-fields .edd-form-group{margin-bottom:0;display:inline-flex;flex-direction:column;flex-grow:1;justify-content:space-between}.edd-repeatable-row-setting-label .edd-help-tip{display:inline-block;margin-right:4px}.edd-bundled-product-item-reorder{min-width:30px}.edd-bundled-product-item-reorder .edd-product-file-reorder{font-size:20px;cursor:move;color:#dcdcde;font-family:dashicons;content:"";transition:color .2s}.edd-bundled-product-item-reorder .edd-product-file-reorder:hover{color:#a7aaad}.edd-bundled-product-actions{-ms-grid-row-align:center;align-self:center}#edd_products .edd-select,.edd_repeatable_product_wrapper .edd-select,.edd_repeatable_upload_wrapper .pricing select{min-width:100%;max-width:200px}.edd_repeatable_product_wrapper td{overflow:visible}@media screen and (max-width:480px){.edd-bundle-products-header,.edd-bundled-product-row,.edd-repeatable-row-header,.edd-repeatable-row-standard-fields{flex-wrap:wrap}.edd-bundled-product-row .edd-form-group,.edd-repeatable-row-standard-fields .edd-form-group{margin-right:0!important;margin-bottom:24px}}.edd_remove_repeatable{border:none;cursor:pointer;display:inline-block;padding:0;overflow:hidden;margin:8px 0 0;text-indent:-9999px;width:10px;height:10px}.edd_remove_repeatable:active,.edd_remove_repeatable:focus,.edd_remove_repeatable:hover{background-position:-10px 0!important}.edd_repeatable_upload_wrapper .edd_repeatable_upload_field_container{position:relative;width:100%}.edd_repeatable_upload_wrapper .edd_repeatable_upload_field_container+span:first-child{width:100%}.edd_repeatable_upload_field{padding-left:32px}.edd_upload_file button{background:#f6f7f7;border:none;border-right:1px solid #c3c4c7;padding:0 4px;position:absolute;height:calc(100% - 4px);overflow:hidden;top:2px;left:2px;display:inline-flex;justify-content:center;align-items:center}#edd-duplicate-action~#publishing-action{position:relative;top:-10px}.edd-form-group{margin-bottom:16px}.edd-form-group:last-of-type{margin-bottom:0}.edd-form-group>label,.edd-form-group__label{display:block;font-weight:600;margin-bottom:8px;padding:0}.edd-form-group__control{margin-bottom:12px;max-width:100%}.edd-form-group__control.is-check,.edd-form-group__control.is-radio{margin-top:4px}.edd-form-group__control:last-of-type{margin-bottom:0}.edd-form-group__control--is-inline{display:inline-flex;align-items:flex-end}.edd-form-group__input{max-width:100%}.edd-form-group__input[type=checkbox],.edd-form-group__input[type=radio]{margin-top:0}.edd-form-group__input[type=checkbox]+label,.edd-form-group__input[type=radio]+label{display:unset}select.edd-form-group__input{max-width:100%}.edd-form-group__help{color:#646970;font-size:13px;font-style:italic;line-height:normal;margin:8px 0 0}.edd-form-row{display:flex;flex-wrap:wrap;gap:12px}.edd-form-row__column{display:inline-flex;flex-direction:column;justify-content:flex-end}.edd-form-row__column.edd-form-group{margin-bottom:0}.edd-form-row label,.edd-form-row label.edd-form-group__label{margin-bottom:8px}#edd-migration-progress .dashicons-minus{color:#949494}#edd-migration-progress .dashicons-yes{color:green}#edd-migration-progress .dashicons-update:before{animation:rotation 2s linear infinite;display:block}#edd-v3-migration-remove-legacy-data-submit-wrap{display:flex;align-items:center;gap:6px}#edd-v3-migration-remove-legacy-data-submit-wrap .button{margin:0}#edd-filters{padding:10px;margin:0;display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px}#edd-filters .filter-items{flex-wrap:wrap;gap:6px;float:none;flex-grow:1}#edd-filters .filter-items,#edd-filters .filter-items .graph-option-section{display:flex;align-items:center}#edd-filters .filter-items .edd-date-range-picker[data-range=other] .edd-graphs-date-options{border-top-left-radius:4px;border-bottom-left-radius:4px}#edd-filters .filter-items .edd-date-range-picker[data-range=other] .edd-date-range-dates,#edd-filters .filter-items .edd-date-range-picker[data-range=other] .edd-date-range-relative-dates{display:none}#edd-filters .filter-items .edd-date-range-options{display:inline-block;margin:10px 0}#edd-filters .filter-items .edd-graphs-date-options{border-top-left-radius:0;border-bottom-left-radius:0}#edd-filters .filter-items .edd-date-range-dates{display:flex;align-items:center;border:1px solid #8c8f94;border-right:none;color:#2c3338;padding:4px 10px;margin-right:-5px;border-top-left-radius:4px;border-bottom-left-radius:4px;cursor:pointer;gap:4px}#edd-filters .filter-items .edd-date-range-dates.hidden{display:none}#edd-filters .filter-items .edd-date-range-selected-date{display:inline-block}#edd-filters .filter-items .edd-date-range-relative-dates{display:flex;align-items:center;margin-right:10px}#edd-filters .filter-items .edd-date-range-relative-dates.hidden{display:none}#edd-filters .filter-items .edd-date-range-selected-relative-date{position:relative;display:flex;align-items:center;border:1px solid #8c8f94;padding:4px 6px 4px 2px;color:#2c3338;margin-right:10px;margin-left:10px;border-radius:4px;cursor:pointer}#edd-filters .filter-items .edd-date-range-selected-relative-date .arrow-down{width:16px;height:auto;margin-right:6px;margin-top:2px;vertical-align:middle}#edd-filters .filter-items .edd-date-range-selected-relative-date.opened .edd-date-range-relative-dropdown{display:block}#edd-filters .filter-items .edd-date-range-relative-dropdown{position:absolute;z-index:99;width:420px;right:50%;top:100%;margin-top:10px;transform:translateX(50%);background-color:#fff;border:1px solid #8c8f94;border-radius:4px;box-shadow:0 2px 5px 0 rgba(0,0,0,.25);display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown:after{height:10px;width:10px;position:absolute;content:"";background:#fff;border-color:#8c8f94;border-style:solid;border-width:0 0 1px 1px;transform:rotate(135deg);top:-6px;right:calc(50% - 4px)}#edd-filters .filter-items .edd-date-range-relative-dropdown .spinner{display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown.loading{padding:10px;text-align:center}#edd-filters .filter-items .edd-date-range-relative-dropdown.loading .spinner{display:inline-block;visibility:visible;margin:0;float:unset}#edd-filters .filter-items .edd-date-range-relative-dropdown.loading :not(.spinner){display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li{display:flex;align-items:center;padding:2px 10px;opacity:.85;gap:20px}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li.active,#edd-filters .filter-items .edd-date-range-relative-dropdown ul li:hover{cursor:pointer;color:var(--wp-admin-theme-color);opacity:1}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li .date-range-name{width:110px}@media screen and (max-width:950px){#edd-filters .filter-items .graph-option-section{margin-top:8px;width:100%}#edd-filters .filter-items .edd-date-range-picker{flex-wrap:wrap}#edd-filters .filter-items .edd-graphs-date-options{width:100%;max-width:100%;min-height:40px;font-size:14px;border-top-left-radius:4px;border-bottom-left-radius:4px}#edd-filters .filter-items .edd-date-range-dates{width:100%;margin-top:10px;border:1px solid #8c8f94;margin-right:unset;border-radius:4px;font-size:14px;padding:8px 8px 8px 6px}#edd-filters .filter-items .edd-date-range-relative-dates{width:100%;flex-wrap:wrap;margin-right:0;margin-top:6px}#edd-filters .filter-items .edd-date-range-selected-relative-date{width:100%;margin-top:8px;margin-right:0;margin-left:0;font-size:14px;padding:8px 8px 8px 6px;flex-wrap:wrap}#edd-filters .filter-items .edd-date-range-selected-relative-date .arrow-down{margin-right:auto}#edd-filters .filter-items .edd-date-range-relative-dropdown{position:relative;width:100%;right:0;top:0;transform:unset;box-shadow:unset;border:unset;margin:0}#edd-filters .filter-items .edd-date-range-relative-dropdown:after{display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown ul{margin-bottom:0}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li{padding-right:0;padding-left:0;justify-content:space-between;flex-wrap:wrap;gap:unset}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li .date-range-dates,#edd-filters .filter-items .edd-date-range-relative-dropdown ul li .date-range-name{width:100%}}#edd-filters>p{color:#757575}#edd-filters input[type=number],#edd-filters input[type=text].edd_datepicker{max-width:105px}#edd-filters .button-secondary,#edd-filters input[type=number]{margin-bottom:0}#edd-filters .search-form{margin:0}@media screen and (max-width:480px){#edd-filters span{margin:2px 0}}#edd-advanced-filters{position:relative}#edd-advanced-filters .inside{z-index:99;position:absolute;top:29px;left:0;border:1px solid #e0e0e0;padding:0;background:#fff;box-shadow:0 3px 5px rgba(0,0,0,.2);min-width:285px;opacity:0;visibility:hidden}#edd-advanced-filters fieldset{display:block;padding:10px 15px 15px;margin:10px 0}#edd-advanced-filters fieldset:not(:last-of-type){border-bottom:1px solid #e0e0e0}#edd-advanced-filters fieldset:last-of-type{padding-bottom:5px}#edd-advanced-filters fieldset.edd-add-on-filters div,#edd-advanced-filters fieldset.edd-add-on-filters label,#edd-advanced-filters fieldset.edd-add-on-filters p,#edd-advanced-filters fieldset.edd-add-on-filters span{display:block;margin-bottom:2px}#edd-advanced-filters div.edd-select-chosen:not(:last-child){margin-bottom:10px}#edd-advanced-filters.open .edd-advanced-filters-button{background:#e0e0e0;border-color:#949494;box-shadow:inset 0 2px 5px -3px rgba(0,0,0,.5);transform:translateY(1px)}#edd-advanced-filters.open .inside{visibility:visible;opacity:1;transition:opacity .2s ease-in}.download_page_edd-reports #edd-filters{margin-bottom:-1px;box-shadow:none}@media screen and (max-width:782px){.download_page_edd-reports #edd-filters{gap:0}}.edd-old-log-filters{margin-top:-30px;margin-right:2px}@media screen and (min-width:600px){#edd-reports-charts-wrap{display:-ms-grid;display:grid;-ms-grid-columns:(minmax(200px,50%))[2];grid-template-columns:repeat(2,minmax(200px,50%));grid-gap:20px}.edd-reports-chart{margin-bottom:0}.edd-reports-chart-bar,.edd-reports-chart-line{-ms-grid-column:1;-ms-grid-column-span:2;grid-column:1/span 2}}.edd-reports-chart{height:500px;max-height:700px}.chart-timezone{font-size:.75rem;color:#ccc}.edd-mobile-link{line-height:32px}.edd-mobile-link a{text-decoration:none}.edd-mobile-link a:after,.edd-mobile-link a:before{display:inline-block;-webkit-font-smoothing:antialiased;font:normal 20px/30px dashicons;vertical-align:top;margin:1px 0 0;padding:0}.edd-mobile-link a:before{content:"";color:#757575;margin-left:-3px}.edd-mobile-link a:after{content:""}#edd-reports-tiles-wrap #dashboard-widgets .sortable-placeholder{padding:0;margin:0 0 20px;line-height:0;box-sizing:border-box;height:110px}#edd-reports-tiles-wrap #dashboard-widgets #primary-sortables{margin-right:0}#edd-reports-tiles-wrap #dashboard-widgets #tertiary-sortables{margin-left:0}#edd-reports-tiles-wrap{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));grid-gap:20px}.edd-reports-tile{text-align:center;padding:20px 10px 35px;display:flex;flex-direction:column;justify-content:center;border:1px solid #e5e5e5;background:#fafafa;position:relative;box-sizing:border-box;gap:.5em}.edd-reports-tile>span:not(.tile-compare){width:100%}.edd-reports-tile .tile-label{text-align:center;text-transform:uppercase;font-size:12px;font-weight:400;color:#101517}.edd-reports-tile .tile-value{color:#333;font-size:2em;line-height:1;transition:all .2s ease-in-out;display:flex;justify-content:center;flex-direction:column;gap:.25em}.edd-reports-tile:hover{border:1px solid #aaa}.edd-reports-tile:hover .tile-value:not(.tile-no-data){transform:scale(1.05)}.edd-reports-tile .tile-amount{color:#2794da}.edd-reports-tile .tile-number{color:#96f}.edd-reports-tile .tile-amount,.edd-reports-tile .tile-number{color:#fff}.edd-reports-tile .tile-value.tile-no-data{color:#ddd}.edd-reports-tile .tile-value.tile-url{font-size:1.5em}.edd-reports-tile .tile-relative{font-size:12px;font-weight:400;color:#888}.edd-reports-tile span.dashicons{display:inline-block;font-size:30px;line-height:20px;height:20px;width:20px;position:relative;top:4px;right:-5px;margin-right:-5px;color:#999}.edd-reports-tile .tile-relative span.dashicons{top:-5px;right:-3px;margin-right:0}.edd-reports-tile .tile-relative span.dashicons-arrow-down,.edd-reports-tile .tile-relative span.dashicons-arrow-up.reverse{color:#d63638}.edd-reports-tile .tile-relative span.dashicons-arrow-down.reverse,.edd-reports-tile .tile-relative span.dashicons-arrow-up{color:#008a20}.edd-reports-tile .tile-compare{position:absolute;left:0;bottom:0;color:#aaa;font-size:11px;line-height:1em;background-color:#fff;border-color:#e5e5e5 #e5e5e5 #fff #fff;border-style:solid;border-width:1px;border-top-right-radius:8px;padding:4px 9px 0 0;margin:0 0 -1px -1px}.edd-reports-tile:hover .tile-compare{border-right:1px solid #bbb;border-top:1px solid #bbb;color:#777}#edd-submit-refund-status{text-align:center;font-size:1.2em}#edd-submit-refund-status .edd-submit-refund-message:before{font-family:dashicons;font-size:1.5em;vertical-align:middle;color:#fff;border-radius:16px;margin:5px}#edd-submit-refund-status .edd-submit-refund-message.success:before{content:"";background-color:#008a20;padding-left:1px}#edd-submit-refund-status .edd-submit-refund-message.fail{display:block;margin-bottom:16px}#edd-submit-refund-status .edd-submit-refund-message.fail:before{content:"";background-color:#d63638}.refund-items td,.refund-items th.check-column{vertical-align:baseline}.refund-items .column-amount,.refund-items .column-discount,.refund-items .column-quantity,.refund-items .column-subtotal,.refund-items .column-tax,.refund-items .column-total{width:80px}.refund-items .edd-form-group__control{display:flex;align-items:center}.refund-items .edd-form-group__control input,.refund-items .edd-form-group__control select{background-color:transparent;border:0;border-bottom:1px solid;border-radius:0;box-shadow:none;text-align:left;width:100%}.refund-items .edd-form-group__control input:disabled,.refund-items .edd-form-group__control select:disabled{border-bottom:none}.refund-items .edd-form-group__control input:focus,.refund-items .edd-form-group__control select:focus{border-bottom:1px solid var(--wp-admin-theme-color-darker-10);box-shadow:0 1px 0 var(--wp-admin-theme-color-darker-10)}.refund-items .edd-form-group__control select[data-original="1"]{background:transparent}.refund-items .edd-form-group__control .is-before+span>input,.refund-items .edd-form-group__control select{text-align:right}.refund-items .edd-refund-submit-line-total{background-color:#fff!important}.refund-items .edd-refund-submit-line-total td{text-align:left}.refund-items .edd-refund-submit-line-total-amount{display:inline-block;margin-right:20px;text-align:right;width:80px}.refund-items #edd-refund-submit-subtotal td{border-top:2px solid #c3c4c7}@media screen and (max-width:782px){.refund-items td.column-total{margin-bottom:16px}.refund-items .edd-refund-submit-line-total-amount{padding-left:16px;width:unset}}.edd-submit-refund-actions{margin:16px 0 0}.did-refund .edd-submit-refund-actions,.did-refund .refund-items{display:none}.edd-admin-notice-top-of-page{font-size:15px;line-height:1.4;color:#fff;margin-right:-20px;padding:12px 20px 12px 32px;background:#2d6ca2}.edd-admin-notice-top-of-page.edd-pro-inactive{background:#d63638}@media screen and (min-width:783px){.edd-admin-notice-top-of-page{padding:10px 22px 10px 46px}}@media screen and (min-width:961px){.edd-admin-notice-top-of-page{text-align:center}}.edd-admin-notice-top-of-page a{color:#fff}.edd-admin-notice-top-of-page a:hover{text-decoration:none}.edd-admin-notice-top-of-page .button-link{position:absolute;top:48px;left:-1px;font-size:20px;color:#fff;font-weight:700;text-decoration:none;margin-right:5px;padding:6px 10px}.edd-admin-notice-top-of-page .button-link:active,.edd-admin-notice-top-of-page .button-link:focus,.edd-admin-notice-top-of-page .button-link:hover{color:#fff;text-decoration:none}@media screen and (min-width:601px){.edd-admin-notice-top-of-page .button-link{top:1px}}@media screen and (min-width:783px){.edd-admin-notice-top-of-page .button-link{left:9px}}#edd-admin-notice-five-star-review{display:-ms-grid;display:grid}#edd_dashboard_sales .edd-promo-notice{border-bottom:1px solid #c3c4c7}.edd-review-actions{display:flex;gap:6px;margin:0 0 16px}.edd-promo-notice .edd-peeking{align-self:flex-end;justify-self:flex-end;margin-left:16px;margin-bottom:-1px}@media screen and (max-width:782px){#edd-admin-notice-five-star-review.notice .edd-peeking{margin-bottom:-6px}}@media screen and (min-width:480px){.edd-promo-notice.notice-info .edd-peeking{justify-self:flex-start;margin-left:0;margin-right:250px}}.edd-promo-notice .edd-peeking,.edd-review-step{-ms-grid-row:1;grid-area:1/-1}.edd-promo-notice__overlay{display:none;position:fixed;background:rgba(16,21,23,.75);top:0;left:0;bottom:0;right:160px;z-index:11;justify-content:center;align-items:center}.folded .edd-promo-notice__overlay{right:36px}@media screen and (max-width:782px){.edd-promo-notice__overlay{right:0}}.edd-admin-notice-overlay{display:none;background-color:#fff;padding:2.5em;text-align:center;max-width:650px;position:relative;flex-direction:column}.edd-promo-notice__overlay .edd-admin-notice-overlay{display:flex}.edd-admin-notice-overlay h2{line-height:1.6em;margin:0 auto;max-width:540px}.edd-admin-notice-overlay .edd-promo-notice__features{text-align:right;display:-ms-grid;display:grid;-ms-grid-columns:(auto)[3];grid-template-columns:repeat(3,auto);margin:2em auto;gap:0 1.5em}.edd-admin-notice-overlay .edd-promo-notice__features li{display:flex;gap:.5em;align-items:center}@media screen and (max-width:600px){.edd-admin-notice-overlay .edd-promo-notice__features{-ms-grid-columns:unset;grid-template-columns:unset}}.edd-admin-notice-overlay .button-primary{padding:4px 36px;margin:0 auto .5em;max-width:360px}.edd-admin-notice-overlay__link{color:#101517}.edd-admin-notice-overlay .edd-promo-notice-dismiss.button-link{position:absolute;color:#537994;text-decoration:none;font-size:2em;top:0;left:.5em}.edd-admin-notice-overlay .edd-promo-notice-dismiss.button-link:active,.edd-admin-notice-overlay .edd-promo-notice-dismiss.button-link:hover{color:#101517}@media screen and (max-width:782px){.edd-admin-notice-overlay{margin:1em}}.edd-paypal-account-status ul{margin-right:25px}.edd-paypal-account-status>li{margin-bottom:1em}.edd-paypal-account-status ul:not(.edd-paypal-webhook-events) li{margin:.25em 0}.edd-paypal-account-status .dashicons-yes{color:#008a20}.edd-paypal-account-status .dashicons-no{color:#d63638}.edd-overlay{position:fixed;z-index:1052;top:0;left:0;bottom:0;right:160px;background-color:#141b38;opacity:.5;transition:.5s}.edd-slide-in{transform:translateX(-100%)!important;-webkit-transform:translateX(-100%)!important}#edd-notifications-panel{background-color:#fff;height:100%;width:100%;max-width:570px;position:fixed;z-index:1053;top:0;left:0;bottom:0;overflow-x:hidden;transition:.5s;transform:translateX(0);-webkit-transform:translateX(0)}body.admin-bar #edd-notifications-panel{top:32px}@media screen and (max-width:600px){body.admin-bar #edd-notifications-panel{top:46px}}#edd-notifications-header{display:flex;align-items:center;padding:0 30px;color:#fff;background-color:#0c5d95}#edd-notifications-header h3{color:#fff;flex:1}#edd-notifications-header .edd-close{background:none;border:none;color:#fff;cursor:pointer}#edd-notifications-body{padding:30px}.edd-notification{display:flex;gap:20px;margin-bottom:20px}.edd-notification--icon{color:#00aa63}.edd-notification--icon.edd-notification--icon-info{color:#005ae0}.edd-notification--icon.edd-notification--icon-warning{color:#f18200}.edd-notification--icon.edd-notification--icon-error{color:#df2a4a}.edd-notification--body{flex:1}.edd-notification--header{align-items:center;display:flex;justify-content:space-between;gap:5px;margin-bottom:7px}.edd-notification--title{color:#141b38;flex:1;font-size:16px;font-weight:600;margin:0}.edd-notification--date{color:#71747e;font-size:12px}.edd-notification--actions{flex-wrap:wrap;display:flex;align-items:center;gap:5px;margin-top:10px}.edd-notification--dismiss{background:none!important;border:none!important;box-shadow:none!important;color:#71747e!important;cursor:pointer;padding:0 10px;text-decoration:underline}.edd-notification--dismiss:hover{text-decoration:none}.edd-dialog{display:none}.edd-item-header-small{padding-bottom:20px;border-bottom:1px solid #e5e5e5;display:flex;justify-content:flex-start;align-items:center;gap:6px}.edd-item-header-small span{font-weight:600;font-size:15px}.edd-admin-order-status-badge,.edd-status-badge{padding:2px 7px;border-radius:4px;background:#ececec;display:inline-flex;align-items:center;gap:2px}.edd-admin-order-status-badge__icon,.edd-status-badge__icon{opacity:.8}.edd-admin-order-status-badge--error,.edd-admin-order-status-badge--expired,.edd-admin-order-status-badge--failed,.edd-admin-order-status-badge--failing,.edd-admin-order-status-badge--red,.edd-admin-order-status-badge--rejected,.edd-admin-order-status-badge--revoked,.edd-status-badge--error,.edd-status-badge--expired,.edd-status-badge--failed,.edd-status-badge--failing,.edd-status-badge--red,.edd-status-badge--rejected,.edd-status-badge--revoked{color:#ac3d3d;background:#ffd6d6}.edd-admin-order-status-badge--active,.edd-admin-order-status-badge--approved,.edd-admin-order-status-badge--complete,.edd-admin-order-status-badge--completed,.edd-admin-order-status-badge--edd_subscription,.edd-admin-order-status-badge--green,.edd-admin-order-status-badge--partially_refunded,.edd-admin-order-status-badge--success,.edd-status-badge--active,.edd-status-badge--approved,.edd-status-badge--complete,.edd-status-badge--completed,.edd-status-badge--edd_subscription,.edd-status-badge--green,.edd-status-badge--partially_refunded,.edd-status-badge--success{color:#017d5c;background:#e5f5f0}.edd-admin-order-status-badge--pending,.edd-admin-order-status-badge--warning,.edd-admin-order-status-badge--yellow,.edd-status-badge--pending,.edd-status-badge--warning,.edd-status-badge--yellow{color:#996800;background:#f5f2e5}.edd-admin-order-status-badge--blue,.edd-admin-order-status-badge--info,.edd-admin-order-status-badge--processing,.edd-admin-order-status-badge--trialling,.edd-status-badge--blue,.edd-status-badge--info,.edd-status-badge--processing,.edd-status-badge--trialling{color:#016087;background:#e5f1f5}.edd-pro-upgrade,.edd-pro-upgrade:hover{color:#1da867;font-weight:600;text-decoration:none}.wrap-licenses .edd-licenses__description{margin:2em 1em}.wrap-licenses .form-table,.wrap-licenses caption,.wrap-licenses tfoot,.wrap-licenses th,.wrap-licenses thead,.wrap-licenses tr{display:block}@media screen and (min-width:600px){.wrap-licenses .form-table,.wrap-licenses caption,.wrap-licenses tfoot,.wrap-licenses th,.wrap-licenses thead,.wrap-licenses tr{display:unset}}.wrap-licenses tbody{display:-ms-grid;display:grid;gap:1em}.wrap-licenses .form-table tr{margin:0;background:#fff;border:1px solid #dcdcde;border-radius:3px;padding:0;box-sizing:border-box;display:flex;flex-direction:column;justify-content:space-between}@media screen and (min-width:600px){.wrap-licenses .form-table tr{display:-ms-grid;display:grid;-ms-grid-columns:200px 1fr;grid-template-columns:200px 1fr}}.wrap-licenses .form-table th{background:#f9f9f9;margin-bottom:2.5em;padding:1em;border-bottom:1px solid #dcdcde;width:unset}@media screen and (min-width:600px){.wrap-licenses .form-table th{border-bottom:none;margin-bottom:0;display:flex;align-items:center}}.wrap-licenses .form-table td{margin:0;padding:0;display:flex;flex-direction:column;gap:2.5em;flex-grow:1}@media screen and (min-width:600px){.wrap-licenses .form-table td{flex-direction:row;gap:unset}}.wrap-licenses .form-table td input.regular-text{margin:0;width:100%;max-width:250px}.wrap-licenses .form-table td button{margin:0}.wrap-licenses .form-table .edd-license__control{flex-grow:1;padding:0 1em;display:flex;gap:4px;align-items:center;justify-content:center}@media screen and (min-width:600px){.wrap-licenses .form-table .edd-license__control{justify-content:flex-end}}.wrap-licenses .form-table .edd-licensing__actions{display:flex;gap:4px}.wrap-licenses .edd-license-data[class*=edd-license-]{background:#f9f9f9;padding:1em;border-top:1px solid #dcdcde;margin:0;width:100%;box-sizing:border-box;display:flex;align-items:flex-end}.wrap-licenses .edd-license-data[class*=edd-license-] a{color:#444}.wrap-licenses .edd-license-data[class*=edd-license-] a:hover{text-decoration:none}@media screen and (min-width:600px){.wrap-licenses .edd-license-data[class*=edd-license-]{border-top:none;width:unset;flex-basis:100%;align-items:center}.wrap-licenses .edd-license-data[class*=edd-license-]:not(:only-child){flex:0 1 300px}}.wrap-licenses .edd-license-data.license-expires-soon-notice{background-color:#00a0d2;color:#fff;border-color:#00a0d2}.wrap-licenses .edd-license-data.edd-license-expired{background-color:#e24e4e;color:#fff;border-color:#e24e4e}.wrap-licenses .edd-license-data.edd-license-error,.wrap-licenses .edd-license-data.edd-license-invalid,.wrap-licenses .edd-license-data.edd-license-item_name_mismatch,.wrap-licenses .edd-license-data.edd-license-missing,.wrap-licenses .edd-license-data.edd-license-site_inactive{background-color:#ffebcd;border-color:#ffebcd}.wrap-licenses .edd-license-data p{font-size:13px;margin-top:0}.wrap-licenses .edd-license-data.edd-license-expired a,.wrap-licenses .edd-license-data.license-expires-soon-notice a{color:#fff}.wrap-licenses .edd-license-data.edd-license-expired a:hover,.wrap-licenses .edd-license-data.license-expires-soon-notice a:hover{text-decoration:none}.edd-settings-content{max-width:1440px}.edd-settings-color,.edd-settings-colors{display:flex;flex-wrap:wrap;gap:1em}.edd-settings-color{flex-direction:column}.edd-upload-button-wrapper{width:100%;display:flex;gap:5px}.edd-upload-button-wrapper button.edd_settings_upload_button{margin-bottom:0}#edd-payment-gateways a.button.edd-settings__button-settings{position:absolute;left:2em;min-height:unset;height:1.5em;width:1.5em;border:none;background-color:#f9f9f9}#edd-payment-gateways a.button.edd-settings__button-settings,#edd-payment-gateways a.button.edd-settings__button-settings:active,#edd-payment-gateways a.button.edd-settings__button-settings:hover{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yOSA2LjI5M2wtMS41MTUuODc1YTUuODczIDUuODczIDAgMDEwIDEuNjY0bDEuNTE1Ljg3NS0yLjE2NiAzLjc1My0xLjUxNi0uODc1YTUuODI3IDUuODI3IDAgMDEtMS40NDEuODMzdjEuNzQ5SDUuODM0di0xLjc1YTUuODI1IDUuODI1IDAgMDEtMS40NDEtLjgzMmwtMS41MTYuODc1TC43MSA5LjcwN2wxLjUxNi0uODc1YTUuODc4IDUuODc4IDAgMDEwLTEuNjY0TC43MSA2LjI5MyAyLjg3NyAyLjU0bDEuNTE2Ljg3NmE1LjgyNyA1LjgyNyAwIDAxMS40NC0uODMzVi44MzNoNC4zMzR2MS43NWE1LjgzIDUuODMgMCAwMTEuNDQuODMzbDEuNTE3LS44NzYgMi4xNjYgMy43NTN6TTggMTAuMzMzYTIuMzMzIDIuMzMzIDAgMTAwLTQuNjY2IDIuMzMzIDIuMzMzIDAgMDAwIDQuNjY2eiIgZmlsbD0iIzZCNzI4MCIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTUuMjkgNi4yOTNsLTEuNTE1Ljg3NWE1Ljg3MyA1Ljg3MyAwIDAxMCAxLjY2NGwxLjUxNS44NzUtMi4xNjYgMy43NTMtMS41MTYtLjg3NWE1LjgyNyA1LjgyNyAwIDAxLTEuNDQxLjgzM3YxLjc0OUg1LjgzNHYtMS43NWE1LjgyNSA1LjgyNSAwIDAxLTEuNDQxLS44MzJsLTEuNTE2Ljg3NUwuNzEgOS43MDdsMS41MTYtLjg3NWE1Ljg3OCA1Ljg3OCAwIDAxMC0xLjY2NEwuNzEgNi4yOTMgMi44NzcgMi41NGwxLjUxNi44NzZhNS44MjcgNS44MjcgMCAwMTEuNDQtLjgzM1YuODMzaDQuMzM0djEuNzVhNS44MyA1LjgzIDAgMDExLjQ0LjgzM2wxLjUxNy0uODc2IDIuMTY2IDMuNzUzek04IDEwLjMzM2EyLjMzMyAyLjMzMyAwIDEwMC00LjY2NiAyLjMzMyAyLjMzMyAwIDAwMCA0LjY2NnoiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjUiLz48L3N2Zz4=);background-size:1em;background-repeat:no-repeat;background-position:50%}.edd-plugin__active #edd-payment-gateways a.button.edd-settings__button-settings{display:block}.edd-settings__list--disc{list-style:disc;list-style-position:inside}.edd-hidden{display:none}.edd-clearfix:after{content:"";display:table;clear:both}.edd-notice .notice-dismiss,.edd-wrap a{text-decoration:none}.wp-core-ui .edd-delete,a.edd-delete{color:#a00}.wp-core-ui .edd-delete:hover,a.edd-delete:hover{color:red}body.post-type-download #contextual-help-link-wrap,body.post-type-download #screen-options-link-wrap{top:5px!important}body.post-type-download #screen-meta{margin:0 -20px -1px 0}#edd-header{border-top:5px solid #0c5d95;border-bottom:1px solid #c3c4c7;padding:20px 0;margin-right:-20px;background:#fff}#edd-header-wrapper{display:flex;justify-content:space-between;padding:0 20px;align-items:center}#edd-header img{display:block;max-width:300px;margin:0}.edd-header-page-title-wrap{font-size:1.75em;margin-top:-5px;margin-left:auto;padding-right:7px}.edd-header-separator{margin-top:-2px;opacity:.25}.edd-header-page-title{font-weight:400;font-size:1em;line-height:1.3em;display:inline}.edd-header-page-title-wrap .button{margin-right:5px}.no-js #edd-header-actions{display:none}#edd-header .edd-round{position:relative;background-color:#f3f4f5;border-radius:50%;width:40px;height:40px;display:flex;align-items:center;justify-content:center;margin-right:10px;cursor:pointer;transition:background-color .2s ease}button.edd-round{border:none}#edd-header button.edd-round:hover{background-color:#e5e5e5}button.edd-round:active,button.edd-round:focus{outline:2px solid #0c5d95}#edd-header .edd-number{position:absolute;background-color:#df2a4a;width:16px;height:16px;font-weight:600;font-size:10px;color:#fff;top:-8px;right:50%;transform:translateX(50%);margin:0;animation:bounce 2s 5}#edd-header .edd-number.edd-hidden{display:none!important}#edd-header .edd-round svg{width:20px;height:20px}@media screen and (max-width:840px){#edd-header img,.edd-header-separator{display:none}}.edd_datepicker{height:29px}.edd-from-to-wrapper input{width:105px;margin:0;position:relative;z-index:1}.edd-from-to-wrapper input[name*=start],.edd-from-to-wrapper input[name=filter_from]{border-top-left-radius:0;border-bottom-left-radius:0}.edd-from-to-wrapper input[name*=end],.edd-from-to-wrapper input[name=filter_to]{margin-right:-1px;border-top-right-radius:0;border-bottom-right-radius:0}.edd-from-to-wrapper input:focus{z-index:2;position:relative}.edd-settings-sub-nav{margin:0 0 10px;width:100%;border-bottom:1px solid #ccc;box-shadow:0 1px 1px rgba(0,0,0,.04)}.edd-settings-sub-nav a{padding:13px;display:block}.edd-settings-sub-nav a.current{border-bottom:4px solid #000;padding-bottom:9px}.admin-color-fresh .edd-settings-sub-nav a.current{border-bottom-color:#00a0d2}.admin-color-blue .edd-settings-sub-nav a.current{border-bottom-color:#096484}.admin-color-coffee .edd-settings-sub-nav a.current{border-bottom-color:#c7a589}.admin-color-ectoplasm .edd-settings-sub-nav a.current{border-bottom-color:#a3b745}.admin-color-midnight .edd-settings-sub-nav a.current{border-bottom-color:#e14d43}.admin-color-ocean .edd-settings-sub-nav a.current{border-bottom-color:#627c83}.admin-color-sunrise .edd-settings-sub-nav a.current{border-bottom-color:#be3631}.admin-color-light .edd-settings-sub-nav a.current{border-bottom-color:#888}.admin-color-evergreen .edd-settings-sub-nav a.current{border-bottom-color:#36533f}.admin-color-mint .edd-settings-sub-nav a.current{border-bottom-color:#4f6d59}.download_page_edd-settings .edd-check-wrapper{clear:both}.download_page_edd-settings .form-table tr>th>h3,.download_page_edd-settings .form-table tr>th>strong{font-size:1.2em;font-weight:600;margin:0 auto}.edd-sortable-list{margin:0;width:300px;position:relative}.edd-sortable-list li{margin:0;padding:0;position:relative;height:28px;cursor:move}.edd-sortable-list li label *{vertical-align:middle}.edd-sortable-list li label:after{display:block;width:17px;height:17px;position:absolute;left:6px;top:0;color:#aaa;font-family:dashicons;font-size:17px;content:"";cursor:move}.form-table .edd-sortable-list li label{display:block;height:28px;padding:0;margin:0}.edd-sortable-list .payment-icon{width:32px;height:24px;position:relative;top:-2px;margin-left:5px}.edd-help-tip{cursor:help;margin-top:-2px;font-size:24px;color:grey}.edd-ui-tooltip{position:absolute;background:#333!important;border-width:1px!important;border-radius:3px!important;box-shadow:-1px 1px 2px 1px hsla(0,0%,83.9%,.5)!important;color:#dedede!important;max-width:300px!important;padding:7px!important;text-rendering:optimizeLegibility;text-shadow:none!important;z-index:9999!important}.download_page_edd-settings .edd-settings-payment-icon-wrapper{margin-top:5px}.download_page_edd-settings .edd-settings-payment-icon-wrapper input{margin-top:1px}.download_page_edd-settings .form-table .edd-settings-payment-icon-wrapper input[type=checkbox]+label{margin:0;display:inline-block}.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-icon-image{margin-left:5px;width:32px;display:inline-block;vertical-align:middle}.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-option-name{vertical-align:middle}.download_page_edd-settings .taxrates td,.download_page_edd-settings .taxrates th{padding:8px 10px}.download_page_edd-settings .taxrates td{line-height:1.5em;vertical-align:top;margin:0}.download_page_edd-settings .taxrates .regular-text{width:100%}#TB_window{overflow:hidden}#TB_title{padding:5px}#TB_ajaxContent{width:calc(100% - 30px)!important;padding:15px;margin:0;height:calc(100% - 118px)!important}#TB_ajaxWindowTitle{font-size:18px;font-weight:600;line-height:30px}#TB_closeWindowButton{left:6px;top:6px}#choose-download-wrapper{width:100%}#choose-download-wrapper .wrap{overflow-y:scroll;margin:0;padding:0;height:calc(100% - 50px)}#choose-download-wrapper .submit-wrapper{position:absolute;width:100%;bottom:0;padding:0;margin:0 -15px 0 0;text-align:left}#choose-download-wrapper .submit-wrapper div{background-color:#fafafa;padding:15px;border-top:1px solid #ddd}.wp-media-buttons .button.edd-thickbox{padding-right:0}.wp-media-buttons .button.edd-email-tags-inserter .dashicons{margin-top:-2px}.download_page_edd-payment-history .edit-post-editor-regions__header{flex-shrink:0;height:auto;border-bottom:1px solid #e2e4e7;z-index:30;position:sticky;top:32px;margin-right:-20px}@media screen and (max-width:782px){.download_page_edd-payment-history .edit-post-editor-regions__header{position:static;top:46px}}.download_page_edd-payment-history .edit-post-header{height:56px;background:#fff;display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;max-width:100%;box-sizing:border-box;padding:4px 20px}@media screen and (max-width:782px){.download_page_edd-payment-history .edit-post-header{padding-right:10px;padding-left:10px}}@media(min-width:280px){.download_page_edd-payment-history .edit-post-header{flex-wrap:nowrap}}.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar{order:0}.download_page_edd-payment-history .edit-post-header .edit-post-header__settings{order:1}.download_page_edd-payment-history .edit-post-header #publishing-action,.download_page_edd-payment-history .edit-post-header .edit-post-header__settings,.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar{display:flex;align-items:center}.download_page_edd-payment-history .edit-post-header #publishing-action .spinner{margin:0 0 0 5px}.download_page_edd-payment-history .edit-post-header .button-primary{margin:2px;height:34px;line-height:32px;font-size:13px}#edd-order-items .hndle{display:flex;align-items:center;justify-content:space-between}#edd-order-items .hndle .edd-toggle{font-weight:400}.edd-add-order-item td{vertical-align:middle}.edd-add-order-item input{width:80%}.edd-add-order-item input[readonly]{color:#555;background:none;border:1px solid transparent;box-shadow:none}.order-customer-info .customer-details-wrap{margin:15px 0;align-items:center}.order-customer-info .customer-details-wrap .spinner{margin:0}.order-customer-info .customer-details{display:flex;flex-direction:column}.order-customer-info .customer-details .customer-since{color:#666;display:block;margin:4px 0 6px}.order-customer-info .customer-details>span{margin-bottom:5px}.edd-order-add-download-select .spinner{display:none}table.edd-order-overview-summary{border-width:0;table-layout:fixed}table.edd-order-overview-summary--refund{border-width:0}@media screen and (min-width:782px){.edd-order-overview .column-right{text-align:left}}.edd-ml-auto{margin-right:auto!important}@media screen and (min-width:782px){.edd-ml-lg-auto{margin-right:auto!important}}.edd-ml-auto+.edd-ml-auto{margin-right:10px!important}.edd-order-overview-summary__items-name{align-self:flex-start}.edd-order-overview-summary__items>:nth-child(odd){background-color:#f9f9f9}@media screen and (min-width:782px){.edd-order-overview-summary__items tr:last-child td,.edd-order-overview-summary__items tr:last-child th{border-bottom:1px solid #e5e5e5}}@media screen and (max-width:782px){.edd-order-overview-summary .row-actions>*,.edd-order-overview-summary__items-name .row-actions{display:block!important}.edd-order-overview-summary .row-actions>:not(:first-child):before{display:none}}.edd-order-overview-summary th:not(.column-primary){width:100px}.edd-order-overview-summary .row-actions>:not(:first-child):before{color:#999;content:" | "}.edd-order-overview-summary .row-actions .text{color:#555}.edd-order-overview-summary .removable{display:flex;align-items:center;position:relative}.edd-order-overview-summary .removable .delete{display:inline-block;margin-left:10px;margin-right:-8px;padding:10px;border-left:1px solid #e5e5e5;color:#a00}.edd-order-overview-summary .removable .delete:hover{color:#dc3232}.edd-order-overview-summary__adjustments .column-primary{font-weight:600}.edd-order-overview-summary__adjustments td small{font-weight:400}.edd-order-overview-summary__subtotal .column-primary,.edd-order-overview-summary__tax tr:first-of-type .column-primary,.edd-order-overview-summary__total .column-primary{font-weight:600}.edd-order-overview-summary__adjustments td,.edd-order-overview-summary__subtotal td,.edd-order-overview-summary__tax td,.edd-order-overview-summary__total td{vertical-align:middle}.edd-order-overview-summary__tax td small,.edd-order-overview-summary__total td small{font-weight:400}.edd-order-overview-summary__total .total{color:#017d5c;display:inline-block}.edd-order-overview-summary__total .total.is-negative{color:#a00}@media screen and (min-width:783px){.edd-order-overview-summary__adjustments .removable .delete{margin-right:-50px}.edd-order-overview-summary__total .total{font-size:150%;padding-top:5px;padding-bottom:5px}}.edd-order-overview-summary__total tr:last-child td:not(:first-of-type),.edd-order-overview-summary__total tr:last-child th{border-top:1px solid #e5e5e5}.edd-order-overview-summary__total .notice{margin:-1px}.edd-order-overview-summary__total .notice p{font-weight:400;margin:.5em 0}.edd-order-overview-summary__refunds .column-primary{font-weight:600}.edd-order-overview-summary__refunds td small{font-weight:400}.edd-order-overview-summary__refunds tr:first-child td{border-top:1px solid #e5e5e5}#edd-order-overview-actions.inside{border-top:1px solid #ccd0d4;margin-top:0;display:flex;align-items:center;flex-wrap:wrap;justify-content:space-between}#edd-order-overview-actions.inside:empty{padding:0;border-top:0}#edd-order-overview-actions.inside>div{display:flex;align-items:center}#edd-order-overview-actions .edd-order-overview-actions__notice{flex-basis:100%;margin-top:15px}.edd-order-overview-actions .button{width:100%;margin-bottom:12px}.edd-order-overview-actions .button:last-of-type{margin-bottom:0}@media screen and (min-width:782px){.edd-order-overview-actions .button{width:auto;margin-right:12px;margin-bottom:0}.edd-order-overview-actions .button:first-of-type{margin-right:auto}}.edd-order-overview-actions__locked{font-style:italic;opacity:.8}@media screen and (max-width:782px){.edd-order-overview-actions__locked{margin-bottom:12px}}.edd-order-overview-actions__refund .dashicons{margin-left:8px}.edd-dialog .ui-button-icon-only{font-size:0}.download_page_edd-payment-history .ui-dialog,.download_page_edd-payment-history .ui-dialog-content{overflow:visible}.edd-order-overview-modal form>p{margin-top:0}.edd-order-overview-modal fieldset legend,.edd-order-overview-modal form label{display:block;margin-bottom:4px}.edd-order-overview-modal fieldset{margin-bottom:calc(1em - 3px)}.edd-order-overview-modal fieldset>p{margin:2px 0 3px}.edd-order-overview-modal form .submit{margin:0 -16px -16px;padding:16px;background:#fcfcfc;border-top:1px solid #dfdfdf;display:flex;align-items:center}.edd-order-overview-modal form .submit .spinner{margin:0}.edd-order-overview-add-item [for=auto-calculate]{display:flex;align-items:center}.edd-order-overview-add-item [for=auto-calculate] input[type=checkbox]{margin-top:0}.edd-order-overview-add-item [for=auto-calculate] .label{line-height:1.15;margin-right:8px}.edd-order-overview-add-item [for=auto-calculate] .label small{margin-top:4px;display:block;opacity:.75}.edd-order-overview-add-adjustment .notice,.edd-order-overview-add-item .notice{margin:0 0 1rem}.edd-order-overview-add-adjustment #description,.edd-order-overview-add-discount select{width:100%}.edd-order-overview-error{font-style:italic;color:#a00;display:block;margin:4px 0}.edd-order-copy-download-link textarea{width:100%}.edd-order-resend-email-chooser legend{font-weight:700;margin-bottom:4px}.edd-order-resend-email-chooser p{margin:4px 0}.edd-notes .edd-note{padding:10px;background-color:#ffe;border:1px solid #cc0;width:100%;position:relative;margin-bottom:10px;box-sizing:border-box;overflow:hidden}.edd-notes .edd-note.deleting{opacity:.5}.edd-notes .edd-note__header{display:flex;align-items:center}.edd-add-note .spinner{float:none;display:inline-block;margin:0}.edd-notes .edd-note time{font-size:11px;color:#aaa}.edd-notes .edd-note .edd-note-author{margin-left:5px}.edd-notes .edd-note .edd-delete-note{color:#a00;font-weight:700;text-decoration:none;margin-right:auto}.edd-notes .edd-note .edd-delete-note:hover{color:#888}.edd-notes .edd-note p:last-child{margin-bottom:0}.edd-notes .edd-no-notes{margin:4px 0 10px}textarea[name=edd-note]{width:100%;min-height:70px;margin-top:0}.edd-notes-wrapper{width:80%}.edd-note-pagination{float:left;margin:-35px 5px 15px}.edd-note-pagination a,.edd-note-pagination span.page-numbers{padding:5px 8px;margin:2px;text-decoration:none}.edd-note-pagination a{border:1px solid #e5e5e5;background:#fcfcfc}.edd-note-pagination a:last-child,.edd-note-pagination span.page-numbers:last-child{margin-left:0}#edd-products{height:100px;min-width:200px}#edd-add-discount input[type=text],#edd-edit-discount input[type=text]{width:300px}#edd-add-discount .edd-discount-datetime input,#edd-edit-discount .edd-discount-datetime input{vertical-align:middle}#edd-add-discount input[type=text].edd_datepicker,#edd-edit-discount input[type=text].edd_datepicker{display:inline-block;width:183px}#edd-edit-discount textarea{height:100px}.edd-amount-type-wrapper{position:relative;display:flex}.edd-amount-type-wrapper select{border-top-right-radius:0;border-bottom-right-radius:0;width:auto!important}.edd-amount-type-wrapper #edd-amount{border-top-left-radius:0;border-bottom-left-radius:0;margin-left:-2px;padding:0 8px;width:unset;max-width:125px}.edd-amount-type-wrapper input:focus{z-index:2}.post-type-download .tablenav.top .edd-select{margin-left:6px}.wp-list-table.addresses .column-primary strong,.wp-list-table.customers .column-primary strong,.wp-list-table.discounts .column-primary strong,.wp-list-table.emails .column-primary strong,.wp-list-table.orderadjustments .column-primary strong,.wp-list-table.orderitems .column-primary strong,.wp-list-table.orders .column-primary strong{font-size:14px}.wp-list-table.customers .column-primary .avatar,.wp-list-table.emails .column-customer .avatar{float:right;margin-left:10px;margin-top:1px;border-radius:5px}.wp-list-table.orders div.order-list-email{font-size:.85em;color:#888}.wp-list-table.orders th.column-amount{width:100px}.wp-list-table .row-actions span.activate a{color:green}.wp-list-table .row-actions span.refund a{color:#836fff}.wp-list-table .row-actions span.cancel a{color:#cc8c00}.wp-list-table .row-actions span.cancel a:hover,.wp-list-table .row-actions span.refund a:hover{opacity:.8}.wp-list-table .type-download .row-actions{color:#999}.no-js.edit-tags-php.post-type-download .wp-heading-inline{position:absolute;top:0}.no-js.edit-tags-php.post-type-download .nav-tab-wrapper{margin-top:50px}.download_page_edd-customers .wrap .nav-tab-wrapper .page-title-action,.download_page_edd-discounts .wrap .nav-tab-wrapper .page-title-action,.download_page_edd-payment-history .wrap .nav-tab-wrapper .page-title-action,.edit-tags-php.post-type-download .wrap .nav-tab-wrapper .page-title-action{top:3px;margin-right:10px;line-height:24px}#edd-payments-filter ul.subsubsub{margin-bottom:8px}tr.status-refunded td{background:#cecece;border-top-color:#ccc}marquee{padding:0;margin:0}@media handheld,only screen and (max-width:640px){.wp-list-table.downloads th{width:auto!important}}#edd-download-link-textarea{width:100%}.edd_files_name_label{width:225px;float:right}.edd_files_url_label{width:220px;float:right}#postbox-container-1 .edd_files_name_label,#postbox-container-1 .edd_files_url_label{width:80px}#edd_product_files .inside,#edd_product_prices .inside{margin-bottom:0}textarea#edd-payment-note{width:100%;height:4em;margin:0}#edd-order-items .row .edd-purchased-files-list-wrapper .download{line-height:1.4}#edd-order-items .edd-purchased-files-list-wrapper .edd-purchased-option{color:#666}input[class*=edd-price-field]{max-width:125px}#edd-order-download-quantity[type=number].small-text,#edd-order-download-tax[type=text].small-text,[class*=item_] [class*=edd-payment-details-download-][type=number].small-text{height:25px}#edd-order-download-quantity[type=number].small-text,.item_price .edd-payment-details-download-quantity[type=number].small-text{width:55px}#edd-order-download-tax[type=text].small-text,.item_tax .edd-payment-details-download-item-tax[type=number].small-text{width:80%;max-width:125px}#edd_product_notes_field{display:block;margin:12px 0 0;height:4em;width:100%}.edd-metabox-title-action{margin:0;float:left;padding:4px 8px;position:relative;top:-1px;text-decoration:none;border:1px solid #ccc;border-radius:2px;background:#f7f7f7;text-shadow:none;font-weight:600;font-size:10px;line-height:normal;color:#0073aa;cursor:pointer;outline:0}.edd-metabox-title-action:hover{border-color:#008ec2;background:#00a0d2;color:#fff}.edd-edit-purchase-element .tablenav{padding:2px 10px 8px}.edd-edit-purchase-element .edd-order-children-wrapper{margin:0 -1px}.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 table{border-top:none;border-bottom:none}.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 .tablenav{display:none}.edd-edit-purchase-element[class*=columns-] ul li{padding-left:1%}#edd-edit-order-form .column:nth-child(odd),#edd-edit-order-form .columns-4 .column:nth-child(odd),#edd-edit-order-form .columns-5 .column:nth-child(3n+1){margin-left:0}#edd-edit-order-form input.large-text{width:90%}.edd-edit-purchase-element ul li.item_price{width:15%}.edd-edit-purchase-element ul li.item_price.item_quantity{width:25%}.edd-edit-purchase-element ul li.item_tax{width:15%}.edd-edit-purchase-element ul li.price{width:20%}.edd-admin-box-inside{border-bottom:1px solid #f1f1f1;clear:both;padding:12px;margin:0;word-wrap:break-word}.edd-admin-box-inside--row{display:flex;flex-wrap:wrap;word-break:break-all;justify-content:space-between;align-items:center}.edd-admin-box-inside>p{margin:8px 3px}.edd-admin-box-inside .strong{font-weight:600}.edd-admin-box div:not(.edd-admin-box-inside--row) .label{display:block;margin-bottom:4px;margin-left:0}.edd-admin-box .label--has-tip{display:flex;align-items:center}.edd-admin-box .label--has-tip .edd-help-tip{margin-top:0;font-size:20px}.edd-admin-box div:not(.edd-admin-box-inside--row) .label--has-checkbox{margin-bottom:0}.edd-payment-fees .fee-label{color:#666;font-weight:400}.edd-admin-box .right{float:left}#edd-order-refunds-list{padding-right:25px}#poststuff .edd-order-data .inside{margin:0;padding:0}.edd-order-data .edd-select-chosen{width:130px!important}.edd-order-data input.edd_datepicker{width:180px}.edd-order-data input[type=number].edd-payment-time-hour,.edd-order-data input[type=number].edd-payment-time-min{width:50px}.edd-order-data .edd-tax-rate{color:#9c9c9c;font-style:italic;padding:5px}#edd_general_logs p{margin:0;padding:0}.edd-admin-box-inside span.label{margin-left:10px}#edd-order-resend-receipt .inside{margin-top:11px}.edd-order-resend-receipt-header{font-size:14px;line-height:1.4}.edd-admin-box-inside:last-child{border-bottom:0}#edd-edit-order-form .data-payment-key{word-break:break-all}.edd-order-update-box #major-publishing-actions .button-secondary{margin-left:10px}.edd-order-update-box .button-primary{margin-left:0}.edd-edit-purchase-element .edd-select-chosen{width:196px}.edd-edit-purchase-element ul{clear:both;display:block}#edd-customer-details .actions{float:left}.order-data-address h3{margin:0 0 10px}.order-data-address #edd-order-address-country-wrap,.order-data-address #edd-order-address-state-wrap{display:inline-block;width:50%;max-width:300px}.edd-order-data input.small-text{margin:0}.edd-order-data input.med-text{margin:0;width:100px}.edd-edit-purchase-element ul li{display:block;line-height:1.4;position:relative;margin:0;vertical-align:middle;font-size:13px}.edd-edit-purchase-element .row{padding:12px}.edd-edit-purchase-element .row:not(:last-child){border-bottom:1px solid #eee}.edd-edit-purchase-element .row:nth-child(odd):not(.header){background-color:#f9f9f9}.edd-edit-purchase-element .row.header{padding:6px 12px;font-weight:600;vertical-align:top}.edd-edit-purchase-element ul{margin:0 0 15px}.edd-edit-purchase-element ul:last-of-type{margin-bottom:0}#edd-order-data .data span{color:#666;font-weight:600}.edd-edit-purchase-element .inside{padding:12px}.edd-edit-purchase-element .edd-purchased-download-title{font-size:14px;font-weight:500}.edd-edit-purchase-element .edd-purchased-download-title .deleted{color:#777}.edd-edit-purchase-element .edd-purchased-download-actions{color:#777;line-height:1.4}.edd-edit-purchase-element .edd-purchased-download-actions .edd-purchased-download-actions-label{font-weight:500}.edd-edit-purchase-element .edd-purchased-download-actions a{color:#777;font-size:12px}.edd-edit-purchase-element .edd-purchased-download-actions a:hover{color:#444}.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download{color:#a00}.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download:hover{color:red}.edd-add-adjustment-to-purchase,.edd-add-download-to-purchase{padding:15px;border-top:1px solid #e5e5e5;background-color:#f5f5f5}.edd-add-adjustment-to-purchase .chosen-container,.edd-add-download-to-purchase .chosen-container{width:90%!important;max-width:220px!important}.edd-add-adjustment-to-purchase .spinner,.edd-add-download-to-purchase .spinner{margin:0;float:none}.edd-add-download-to-purchase .edd-add-order-quantity{width:40px;height:29px;vertical-align:middle}.edd-add-adjustment-to-purchase .edd-add-adjustment-button,.edd-add-adjustment-to-purchase input[type=text],.edd-add-download-to-purchase .edd-add-order-item-button{height:29px}@media screen and (max-width:1284px){.edd-edit-purchase-element .edd-purchased-download-title{font-size:16px}.edd-edit-purchase-element ul li.item_price{width:22%}.edd-edit-purchase-element ul li.item_price.item_quantity{width:35%}.edd-edit-purchase-element ul li.item_tax{width:25%}.edd-edit-purchase-element ul li.price{width:20%}.edd-edit-purchase-element .edd-purchased-download-actions{padding-top:10px}}@media screen and (max-width:1024px){.edd-edit-purchase-element ul li.item_price.item_quantity{width:40%}.edd-edit-purchase-element ul li.price{width:24%}.edd-edit-purchase-element .edd-purchased-download-actions{padding-top:15px}.edd-edit-purchase-element .edd-purchased-download-actions,.edd-edit-purchase-element .edd-purchased-download-actions a{font-size:14px}}@media screen and (max-width:782px){.edd-edit-purchase-element ul li.item_price,.edd-edit-purchase-element ul li.item_price.item_quantity{padding-bottom:10px}.edd-edit-purchase-element ul li.item_price.item_quantity{width:35%}.edd-edit-purchase-element ul li.item_tax,.edd-edit-purchase-element ul li.price{width:20%;padding-bottom:10px}.edd-payment-details-download-amount,.edd-price-currency{font-size:16px}.order-data-column input[type=email]{padding:6px 10px}.edd-refund-submit-line-total td:last-of-type{flex:0 0 120px}#edd-item-tables-wrapper .addresses tbody tr{display:-ms-grid;display:grid}#edd-item-tables-wrapper .addresses tbody td:not(.no-items){padding-right:35%}}@media screen and (max-width:600px){.edd-edit-purchase-element ul li.item_price,.edd-edit-purchase-element ul li.item_price.item_quantity,.edd-edit-purchase-element ul li.item_tax{width:100%;padding-bottom:20px}.edd-edit-purchase-element .edd-add-download-to-purchase ul li.item_tax,.edd-edit-purchase-element ul li.price{width:100%;padding-bottom:0}.edd-edit-purchase-element .edd-add-download-to-purchase-actions{padding-top:15px}}#edd_product_stats .label{display:inline-block}#edd_product_stats .product-earnings-stats:before,#edd_product_stats .product-sales-stats:before{color:#82878c;font:normal 20px/1 dashicons;display:inline-block;padding:0 0 0 2px;position:relative;top:0;right:-1px;speak:none;text-decoration:none!important;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#edd_product_stats .product-sales-stats:before{content:""}#edd_product_stats .product-earnings-stats:before{content:""}body.dashboard_page_edd-upgrades.js .postbox .hndle{cursor:default}.edd_dashboard_widget table thead td{border-bottom:1px solid #ececec;color:#777}.edd_dashboard_widget .table_left{float:right;width:45%}.edd_dashboard_widget .table_right{float:left;width:45%}.edd_dashboard_widget .inside{font-size:12px}.edd_dashboard_widget td{padding:3px 0}.edd_dashboard_widget .b,.edd_dashboard_widget .t{line-height:1.5;vertical-align:middle}.edd_dashboard_widget .b{padding-left:6px;width:auto}.edd_dashboard_widget .t{font-size:12px;padding-left:12px;color:#777;width:100%}.edd_dashboard_widget .label_heading{border-top:1px solid #ececec;color:#8f8f8f;font-family:Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;display:block;padding-top:10px;margin:0 12px 8px 0}.edd_dashboard_widget .edd_dashboard_widget_subheading{border-top:1px solid #ececec;color:#8f8f8f;font-size:14px;padding-top:10px;margin:1em 0 0}.edd_dashboard_widget .edd_dashboard_widget_subheading+.table{margin:8px 0 0}.edd_dashboard_widget .edd_price_label{background:#00769c;border-radius:3px;color:#fff;font-size:10px;padding:2px 4px;margin-left:2px}.edd_dashboard_widget table{width:100%;margin-right:0;margin-bottom:1em}td.edd_order_label{width:80%}td.edd_order_price{text-align:left}@media handheld,only screen and (max-width:1000px){.edd_dashboard_widget .edd-recent-email{display:none}}body.download_page_edd-reports{overflow-y:scroll}.edd-chip{font-size:10px;font-weight:700;text-transform:uppercase;line-height:1;padding:3px;border-radius:3px;color:#fff;background-color:#444}.edd-reports-wrapper .postbox h2,.edd-reports-wrapper .postbox h3{font-size:1.3em}#edd-dashboard-widgets-wrap .metabox-holder{padding-top:0}.edd-reports-wrapper .postbox .edd-select{max-width:200px;vertical-align:baseline;margin-left:4px;margin-bottom:16px}.download_page_edd-reports #edd-item-wrapper{margin:0}#edd-dashboard-widgets-wrap .postbox h2,#edd-dashboard-widgets-wrap .postbox h3{cursor:default}.edd-date-range-options .edd_datepicker{width:105px}.edd-report-wrap{clear:both}.edd-report-wrap h3{clear:both;margin:0 0 20px}.edd-reports-chart,.edd-reports-table{margin-bottom:20px}.edd-admin--has-grid{display:grid;display:-ms-grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));grid-gap:20px}.edd-admin--has-grid .postbox{margin-bottom:0}.edd-admin--has-grid .edd-from-to-wrapper{display:flex;margin-bottom:16px;width:100%}.edd-admin--has-grid .edd-from-to-wrapper input{width:100%}.edd-admin--has-grid .edd-from-to-wrapper span{flex-grow:1}.edd-admin--has-grid form{display:flex;flex-direction:column;flex-wrap:wrap;position:relative}fieldset.edd-to-and-from-container{display:flex;gap:8px}fieldset.edd-to-and-from-container select{flex:0 0 calc(50% - 6px)}span.edd-to-and-from--separator{line-height:normal;-ms-grid-row-align:center;align-self:center;margin-bottom:16px}.edd-admin--has-grid .postbox .edd-select{max-width:100%;margin-left:0}.edd-admin--has-grid .button.updated-message:before,.edd-admin--has-grid .button.updating-message:before{vertical-align:text-bottom;margin:0 0 0 5px}.edd-import-export-form .edd-progress{background:#ddd;border-radius:15px;height:15px;flex-basis:100%}.edd-import-export-form .edd-progress div{background:#ccc;border-radius:15px;height:100%;width:0}.edd-import-export-form .notice-wrap{background-color:#f4f4f4;border-color:#eae9e9;border-style:solid;border-width:1px 0;padding:12px;overflow:auto;margin:20px -12px -23px;position:relative;width:100%;display:flex;justify-content:space-between;align-items:center}.notice-wrap div.notice{margin:0}.admin-color-fresh .edd-import-export-form .edd-progress div{background:#0073aa}.admin-color-light .edd-import-export-form .edd-progress div{background:#888}.admin-color-blue .edd-import-export-form .edd-progress div{background:#096484}.admin-color-coffee .edd-import-export-form .edd-progress div{background:#c7a589}.admin-color-ectoplasm .edd-import-export-form .edd-progress div{background:#a3b745}.admin-color-midnight .edd-import-export-form .edd-progress div{background:#e14d43}.admin-color-sunrise .edd-import-export-form .edd-progress div{background:#dd823b}.graph-option-section{float:right}.edd-report-filters-title span{display:block;padding:20px}#edd-graphs-filter form{padding:20px}#edd-graphs-filter label{vertical-align:inherit}#edd-graphs-filter .graph-option-section{display:inline-block;line-height:2em;margin:0 0 0 5px;padding:0}.download_page_edd-reports .section-content #post-body-content{float:none}.download_page_edd-reports .section-content select[name=range]{display:none}.edd-mix-totals{background-color:#fff;border:1px solid #e5e5e5;box-shadow:0 1px 1px rgba(0,0,0,.04);padding:10px}.edd-mix-chart{display:inline-block;width:49%;vertical-align:top}.edd-graph-notes{color:#9c9c9c}.edd-graph-notes span{display:block}.edd-pie-graph .legend{display:none}.edd-pie-legend{overflow:auto;margin-top:10px}.edd-legend-item-wrapper{color:#333;display:inline-block;font-size:8pt;padding:2px 5px 0;width:48%;height:20px}.edd-legend-color{border:1px solid #cfcfcf;display:inline-block;margin-left:5px;width:20px;height:15px}.edd-pie-legend-item{display:inline-block;vertical-align:top;width:80%}#edd-reports-tiles-wrap .metabox-holder{padding:0}#edd-reports-tiles-wrap #dashboard-widgets{overflow:auto}#edd-reports-tiles-wrap #dashboard-widgets .postbox-container{width:33.3%}.download_page_edd-reports .section-content .tablenav.top{display:none}#edd_tax_rates{margin:1em 0 0}[id*=edd-recapture-].button{font-size:16px;height:auto;padding:8px 14px;margin:6px 0 0}[id*=edd-recapture-].button .dashicons{line-height:29px;margin-left:8px}[id*=edd-recapture-].button .edd-loading,[id*=edd-recapture-].button .edd-loading:after{border-radius:50%;display:inline-block;width:14px;height:14px}[id*=edd-recapture-].button .edd-loading{position:relative;top:3px;margin-right:4px;box-shadow:0 0 2px rgba(0,0,0,.2);animation:edd-spinning 1.1s linear infinite;border:2px solid hsla(0,0%,100%,.5);border-right-color:#fff;font-size:14px;filter:alpha(opacity=0);transform:translateZ(0)}#edd-recapture-disconnect.button .edd-loading.dark{border-color:rgba(0,0,0,.2) #666 rgba(0,0,0,.2) rgba(0,0,0,.2);box-shadow:none}.recapture-notice{position:relative}@keyframes edd-spinning{0%{transform:rotate(0deg)}to{transform:rotate(-1turn)}}#edd-chartjs-tooltip{position:absolute;background-color:#fff;border-radius:7px;transition:all .1s ease;pointer-events:none;transform:translate(50%);font-size:12px;box-shadow:0 0 0 1px rgba(89,94,100,.1),0 15px 35px 0 rgba(89,94,100,.1),0 5px 15px 0 rgba(0,0,0,.12);min-width:120px;opacity:0}.edd-chartjs-tooltip-key{display:inline-block;width:10px;height:10px;margin-left:5px}#edd-send-test-summary-save-changes-notice .notice p{font-size:13px}#edd-send-test-summary-notice,#edd-send-test-summary-save-changes-notice{display:flex;margin-top:5px}.edd-graph .y1Axis{color:#edc240!important}.edd-graph .y2Axis{color:#afd8f8!important}.wp-list-table.apikeys input.code{width:100%;font-size:10px;cursor:text;background:#fff;border:1px solid #ddd;box-shadow:none;color:#555}.edd-toggle{position:relative;display:inline-block;overflow:visible}.edd-toggle input[type=checkbox]{display:inline-block;vertical-align:middle;position:relative;margin:0;padding:0;width:42px;min-width:42px;height:24px;background-color:#ccc;transition:background .2s ease;border-radius:34px;box-shadow:none;border:none}.edd-toggle .label{display:inline-block;vertical-align:middle;white-space:nowrap}.edd-toggle input[type=checkbox]:before{position:absolute;content:"";height:18px;width:18px;right:3px;bottom:3px;background-color:#fff;transition:transform .1s ease;border-radius:50%}@media only screen and (max-width:782px){.edd-toggle input[type=checkbox]:checked:before{margin:-.1875rem -.25rem 0 0}}.edd-toggle input[type=checkbox]:checked{background-color:#007cba;background-color:var(--wp-admin-theme-color)}.edd-toggle input[type=checkbox]:active,.edd-toggle input[type=checkbox]:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}.edd-toggle input[type=checkbox]:checked:active,.edd-toggle input[type=checkbox]:checked:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #007cba;box-shadow:0 0 0 1px #fff,0 0 0 3px var(--wp-admin-theme-color)}.edd-toggle input[type=checkbox]:checked:before{transform:translateX(-22px)}.edd-toggle .label+input,.edd-toggle input+.label{margin-right:5px}.download_page_edd-tools .tablenav .actions{overflow:visible}.edd_user_search_wrap{position:relative;overflow:visible}.edd_user_search_wrap .spinner{position:absolute;margin:0;padding:0;left:4px;top:-2px}.edd_user_search_wrap.loading .spinner{visibility:visible}.edd_user_search_results{position:absolute;right:0;top:20px}.edd_user_search_results a.edd-ajax-user-cancel{position:absolute;left:6px;top:2px}.edd_user_search_results ul{background:#fafafa;border:1px solid #dfdfdf;overflow-y:scroll;padding:0;margin:0;height:150px;width:185px;box-shadow:0 3px 5px rgba(0,0,0,.1)}.edd_user_search_results li{margin:0}.edd_user_search_results li a{display:block;text-decoration:none;padding:6px 10px}.edd_user_search_results li a:hover{background:#f5f5f5}.edd_user_search_results li.no-users{text-align:center;vertical-align:middle;display:block;line-height:150px;color:#bbb;text-transform:uppercase;font-size:11px}@media screen and (max-width:1100px){.edd-mix-chart{display:block;width:100%}}@media screen and (max-width:782px){.license-expiration-date-notice,.license-lifetime-notice,.license-null{padding-right:0}}@media screen and (max-width:600px){#edd-edit-order-form input.large-text{width:100%}}#edd-item-wrapper{background:#fff;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04);position:relative;margin-top:15px;display:flex}#edd-item-wrapper.full-width{max-width:100%}#edd-item-wrapper:after{content:"";display:block;clear:both;visibility:hidden;font-size:0;height:0}.edd-sections-wrap{clear:both;width:100%}.edd-sections-wrap .section-wrap{background-color:#fff;display:inline-block;z-index:2}.js .edd-sections-wrap .edd-vertical-sections:not(.meta-box) .section-wrap>div{min-height:500px;height:100%}.edd-sections-wrap .section-wrap .customer-section:not(:last-child){border-bottom:1px solid #eee}.edd-sections-wrap .section-wrap .customer-section table{margin-bottom:20px}.edd-sections-wrap .section-wrap{border-right:1px solid #e5e5e5}.edd-sections-wrap .section-wrap .section-content>*{padding:20px}.edd-sections-wrap .section-wrap .section-content h2{margin:0;padding-bottom:0}.edd-sections-wrap .section-wrap .avatar-wrap{float:right;padding-left:10px;text-align:center}.edd-sections-wrap .section-wrap img.avatar{border-radius:5px}.edd-sections-wrap .section-wrap .customer-id{position:absolute;left:0;top:0;padding:10px;background-color:#fafafa;border-bottom-right-radius:20%;border:1px solid #eee;border-top:none;border-left:none;font-family:monospace;font-size:18px;font-weight:600}.edd-item-info.customer-info input[type=password],.edd-item-info.customer-info input[type=text],.edd-item-info.customer-info select{width:200px;height:auto;box-shadow:none;transition:none;border:1px solid #ddd;margin:-5px -2px 4px 0;font-size:13px;padding:2px 4px}.edd-sections-wrap .section-wrap .customer-main-wrapper{float:right}.edd-sections-wrap .section-wrap .customer-main-wrapper input[name="customerinfo[name]"]{font-size:24px}.edd-sections-wrap .section-wrap .customer-address-wrapper{float:left;margin-top:-3px;margin-left:50px;width:202px}.edd-sections-wrap .section-wrap .info-wrapper{min-height:125px;overflow:visible}.edd-sections-wrap .section-wrap .customer-address span[data-key=address2],.edd-sections-wrap .section-wrap .customer-address span[data-key=address],.edd-sections-wrap .section-wrap .customer-address span[data-key=country]{display:block}.edd-sections-wrap .section-wrap a.delete{color:red;margin-left:5px;text-decoration:none}.customer-info{min-height:185px}.customer-info .customer-name{font-size:24px;font-weight:600}.customer-info .customer-name.editable{margin-bottom:6px}.customer-edit-link a{font-weight:400;text-decoration:none}.disconnect-user a{color:#aaa;font-size:20px}#customer-edit-actions{padding:3px;line-height:28px;text-align:center}#customer-edit-actions .button-secondary{margin-left:5px}#customer-edit-actions .cancel{padding:5px}.edd-sections-wrap .section-wrap .row-title{width:30%}.edd-sections-wrap .section-wrap .editable{display:block;padding:3px}.edd-sections-wrap .section-wrap div.edit-item{margin-right:-4px;margin-top:-20px}.edd-sections-wrap .section-wrap .customer-address.edit-item{margin-top:3px}.edd-sections-wrap .section-wrap span.edit-item{display:none}.edd-sections-wrap .section-wrap .edit-item input{font-size:13px}.edd-sections-wrap .section-wrap .customer-name.edit-item input{margin-top:-5px}.edd-sections-wrap .section-wrap .edd_user_search_results{right:-2px;top:18px}.edd-sections-wrap .section-wrap .edd_user_search_results ul{width:198px}#edd-item-stats-wrapper{margin:0 auto;text-align:center}#edd-item-stats-wrapper ul{display:flex;margin:0}#edd-item-stats-wrapper li{font-size:14px;margin-bottom:0;width:50%}#edd-item-stats-wrapper a{text-decoration:none}#edd-item-stats-wrapper .dashicons{color:#888;margin-top:-2px}#edd-item-tables-wrapper table{width:100%}#edd-item-tables-wrapper .no-items{text-align:right}#edd-item-tables-wrapper .emails .add-customer-email-row{background-color:#f4f4f4;border-top:1px solid #e5e5e5}#edd-item-tables-wrapper .add-customer-email-wrapper{display:flex;flex-wrap:wrap;align-items:center;margin:12px 0}#edd-item-tables-wrapper .edd-form-group{margin-bottom:0}#edd-item-tables-wrapper .edd-make-email-primary{flex-grow:1;margin-right:12px}#edd-item-tables-wrapper .emails .spinner{float:none;margin:0 10px;-ms-grid-row-align:center;align-self:center}#edd-item-tables-wrapper .notice-error{background-color:#fff5f5}#edd-item-notes-wrapper{min-height:50px}.customer-note-input{margin-bottom:5px;width:100%}.customer-note-wrapper{border-bottom:1px solid #f9f9f9;min-height:38px;padding:7px 7px 7px 0}.customer-note-wrapper span{display:block}.note-content-wrap{padding-top:7px}.edd-sections-wrap .section-wrap .notice-container{padding-right:20px;padding-left:20px;margin-right:-20px;margin-left:-20px}@media screen and (max-width:810px)and (min-width:656px){.customer-info .customer-name{font-size:16px}.edd-sections-wrap .section-wrap .widefat td,.widefat th{max-width:100%!important;display:table-cell}}@media screen and (max-width:781px){#edd-item-tab-wrapper,.edd-sections-wrap .section-wrap{margin:0;width:100%}#edd-item-tab-wrapper-list .dashicons{font-size:18px}.edd-item-has-tabs .edd-sections-wrap .section-wrap{border-top:1px solid #e5e5e5;border-right:0;margin-top:-1px}}@media screen and (max-width:656px){.edd-item-info.customer-info{position:relative}.edd-sections-wrap .section-wrap .customer-address-wrapper{float:none;position:absolute;top:84px;right:165px;max-width:200px}.edd-sections-wrap .section-wrap .customer-main-wrapper{float:none;position:absolute;right:165px}.customer-info .customer-name{font-size:16px}.edd-sections-wrap .section-wrap #edd-item-stats-wrapper{padding-right:0;padding-left:0}.edd-sections-wrap .section-wrap .customer-section{margin-bottom:0}.edd-sections-wrap .section-wrap .widefat td.column-primary,.edd-sections-wrap .section-wrap .widefat td.no-items,.edd-sections-wrap .section-wrap .widefat th.column-primary{width:100px!important;display:table-cell;overflow:hidden;text-align:right}.edd-sections-wrap .section-wrap .customer-id{display:none}#edd-item-tables-wrapper .emails td.column-primary{padding-left:10px;width:100%!important}#edd-item-tables-wrapper .edd-form-group{margin:0 0 16px}}@media screen and (max-width:480px){#edd-item-tab-wrapper-list li{width:50%}#edd-item-tab-wrapper-list li:nth-child(3n+3){border-width:0 0 1px 1px}#edd-item-tab-wrapper-list li:nth-child(2n){border-width:0 0 1px}.download_page_edd-reports .button{text-align:center}#edd-payment-date-filters span{display:block}#edd-payment-date-filters span>input{float:left}#edd-add-discount select[multiple] option,#edd-edit-discount select[multiple] option{height:20px}.download_page_edd-reports .inside .button,.download_page_edd-reports .inside input[type=submit],.download_page_edd-reports .inside input[type=text],.download_page_edd-reports .inside select,.download_page_edd-settings .inside input[type=button],.download_page_edd-tools .inside input[type=submit],.download_page_edd-tools .inside input[type=text],.download_page_edd-tools .inside select{width:100%}#edd-add-discount select[multiple],#edd-edit-discount select[multiple],.download_page_edd-tools select[multiple]{height:200px!important}.download_page_edd-settings input[type=checkbox]{margin:2px 0}.post-type-download input[type=checkbox]{margin-right:2px}}.inside .edd-tools-textarea{background:#32373c;color:rgba(240,245,250,.7);font-size:12px;font-family:Menlo,Monaco,monospace;display:block;overflow:auto;white-space:pre;width:100%;height:450px;padding:10px;outline:none}#system-info-textarea::selection{background:#555;color:#fff}#edd-system-info .edd-inline-button{margin-right:5px}.recount-stats-controls form{display:inline}.edd-recount-stats-descriptions span{display:none;line-height:24px}.edd-vertical-sections{overflow:visible;display:block;display:flex}#edd-item-tab-wrapper,.edd-vertical-sections .section-nav{position:relative;width:20%;line-height:1em;margin:0 0 0 -1px;padding:0;background-color:#f5f5f5;border-left:1px solid #e5e5e5;box-sizing:border-box;max-width:200px}#edd-item-tab-wrapper-list{margin:0}#edd-item-tab-wrapper li,.edd-vertical-sections .section-nav li{display:block;position:relative;margin:0;padding:0;background-color:#fcfcfc}.edd-vertical-sections .section-title:last-of-type{margin-bottom:24px}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap,#edd-item-tab-wrapper li a,.edd-vertical-sections .section-nav li a{display:flex;margin:0;padding:9px;text-decoration:none;border-bottom:1px solid #e5e5e5;box-shadow:none;position:relative;align-items:center}#edd-item-tab-wrapper li a:focus,#edd-item-tab-wrapper li a:hover,.edd-vertical-sections .section-nav li a:focus,.edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0;outline:0;transition:all .25s}.edd-vertical-sections .section-nav .section-title--is-active a:after{content:"";width:1px;height:100%;background:#fff;position:absolute;left:0;top:0;bottom:0;z-index:3}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap{background-color:#fff}.edd-vertical-sections .section-nav li a>.dashicons,.edd-vertical-sections .section-nav li a>span{display:inline-block}.edd-vertical-sections .section-nav li a>span{max-width:76%}.edd-vertical-sections .section-nav li a .dashicons{line-height:20px;margin-left:3px;color:#888}.edd-vertical-sections .section-nav .section-title--is-active a{font-weight:700;color:#555;background-color:#fff;border-left:none;margin-left:-1px}.edd-vertical-sections.use-js .section-content,.no-js .edd-vertical-sections.use-js.edd-item-header-small,.no-js .edd-vertical-sections.use-js .section-nav{display:none}.no-js .edd-vertical-sections.use-js .section-content{display:block}.admin-color-fresh .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-fresh .edd-vertical-sections .section-nav li a:focus,.admin-color-fresh .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #0073aa}.admin-color-blue .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-blue .edd-vertical-sections .section-nav li a:focus,.admin-color-blue .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #096484}.admin-color-coffee .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-coffee .edd-vertical-sections .section-nav li a:focus,.admin-color-coffee .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #c7a589}.admin-color-ectoplasm .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:focus,.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #a3b745}.admin-color-midnight .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-midnight .edd-vertical-sections .section-nav li a:focus,.admin-color-midnight .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #e14d43}.admin-color-ocean .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-ocean .edd-vertical-sections .section-nav li a:focus,.admin-color-ocean .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #627c83}.admin-color-sunrise .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-sunrise .edd-vertical-sections .section-nav li a:focus,.admin-color-sunrise .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #be3631}.admin-color-light .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-light .edd-vertical-sections .section-nav li a:focus,.admin-color-light .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #888}.admin-color-evergreen .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-evergreen .edd-vertical-sections .section-nav li a:focus,.admin-color-evergreen .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #36533f}.admin-color-mint .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-mint .edd-vertical-sections .section-nav li a:focus,.admin-color-mint .edd-vertical-sections .section-nav li a:hover{box-shadow:inset -5px 0 #4f6d59}.edd-vertical-sections .section-nav .section-title--is-active .dashicons{color:#555}@media only screen and (max-width:782px){#edd-item-tab-wrapper,.edd-vertical-sections .section-nav{width:48px}.edd-vertical-sections .section-nav li a{justify-content:center}.edd-vertical-sections .section-nav li a .dashicons{width:24px;height:24px;font-size:24px;line-height:24px;margin:0}.section-nav li .dashicons:before{width:24px;height:24px}#edd-item-tab-wrapper .edd-item-tab-label,.section-nav li .label{overflow:hidden;position:absolute;top:-1000em;right:-1000em;width:1px;height:1px}}#edd-item-card-wrapper,.edd-vertical-sections .section-wrap{width:80%}#edd-item-card-wrapper .item-section{background:#fff;overflow:hidden;box-sizing:border-box}:not(#edd-item-tab-wrapper)+#edd-item-card-wrapper .item-section{margin:25px 0;padding:20px;border:1px solid #e5e5e5;box-shadow:0 1px 1px rgba(0,0,0,.04)}#edd-item-tab-wrapper+#edd-item-card-wrapper{padding:20px;border-right:1px solid #e5e5e5;box-sizing:border-box}@media only screen and (min-width:1200px){#edd-graphs-filter,#edd-item-card-wrapper,.edd-vertical-sections:not(.meta-box) .section-wrap{width:calc(100% - 200px)}}@media only screen and (max-width:782px){#edd-graphs-filter,#edd-item-card-wrapper,.edd-vertical-sections .section-wrap{width:calc(100% - 48px)}}#edd-debug-log .edd-inline-button{margin-right:5px}.edd-settings-sidebar{padding-top:27px}.edd-settings-sidebar-content{background-color:#fff;text-align:center;border:1px solid #ddd;box-sizing:border-box;max-width:300px}.edd-settings-sidebar-content p{font-size:14px;line-height:1.5;margin-top:0}.edd-sidebar-header-section{background-color:#35495c;line-height:1;padding:26px 20px 24px;border-bottom:3px dashed #fafafa}.edd-sidebar-description-section{background-color:#fafafa;padding:16px 20px;border-bottom:1px solid #ddd}.edd-sidebar-description-section .edd-sidebar-description{margin:0}.edd-sidebar-coupon-section{font-size:14px;padding:16px 20px}.edd-sidebar-coupon-section label{display:block;line-height:1.4;margin-bottom:6px}.edd-sidebar-coupon-section label strong{color:#253b51;font-weight:700}.edd-sidebar-coupon-section input{background:#f4f7fa;font-size:22px;font-weight:600;text-align:center;padding:10px;border:2px dashed #2794da;border-radius:4px;margin-bottom:16px;box-shadow:none;width:100%}.edd-sidebar-coupon-section input:focus{border:2px dashed #2794da;box-shadow:none}.edd-settings-sidebar-content .edd-coupon-note{color:#6c7883;font-size:13px;font-style:italic;margin:0}.edd-settings-sidebar-content .edd-coupon-note a{color:#253b51}.edd-settings-sidebar-content .edd-coupon-note a:hover{text-decoration:none}.edd-sidebar-footer-section{background-color:#fafafa;padding:16px 20px;border-top:1px solid #ddd}.edd-sidebar-footer-section .edd-cta-button{display:block;background-color:#2794da;color:#fff;text-decoration:none;font-size:20px;font-weight:700;text-transform:uppercase;padding:17px 10px;border:none;border-radius:4px;width:100%;box-sizing:border-box;box-shadow:none;transition:background-color .2s}.edd-sidebar-footer-section .edd-cta-button:hover{background-color:#2386c5}@media (min-width:1080px){.edd-has-sidebar .edd-settings-content{float:right;width:67%}.edd-has-sidebar .edd-settings-sidebar{float:left;width:31%}}@media (min-width:1240px){.edd-has-sidebar .edd-settings-content{width:74%}.edd-has-sidebar .edd-settings-sidebar{width:23%}}.taxes-tab .edd-has-sidebar .edd-settings-content,.taxes-tab .edd-has-sidebar .edd-settings-sidebar{float:none;width:100%}.bfcm-promo-img-container{background-color:#35495c;width:100%;height:160px}.bfcm-code{color:#2794da;font-weight:700}.sale-ends{position:absolute;bottom:9px;left:14px;display:inline-block;color:#6c7883;font-size:12px;text-align:left;font-style:italic;width:150px} \ No newline at end of file diff --git a/assets/css/edd-admin-tax-rates-rtl.min.css b/assets/css/edd-admin-tax-rates-rtl.min.css new file mode 100644 index 00000000000..8dd181054b2 --- /dev/null +++ b/assets/css/edd-admin-tax-rates-rtl.min.css @@ -0,0 +1 @@ +#edd-admin-tax-rates{margin:1em 0 0}#edd-admin-tax-rates table{border-collapse:collapse}#edd-admin-tax-rates .tablenav.top{display:flex;justify-content:space-between}#edd-admin-tax-rates .edd-admin-tax-rates__tablenav--left{display:inline-flex}#edd-admin-tax-rates th:not(.check-column){padding:15px 10px;width:unset}#edd-admin-tax-rates .chosen-container{width:100%!important}#edd-admin-tax-rates tbody tr:not(:last-of-type){border-bottom:1px solid #e0e0e0}#edd-admin-tax-rates tfoot.add-new th{font-weight:400;padding:12px 8px 10px}#edd-admin-tax-rates .edd-tax-rate-row--inactive,#edd-admin-tax-rates .edd-tax-rate-row--is-empty+.edd-tax-rate-row--is-empty{display:none}#edd-admin-tax-rates .has-inactive .edd-tax-rate-row--inactive{display:table-row}#edd-admin-tax-rates .edd-tax-rate-row--is-empty td{background-color:#f9f9f9}#edd-admin-tax-rates .edd-tax-rate-row--inactive td{color:#999;background-color:#f9f9f9}#edd-admin-tax-rates .edd-tax-rate-table-add{background-color:#f9f9f9}@media screen and (max-width:782px){#edd-admin-tax-rates tfoot:not(.add-new) th:not(.edd-tax-rates-table-rate),#edd-admin-tax-rates thead th:not(.edd-tax-rates-table-rate){display:none}#edd-admin-tax-rates .edd-tax-rate-row,#edd-admin-tax-rates tfoot:not(.add-new) tr,#edd-admin-tax-rates thead tr{display:-ms-grid;display:grid;-ms-grid-columns:2.5em 1fr;grid-template-columns:2.5em 1fr;-ms-grid-rows:1fr;grid-template-rows:1fr;grid-gap:0 16px}#edd-admin-tax-rates th.edd-tax-rates-table-rate{padding-right:12px}#edd-admin-tax-rates .edd-tax-rates-table-checkbox{-ms-grid-row:1;-ms-grid-row-span:4;grid-row:1/5}#edd-admin-tax-rates tbody td{padding-right:35%!important}#edd-admin-tax-rates td:before{content:attr(data-colname);display:block;width:32%;position:absolute}#edd-admin-tax-rates .tablenav.top{flex-wrap:wrap}#edd-admin-tax-rates .edd-admin-tax-rates__tablenav--left{margin-bottom:16px}#edd-admin-tax-rates .edd-admin-tax-rates__tablenav--left select{margin-left:6px}}.edd-tax-rate-table-add th input[type=number],.edd-tax-rate-table-add th input[type=text],.edd-tax-rate-table-add th select{width:100%;margin:0;padding:4px}.edd-tax-rate-table-add #tax_rate_region_global{margin-left:4px;margin-bottom:8px}@media screen and (max-width:782px){.edd-tax-rate-table-add,.edd-tax-rate-table-add th{display:block}.edd-tax-rate-table-add .screen-reader-text{display:block;width:unset;clip:unset;height:unset;-webkit-clip-path:unset;clip-path:unset;margin:0 0 12px;position:relative}} \ No newline at end of file diff --git a/assets/css/edd-admin-tax-rates.min.css b/assets/css/edd-admin-tax-rates.min.css new file mode 100644 index 00000000000..fc6d469720a --- /dev/null +++ b/assets/css/edd-admin-tax-rates.min.css @@ -0,0 +1 @@ +#edd-admin-tax-rates{margin:1em 0 0}#edd-admin-tax-rates table{border-collapse:collapse}#edd-admin-tax-rates .tablenav.top{display:flex;justify-content:space-between}#edd-admin-tax-rates .edd-admin-tax-rates__tablenav--left{display:inline-flex}#edd-admin-tax-rates th:not(.check-column){padding:15px 10px;width:unset}#edd-admin-tax-rates .chosen-container{width:100%!important}#edd-admin-tax-rates tbody tr:not(:last-of-type){border-bottom:1px solid #e0e0e0}#edd-admin-tax-rates tfoot.add-new th{font-weight:400;padding:12px 8px 10px}#edd-admin-tax-rates .edd-tax-rate-row--inactive,#edd-admin-tax-rates .edd-tax-rate-row--is-empty+.edd-tax-rate-row--is-empty{display:none}#edd-admin-tax-rates .has-inactive .edd-tax-rate-row--inactive{display:table-row}#edd-admin-tax-rates .edd-tax-rate-row--is-empty td{background-color:#f9f9f9}#edd-admin-tax-rates .edd-tax-rate-row--inactive td{color:#999;background-color:#f9f9f9}#edd-admin-tax-rates .edd-tax-rate-table-add{background-color:#f9f9f9}@media screen and (max-width:782px){#edd-admin-tax-rates tfoot:not(.add-new) th:not(.edd-tax-rates-table-rate),#edd-admin-tax-rates thead th:not(.edd-tax-rates-table-rate){display:none}#edd-admin-tax-rates .edd-tax-rate-row,#edd-admin-tax-rates tfoot:not(.add-new) tr,#edd-admin-tax-rates thead tr{display:-ms-grid;display:grid;-ms-grid-columns:2.5em 1fr;grid-template-columns:2.5em 1fr;-ms-grid-rows:1fr;grid-template-rows:1fr;grid-gap:0 16px}#edd-admin-tax-rates th.edd-tax-rates-table-rate{padding-left:12px}#edd-admin-tax-rates .edd-tax-rates-table-checkbox{-ms-grid-row:1;-ms-grid-row-span:4;grid-row:1/5}#edd-admin-tax-rates tbody td{padding-left:35%!important}#edd-admin-tax-rates td:before{content:attr(data-colname);display:block;width:32%;position:absolute}#edd-admin-tax-rates .tablenav.top{flex-wrap:wrap}#edd-admin-tax-rates .edd-admin-tax-rates__tablenav--left{margin-bottom:16px}#edd-admin-tax-rates .edd-admin-tax-rates__tablenav--left select{margin-right:6px}}.edd-tax-rate-table-add th input[type=number],.edd-tax-rate-table-add th input[type=text],.edd-tax-rate-table-add th select{width:100%;margin:0;padding:4px}.edd-tax-rate-table-add #tax_rate_region_global{margin-right:4px;margin-bottom:8px}@media screen and (max-width:782px){.edd-tax-rate-table-add,.edd-tax-rate-table-add th{display:block}.edd-tax-rate-table-add .screen-reader-text{display:block;width:unset;clip:unset;height:unset;-webkit-clip-path:unset;clip-path:unset;margin:0 0 12px;position:relative}} \ No newline at end of file diff --git a/assets/css/edd-admin.min.css b/assets/css/edd-admin.min.css new file mode 100644 index 00000000000..39389650833 --- /dev/null +++ b/assets/css/edd-admin.min.css @@ -0,0 +1 @@ +.edd-custom-price-option-sections-wrap{display:none;border:1px solid #c3c4c7;border-top:0 solid #c3c4c7;box-sizing:border-box;width:100%}.edd-custom-price-option-section{display:block;padding:10px 8px;border-bottom:1px solid hsla(0,0%,87.1%,.3)}.edd-custom-price-option-section-title{display:block;font-weight:600;padding:0 0 10px}.edd-custom-price-option-section-content{display:flex;gap:12px;margin-bottom:6px}.edd-custom-price-option-section:last-child{border-bottom:none}.toggle-custom-price-option-section{color:#787c82}.toggle-custom-price-option-section:hover{color:#537994}#edd_product_settings .edd-product-options__title,#edd_product_settings .inside strong{border-top:1px solid #c3c4c7;border-bottom:1px solid #c3c4c7;background-color:#f9f9f9;display:flex;font-weight:600;margin:0 -12px 16px;padding:8px 12px;justify-content:space-between;align-items:center}#edd_product_settings .edd-product-options-wrapper:first-of-type .edd-product-options__title,#edd_product_settings .inside div:first-child strong{margin-top:-8px}#edd_product_settings .edd-product-options__title .edd-help-tip,#edd_product_settings .inside strong .edd-help-tip{font-size:20px}#edd_product_settings .label--block{display:block;margin:0 0 4px}.edd_repeatable_row.ui-sortable-placeholder{line-height:0;padding:0;margin:0;box-sizing:border-box;border:1px dashed #c3c4c7;visibility:visible!important}.edd-add-repeatable-row{border-top:1px solid #c3c4c7;padding:12px;margin:15px -12px -12px;display:flex;justify-content:flex-end;align-items:center}.edd_repeatable_row input[type=text].large-text{width:100%}.edd_repeatable_upload_wrapper:not(:first-child),.edd_variable_prices_wrapper:not(:first-child){margin-top:12px}.edd_repeatable_row.ui-sortable-helper .edd-repeatable-row-actions .edd-remove-row{display:none}.edd-repeatable-row-actions{color:#787c82}.edd-repeatable-row-actions a{text-decoration:none;width:auto;cursor:pointer}.edd-bundle-products-header,.edd-repeatable-row-header{clear:both;background:#f6f7f7;border:1px solid #c3c4c7;display:flex;justify-content:space-between}.edd-repeatable-row-header{cursor:move}.edd_repeatable_row:hover .edd-repeatable-row-header,.edd_repeatable_row:hover .edd-repeatable-row-standard-fields{border-color:#c3c4c7}.edd-bundled-product-row:after,.edd-bundled-product-row:before,.edd-repeatable-row-header:after,.edd-repeatable-row-header:before{content:"";display:table}.edd-bundled-product-row:after,.edd-repeatable-row-header:after{clear:both}.edd-bundle-products-header,.edd-repeatable-row-title{font-weight:600}.edd-bundle-products-header,.edd-repeatable-row-actions,.edd-repeatable-row-title{padding:8px;box-sizing:border-box}.edd-repeatable-row-actions{flex-grow:1;text-align:right}.edd-bundled-product-row .edd-remove-row,.edd-repeatable-row-actions .edd-remove-row{width:auto;cursor:pointer}.edd-bundled-product-row,.edd-repeatable-row-standard-fields{padding:8px;border:1px solid #c3c4c7;border-top:0 solid #c3c4c7;display:flex;justify-content:space-between;align-items:center;gap:18px}.edd-bundled-product-row .edd-form-group,.edd-repeatable-row-standard-fields .edd-form-group{margin-bottom:0;display:inline-flex;flex-direction:column;flex-grow:1;justify-content:space-between}.edd-repeatable-row-setting-label .edd-help-tip{display:inline-block;margin-left:4px}.edd-bundled-product-item-reorder{min-width:30px}.edd-bundled-product-item-reorder .edd-product-file-reorder{font-size:20px;cursor:move;color:#dcdcde;font-family:dashicons;content:"";transition:color .2s}.edd-bundled-product-item-reorder .edd-product-file-reorder:hover{color:#a7aaad}.edd-bundled-product-actions{-ms-grid-row-align:center;align-self:center}#edd_products .edd-select,.edd_repeatable_product_wrapper .edd-select,.edd_repeatable_upload_wrapper .pricing select{min-width:100%;max-width:200px}.edd_repeatable_product_wrapper td{overflow:visible}@media screen and (max-width:480px){.edd-bundle-products-header,.edd-bundled-product-row,.edd-repeatable-row-header,.edd-repeatable-row-standard-fields{flex-wrap:wrap}.edd-bundled-product-row .edd-form-group,.edd-repeatable-row-standard-fields .edd-form-group{margin-left:0!important;margin-bottom:24px}}.edd_remove_repeatable{border:none;cursor:pointer;display:inline-block;padding:0;overflow:hidden;margin:8px 0 0;text-indent:-9999px;width:10px;height:10px}.edd_remove_repeatable:active,.edd_remove_repeatable:focus,.edd_remove_repeatable:hover{background-position:-10px 0!important}.edd_repeatable_upload_wrapper .edd_repeatable_upload_field_container{position:relative;width:100%}.edd_repeatable_upload_wrapper .edd_repeatable_upload_field_container+span:first-child{width:100%}.edd_repeatable_upload_field{padding-right:32px}.edd_upload_file button{background:#f6f7f7;border:none;border-left:1px solid #c3c4c7;padding:0 4px;position:absolute;height:calc(100% - 4px);overflow:hidden;top:2px;right:2px;display:inline-flex;justify-content:center;align-items:center}#edd-duplicate-action~#publishing-action{position:relative;top:-10px}.edd-form-group{margin-bottom:16px}.edd-form-group:last-of-type{margin-bottom:0}.edd-form-group>label,.edd-form-group__label{display:block;font-weight:600;margin-bottom:8px;padding:0}.edd-form-group__control{margin-bottom:12px;max-width:100%}.edd-form-group__control.is-check,.edd-form-group__control.is-radio{margin-top:4px}.edd-form-group__control:last-of-type{margin-bottom:0}.edd-form-group__control--is-inline{display:inline-flex;align-items:flex-end}.edd-form-group__input{max-width:100%}.edd-form-group__input[type=checkbox],.edd-form-group__input[type=radio]{margin-top:0}.edd-form-group__input[type=checkbox]+label,.edd-form-group__input[type=radio]+label{display:unset}select.edd-form-group__input{max-width:100%}.edd-form-group__help{color:#646970;font-size:13px;font-style:italic;line-height:normal;margin:8px 0 0}.edd-form-row{display:flex;flex-wrap:wrap;gap:12px}.edd-form-row__column{display:inline-flex;flex-direction:column;justify-content:flex-end}.edd-form-row__column.edd-form-group{margin-bottom:0}.edd-form-row label,.edd-form-row label.edd-form-group__label{margin-bottom:8px}#edd-migration-progress .dashicons-minus{color:#949494}#edd-migration-progress .dashicons-yes{color:green}#edd-migration-progress .dashicons-update:before{animation:rotation 2s linear infinite;display:block}#edd-v3-migration-remove-legacy-data-submit-wrap{display:flex;align-items:center;gap:6px}#edd-v3-migration-remove-legacy-data-submit-wrap .button{margin:0}#edd-filters{padding:10px;margin:0;display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px}#edd-filters .filter-items{flex-wrap:wrap;gap:6px;float:none;flex-grow:1}#edd-filters .filter-items,#edd-filters .filter-items .graph-option-section{display:flex;align-items:center}#edd-filters .filter-items .edd-date-range-picker[data-range=other] .edd-graphs-date-options{border-top-right-radius:4px;border-bottom-right-radius:4px}#edd-filters .filter-items .edd-date-range-picker[data-range=other] .edd-date-range-dates,#edd-filters .filter-items .edd-date-range-picker[data-range=other] .edd-date-range-relative-dates{display:none}#edd-filters .filter-items .edd-date-range-options{display:inline-block;margin:10px 0}#edd-filters .filter-items .edd-graphs-date-options{border-top-right-radius:0;border-bottom-right-radius:0}#edd-filters .filter-items .edd-date-range-dates{display:flex;align-items:center;border:1px solid #8c8f94;border-left:none;color:#2c3338;padding:4px 10px;margin-left:-5px;border-top-right-radius:4px;border-bottom-right-radius:4px;cursor:pointer;gap:4px}#edd-filters .filter-items .edd-date-range-dates.hidden{display:none}#edd-filters .filter-items .edd-date-range-selected-date{display:inline-block}#edd-filters .filter-items .edd-date-range-relative-dates{display:flex;align-items:center;margin-left:10px}#edd-filters .filter-items .edd-date-range-relative-dates.hidden{display:none}#edd-filters .filter-items .edd-date-range-selected-relative-date{position:relative;display:flex;align-items:center;border:1px solid #8c8f94;padding:4px 2px 4px 6px;color:#2c3338;margin-left:10px;margin-right:10px;border-radius:4px;cursor:pointer}#edd-filters .filter-items .edd-date-range-selected-relative-date .arrow-down{width:16px;height:auto;margin-left:6px;margin-top:2px;vertical-align:middle}#edd-filters .filter-items .edd-date-range-selected-relative-date.opened .edd-date-range-relative-dropdown{display:block}#edd-filters .filter-items .edd-date-range-relative-dropdown{position:absolute;z-index:99;width:420px;left:50%;top:100%;margin-top:10px;transform:translateX(-50%);background-color:#fff;border:1px solid #8c8f94;border-radius:4px;box-shadow:0 2px 5px 0 rgba(0,0,0,.25);display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown:after{height:10px;width:10px;position:absolute;content:"";background:#fff;border-color:#8c8f94;border-style:solid;border-width:0 1px 1px 0;transform:rotate(-135deg);top:-6px;left:calc(50% - 4px)}#edd-filters .filter-items .edd-date-range-relative-dropdown .spinner{display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown.loading{padding:10px;text-align:center}#edd-filters .filter-items .edd-date-range-relative-dropdown.loading .spinner{display:inline-block;visibility:visible;margin:0;float:unset}#edd-filters .filter-items .edd-date-range-relative-dropdown.loading :not(.spinner){display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li{display:flex;align-items:center;padding:2px 10px;opacity:.85;gap:20px}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li.active,#edd-filters .filter-items .edd-date-range-relative-dropdown ul li:hover{cursor:pointer;color:var(--wp-admin-theme-color);opacity:1}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li .date-range-name{width:110px}@media screen and (max-width:950px){#edd-filters .filter-items .graph-option-section{margin-top:8px;width:100%}#edd-filters .filter-items .edd-date-range-picker{flex-wrap:wrap}#edd-filters .filter-items .edd-graphs-date-options{width:100%;max-width:100%;min-height:40px;font-size:14px;border-top-right-radius:4px;border-bottom-right-radius:4px}#edd-filters .filter-items .edd-date-range-dates{width:100%;margin-top:10px;border:1px solid #8c8f94;margin-left:unset;border-radius:4px;font-size:14px;padding:8px 6px 8px 8px}#edd-filters .filter-items .edd-date-range-relative-dates{width:100%;flex-wrap:wrap;margin-left:0;margin-top:6px}#edd-filters .filter-items .edd-date-range-selected-relative-date{width:100%;margin-top:8px;margin-left:0;margin-right:0;font-size:14px;padding:8px 6px 8px 8px;flex-wrap:wrap}#edd-filters .filter-items .edd-date-range-selected-relative-date .arrow-down{margin-left:auto}#edd-filters .filter-items .edd-date-range-relative-dropdown{position:relative;width:100%;left:0;top:0;transform:unset;box-shadow:unset;border:unset;margin:0}#edd-filters .filter-items .edd-date-range-relative-dropdown:after{display:none}#edd-filters .filter-items .edd-date-range-relative-dropdown ul{margin-bottom:0}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li{padding-left:0;padding-right:0;justify-content:space-between;flex-wrap:wrap;gap:unset}#edd-filters .filter-items .edd-date-range-relative-dropdown ul li .date-range-dates,#edd-filters .filter-items .edd-date-range-relative-dropdown ul li .date-range-name{width:100%}}#edd-filters>p{color:#757575}#edd-filters input[type=number],#edd-filters input[type=text].edd_datepicker{max-width:105px}#edd-filters .button-secondary,#edd-filters input[type=number]{margin-bottom:0}#edd-filters .search-form{margin:0}@media screen and (max-width:480px){#edd-filters span{margin:2px 0}}#edd-advanced-filters{position:relative}#edd-advanced-filters .inside{z-index:99;position:absolute;top:29px;right:0;border:1px solid #e0e0e0;padding:0;background:#fff;box-shadow:0 3px 5px rgba(0,0,0,.2);min-width:285px;opacity:0;visibility:hidden}#edd-advanced-filters fieldset{display:block;padding:10px 15px 15px;margin:10px 0}#edd-advanced-filters fieldset:not(:last-of-type){border-bottom:1px solid #e0e0e0}#edd-advanced-filters fieldset:last-of-type{padding-bottom:5px}#edd-advanced-filters fieldset.edd-add-on-filters div,#edd-advanced-filters fieldset.edd-add-on-filters label,#edd-advanced-filters fieldset.edd-add-on-filters p,#edd-advanced-filters fieldset.edd-add-on-filters span{display:block;margin-bottom:2px}#edd-advanced-filters div.edd-select-chosen:not(:last-child){margin-bottom:10px}#edd-advanced-filters.open .edd-advanced-filters-button{background:#e0e0e0;border-color:#949494;box-shadow:inset 0 2px 5px -3px rgba(0,0,0,.5);transform:translateY(1px)}#edd-advanced-filters.open .inside{visibility:visible;opacity:1;transition:opacity .2s ease-in}.download_page_edd-reports #edd-filters{margin-bottom:-1px;box-shadow:none}@media screen and (max-width:782px){.download_page_edd-reports #edd-filters{gap:0}}.edd-old-log-filters{margin-top:-30px;margin-left:2px}@media screen and (min-width:600px){#edd-reports-charts-wrap{display:-ms-grid;display:grid;-ms-grid-columns:(minmax(200px,50%))[2];grid-template-columns:repeat(2,minmax(200px,50%));grid-gap:20px}.edd-reports-chart{margin-bottom:0}.edd-reports-chart-bar,.edd-reports-chart-line{-ms-grid-column:1;-ms-grid-column-span:2;grid-column:1/span 2}}.edd-reports-chart{height:500px;max-height:700px}.chart-timezone{font-size:.75rem;color:#ccc}.edd-mobile-link{line-height:32px}.edd-mobile-link a{text-decoration:none}.edd-mobile-link a:after,.edd-mobile-link a:before{display:inline-block;-webkit-font-smoothing:antialiased;font:normal 20px/30px dashicons;vertical-align:top;margin:1px 0 0;padding:0}.edd-mobile-link a:before{content:"";color:#757575;margin-right:-3px}.edd-mobile-link a:after{content:""}#edd-reports-tiles-wrap #dashboard-widgets .sortable-placeholder{padding:0;margin:0 0 20px;line-height:0;box-sizing:border-box;height:110px}#edd-reports-tiles-wrap #dashboard-widgets #primary-sortables{margin-left:0}#edd-reports-tiles-wrap #dashboard-widgets #tertiary-sortables{margin-right:0}#edd-reports-tiles-wrap{display:-ms-grid;display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));grid-gap:20px}.edd-reports-tile{text-align:center;padding:20px 10px 35px;display:flex;flex-direction:column;justify-content:center;border:1px solid #e5e5e5;background:#fafafa;position:relative;box-sizing:border-box;gap:.5em}.edd-reports-tile>span:not(.tile-compare){width:100%}.edd-reports-tile .tile-label{text-align:center;text-transform:uppercase;font-size:12px;font-weight:400;color:#101517}.edd-reports-tile .tile-value{color:#333;font-size:2em;line-height:1;transition:all .2s ease-in-out;display:flex;justify-content:center;flex-direction:column;gap:.25em}.edd-reports-tile:hover{border:1px solid #aaa}.edd-reports-tile:hover .tile-value:not(.tile-no-data){transform:scale(1.05)}.edd-reports-tile .tile-amount{color:#2794da}.edd-reports-tile .tile-number{color:#96f}.edd-reports-tile .tile-amount,.edd-reports-tile .tile-number{color:#fff}.edd-reports-tile .tile-value.tile-no-data{color:#ddd}.edd-reports-tile .tile-value.tile-url{font-size:1.5em}.edd-reports-tile .tile-relative{font-size:12px;font-weight:400;color:#888}.edd-reports-tile span.dashicons{display:inline-block;font-size:30px;line-height:20px;height:20px;width:20px;position:relative;top:4px;left:-5px;margin-left:-5px;color:#999}.edd-reports-tile .tile-relative span.dashicons{top:-5px;left:-3px;margin-left:0}.edd-reports-tile .tile-relative span.dashicons-arrow-down,.edd-reports-tile .tile-relative span.dashicons-arrow-up.reverse{color:#d63638}.edd-reports-tile .tile-relative span.dashicons-arrow-down.reverse,.edd-reports-tile .tile-relative span.dashicons-arrow-up{color:#008a20}.edd-reports-tile .tile-compare{position:absolute;right:0;bottom:0;color:#aaa;font-size:11px;line-height:1em;background-color:#fff;border-color:#e5e5e5 #fff #fff #e5e5e5;border-style:solid;border-width:1px;border-top-left-radius:8px;padding:4px 0 0 9px;margin:0 -1px -1px 0}.edd-reports-tile:hover .tile-compare{border-left:1px solid #bbb;border-top:1px solid #bbb;color:#777}#edd-submit-refund-status{text-align:center;font-size:1.2em}#edd-submit-refund-status .edd-submit-refund-message:before{font-family:dashicons;font-size:1.5em;vertical-align:middle;color:#fff;border-radius:16px;margin:5px}#edd-submit-refund-status .edd-submit-refund-message.success:before{content:"";background-color:#008a20;padding-right:1px}#edd-submit-refund-status .edd-submit-refund-message.fail{display:block;margin-bottom:16px}#edd-submit-refund-status .edd-submit-refund-message.fail:before{content:"";background-color:#d63638}.refund-items td,.refund-items th.check-column{vertical-align:baseline}.refund-items .column-amount,.refund-items .column-discount,.refund-items .column-quantity,.refund-items .column-subtotal,.refund-items .column-tax,.refund-items .column-total{width:80px}.refund-items .edd-form-group__control{display:flex;align-items:center}.refund-items .edd-form-group__control input,.refund-items .edd-form-group__control select{background-color:transparent;border:0;border-bottom:1px solid;border-radius:0;box-shadow:none;text-align:right;width:100%}.refund-items .edd-form-group__control input:disabled,.refund-items .edd-form-group__control select:disabled{border-bottom:none}.refund-items .edd-form-group__control input:focus,.refund-items .edd-form-group__control select:focus{border-bottom:1px solid var(--wp-admin-theme-color-darker-10);box-shadow:0 1px 0 var(--wp-admin-theme-color-darker-10)}.refund-items .edd-form-group__control select[data-original="1"]{background:transparent}.refund-items .edd-form-group__control .is-before+span>input,.refund-items .edd-form-group__control select{text-align:left}.refund-items .edd-refund-submit-line-total{background-color:#fff!important}.refund-items .edd-refund-submit-line-total td{text-align:right}.refund-items .edd-refund-submit-line-total-amount{display:inline-block;margin-left:20px;text-align:left;width:80px}.refund-items #edd-refund-submit-subtotal td{border-top:2px solid #c3c4c7}@media screen and (max-width:782px){.refund-items td.column-total{margin-bottom:16px}.refund-items .edd-refund-submit-line-total-amount{padding-right:16px;width:unset}}.edd-submit-refund-actions{margin:16px 0 0}.did-refund .edd-submit-refund-actions,.did-refund .refund-items{display:none}.edd-admin-notice-top-of-page{font-size:15px;line-height:1.4;color:#fff;margin-left:-20px;padding:12px 32px 12px 20px;background:#2d6ca2}.edd-admin-notice-top-of-page.edd-pro-inactive{background:#d63638}@media screen and (min-width:783px){.edd-admin-notice-top-of-page{padding:10px 46px 10px 22px}}@media screen and (min-width:961px){.edd-admin-notice-top-of-page{text-align:center}}.edd-admin-notice-top-of-page a{color:#fff}.edd-admin-notice-top-of-page a:hover{text-decoration:none}.edd-admin-notice-top-of-page .button-link{position:absolute;top:48px;right:-1px;font-size:20px;color:#fff;font-weight:700;text-decoration:none;margin-left:5px;padding:6px 10px}.edd-admin-notice-top-of-page .button-link:active,.edd-admin-notice-top-of-page .button-link:focus,.edd-admin-notice-top-of-page .button-link:hover{color:#fff;text-decoration:none}@media screen and (min-width:601px){.edd-admin-notice-top-of-page .button-link{top:1px}}@media screen and (min-width:783px){.edd-admin-notice-top-of-page .button-link{right:9px}}#edd-admin-notice-five-star-review{display:-ms-grid;display:grid}#edd_dashboard_sales .edd-promo-notice{border-bottom:1px solid #c3c4c7}.edd-review-actions{display:flex;gap:6px;margin:0 0 16px}.edd-promo-notice .edd-peeking{align-self:flex-end;justify-self:flex-end;margin-right:16px;margin-bottom:-1px}@media screen and (max-width:782px){#edd-admin-notice-five-star-review.notice .edd-peeking{margin-bottom:-6px}}@media screen and (min-width:480px){.edd-promo-notice.notice-info .edd-peeking{justify-self:flex-start;margin-right:0;margin-left:250px}}.edd-promo-notice .edd-peeking,.edd-review-step{-ms-grid-row:1;grid-area:1/-1}.edd-promo-notice__overlay{display:none;position:fixed;background:rgba(16,21,23,.75);top:0;right:0;bottom:0;left:160px;z-index:11;justify-content:center;align-items:center}.folded .edd-promo-notice__overlay{left:36px}@media screen and (max-width:782px){.edd-promo-notice__overlay{left:0}}.edd-admin-notice-overlay{display:none;background-color:#fff;padding:2.5em;text-align:center;max-width:650px;position:relative;flex-direction:column}.edd-promo-notice__overlay .edd-admin-notice-overlay{display:flex}.edd-admin-notice-overlay h2{line-height:1.6em;margin:0 auto;max-width:540px}.edd-admin-notice-overlay .edd-promo-notice__features{text-align:left;display:-ms-grid;display:grid;-ms-grid-columns:(auto)[3];grid-template-columns:repeat(3,auto);margin:2em auto;gap:0 1.5em}.edd-admin-notice-overlay .edd-promo-notice__features li{display:flex;gap:.5em;align-items:center}@media screen and (max-width:600px){.edd-admin-notice-overlay .edd-promo-notice__features{-ms-grid-columns:unset;grid-template-columns:unset}}.edd-admin-notice-overlay .button-primary{padding:4px 36px;margin:0 auto .5em;max-width:360px}.edd-admin-notice-overlay__link{color:#101517}.edd-admin-notice-overlay .edd-promo-notice-dismiss.button-link{position:absolute;color:#537994;text-decoration:none;font-size:2em;top:0;right:.5em}.edd-admin-notice-overlay .edd-promo-notice-dismiss.button-link:active,.edd-admin-notice-overlay .edd-promo-notice-dismiss.button-link:hover{color:#101517}@media screen and (max-width:782px){.edd-admin-notice-overlay{margin:1em}}.edd-paypal-account-status ul{margin-left:25px}.edd-paypal-account-status>li{margin-bottom:1em}.edd-paypal-account-status ul:not(.edd-paypal-webhook-events) li{margin:.25em 0}.edd-paypal-account-status .dashicons-yes{color:#008a20}.edd-paypal-account-status .dashicons-no{color:#d63638}.edd-overlay{position:fixed;z-index:1052;top:0;right:0;bottom:0;left:160px;background-color:#141b38;opacity:.5;transition:.5s}.edd-slide-in{transform:translateX(100%)!important;-webkit-transform:translateX(100%)!important}#edd-notifications-panel{background-color:#fff;height:100%;width:100%;max-width:570px;position:fixed;z-index:1053;top:0;right:0;bottom:0;overflow-x:hidden;transition:.5s;transform:translateX(0);-webkit-transform:translateX(0)}body.admin-bar #edd-notifications-panel{top:32px}@media screen and (max-width:600px){body.admin-bar #edd-notifications-panel{top:46px}}#edd-notifications-header{display:flex;align-items:center;padding:0 30px;color:#fff;background-color:#0c5d95}#edd-notifications-header h3{color:#fff;flex:1}#edd-notifications-header .edd-close{background:none;border:none;color:#fff;cursor:pointer}#edd-notifications-body{padding:30px}.edd-notification{display:flex;gap:20px;margin-bottom:20px}.edd-notification--icon{color:#00aa63}.edd-notification--icon.edd-notification--icon-info{color:#005ae0}.edd-notification--icon.edd-notification--icon-warning{color:#f18200}.edd-notification--icon.edd-notification--icon-error{color:#df2a4a}.edd-notification--body{flex:1}.edd-notification--header{align-items:center;display:flex;justify-content:space-between;gap:5px;margin-bottom:7px}.edd-notification--title{color:#141b38;flex:1;font-size:16px;font-weight:600;margin:0}.edd-notification--date{color:#71747e;font-size:12px}.edd-notification--actions{flex-wrap:wrap;display:flex;align-items:center;gap:5px;margin-top:10px}.edd-notification--dismiss{background:none!important;border:none!important;box-shadow:none!important;color:#71747e!important;cursor:pointer;padding:0 10px;text-decoration:underline}.edd-notification--dismiss:hover{text-decoration:none}.edd-dialog{display:none}.edd-item-header-small{padding-bottom:20px;border-bottom:1px solid #e5e5e5;display:flex;justify-content:flex-start;align-items:center;gap:6px}.edd-item-header-small span{font-weight:600;font-size:15px}.edd-admin-order-status-badge,.edd-status-badge{padding:2px 7px;border-radius:4px;background:#ececec;display:inline-flex;align-items:center;gap:2px}.edd-admin-order-status-badge__icon,.edd-status-badge__icon{opacity:.8}.edd-admin-order-status-badge--error,.edd-admin-order-status-badge--expired,.edd-admin-order-status-badge--failed,.edd-admin-order-status-badge--failing,.edd-admin-order-status-badge--red,.edd-admin-order-status-badge--rejected,.edd-admin-order-status-badge--revoked,.edd-status-badge--error,.edd-status-badge--expired,.edd-status-badge--failed,.edd-status-badge--failing,.edd-status-badge--red,.edd-status-badge--rejected,.edd-status-badge--revoked{color:#ac3d3d;background:#ffd6d6}.edd-admin-order-status-badge--active,.edd-admin-order-status-badge--approved,.edd-admin-order-status-badge--complete,.edd-admin-order-status-badge--completed,.edd-admin-order-status-badge--edd_subscription,.edd-admin-order-status-badge--green,.edd-admin-order-status-badge--partially_refunded,.edd-admin-order-status-badge--success,.edd-status-badge--active,.edd-status-badge--approved,.edd-status-badge--complete,.edd-status-badge--completed,.edd-status-badge--edd_subscription,.edd-status-badge--green,.edd-status-badge--partially_refunded,.edd-status-badge--success{color:#017d5c;background:#e5f5f0}.edd-admin-order-status-badge--pending,.edd-admin-order-status-badge--warning,.edd-admin-order-status-badge--yellow,.edd-status-badge--pending,.edd-status-badge--warning,.edd-status-badge--yellow{color:#996800;background:#f5f2e5}.edd-admin-order-status-badge--blue,.edd-admin-order-status-badge--info,.edd-admin-order-status-badge--processing,.edd-admin-order-status-badge--trialling,.edd-status-badge--blue,.edd-status-badge--info,.edd-status-badge--processing,.edd-status-badge--trialling{color:#016087;background:#e5f1f5}.edd-pro-upgrade,.edd-pro-upgrade:hover{color:#1da867;font-weight:600;text-decoration:none}.wrap-licenses .edd-licenses__description{margin:2em 1em}.wrap-licenses .form-table,.wrap-licenses caption,.wrap-licenses tfoot,.wrap-licenses th,.wrap-licenses thead,.wrap-licenses tr{display:block}@media screen and (min-width:600px){.wrap-licenses .form-table,.wrap-licenses caption,.wrap-licenses tfoot,.wrap-licenses th,.wrap-licenses thead,.wrap-licenses tr{display:unset}}.wrap-licenses tbody{display:-ms-grid;display:grid;gap:1em}.wrap-licenses .form-table tr{margin:0;background:#fff;border:1px solid #dcdcde;border-radius:3px;padding:0;box-sizing:border-box;display:flex;flex-direction:column;justify-content:space-between}@media screen and (min-width:600px){.wrap-licenses .form-table tr{display:-ms-grid;display:grid;-ms-grid-columns:200px 1fr;grid-template-columns:200px 1fr}}.wrap-licenses .form-table th{background:#f9f9f9;margin-bottom:2.5em;padding:1em;border-bottom:1px solid #dcdcde;width:unset}@media screen and (min-width:600px){.wrap-licenses .form-table th{border-bottom:none;margin-bottom:0;display:flex;align-items:center}}.wrap-licenses .form-table td{margin:0;padding:0;display:flex;flex-direction:column;gap:2.5em;flex-grow:1}@media screen and (min-width:600px){.wrap-licenses .form-table td{flex-direction:row;gap:unset}}.wrap-licenses .form-table td input.regular-text{margin:0;width:100%;max-width:250px}.wrap-licenses .form-table td button{margin:0}.wrap-licenses .form-table .edd-license__control{flex-grow:1;padding:0 1em;display:flex;gap:4px;align-items:center;justify-content:center}@media screen and (min-width:600px){.wrap-licenses .form-table .edd-license__control{justify-content:flex-end}}.wrap-licenses .form-table .edd-licensing__actions{display:flex;gap:4px}.wrap-licenses .edd-license-data[class*=edd-license-]{background:#f9f9f9;padding:1em;border-top:1px solid #dcdcde;margin:0;width:100%;box-sizing:border-box;display:flex;align-items:flex-end}.wrap-licenses .edd-license-data[class*=edd-license-] a{color:#444}.wrap-licenses .edd-license-data[class*=edd-license-] a:hover{text-decoration:none}@media screen and (min-width:600px){.wrap-licenses .edd-license-data[class*=edd-license-]{border-top:none;width:unset;flex-basis:100%;align-items:center}.wrap-licenses .edd-license-data[class*=edd-license-]:not(:only-child){flex:0 1 300px}}.wrap-licenses .edd-license-data.license-expires-soon-notice{background-color:#00a0d2;color:#fff;border-color:#00a0d2}.wrap-licenses .edd-license-data.edd-license-expired{background-color:#e24e4e;color:#fff;border-color:#e24e4e}.wrap-licenses .edd-license-data.edd-license-error,.wrap-licenses .edd-license-data.edd-license-invalid,.wrap-licenses .edd-license-data.edd-license-item_name_mismatch,.wrap-licenses .edd-license-data.edd-license-missing,.wrap-licenses .edd-license-data.edd-license-site_inactive{background-color:#ffebcd;border-color:#ffebcd}.wrap-licenses .edd-license-data p{font-size:13px;margin-top:0}.wrap-licenses .edd-license-data.edd-license-expired a,.wrap-licenses .edd-license-data.license-expires-soon-notice a{color:#fff}.wrap-licenses .edd-license-data.edd-license-expired a:hover,.wrap-licenses .edd-license-data.license-expires-soon-notice a:hover{text-decoration:none}.edd-settings-content{max-width:1440px}.edd-settings-color,.edd-settings-colors{display:flex;flex-wrap:wrap;gap:1em}.edd-settings-color{flex-direction:column}.edd-upload-button-wrapper{width:100%;display:flex;gap:5px}.edd-upload-button-wrapper button.edd_settings_upload_button{margin-bottom:0}#edd-payment-gateways a.button.edd-settings__button-settings{position:absolute;right:2em;min-height:unset;height:1.5em;width:1.5em;border:none;background-color:#f9f9f9}#edd-payment-gateways a.button.edd-settings__button-settings,#edd-payment-gateways a.button.edd-settings__button-settings:active,#edd-payment-gateways a.button.edd-settings__button-settings:hover{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yOSA2LjI5M2wtMS41MTUuODc1YTUuODczIDUuODczIDAgMDEwIDEuNjY0bDEuNTE1Ljg3NS0yLjE2NiAzLjc1My0xLjUxNi0uODc1YTUuODI3IDUuODI3IDAgMDEtMS40NDEuODMzdjEuNzQ5SDUuODM0di0xLjc1YTUuODI1IDUuODI1IDAgMDEtMS40NDEtLjgzMmwtMS41MTYuODc1TC43MSA5LjcwN2wxLjUxNi0uODc1YTUuODc4IDUuODc4IDAgMDEwLTEuNjY0TC43MSA2LjI5MyAyLjg3NyAyLjU0bDEuNTE2Ljg3NmE1LjgyNyA1LjgyNyAwIDAxMS40NC0uODMzVi44MzNoNC4zMzR2MS43NWE1LjgzIDUuODMgMCAwMTEuNDQuODMzbDEuNTE3LS44NzYgMi4xNjYgMy43NTN6TTggMTAuMzMzYTIuMzMzIDIuMzMzIDAgMTAwLTQuNjY2IDIuMzMzIDIuMzMzIDAgMDAwIDQuNjY2eiIgZmlsbD0iIzZCNzI4MCIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTUuMjkgNi4yOTNsLTEuNTE1Ljg3NWE1Ljg3MyA1Ljg3MyAwIDAxMCAxLjY2NGwxLjUxNS44NzUtMi4xNjYgMy43NTMtMS41MTYtLjg3NWE1LjgyNyA1LjgyNyAwIDAxLTEuNDQxLjgzM3YxLjc0OUg1LjgzNHYtMS43NWE1LjgyNSA1LjgyNSAwIDAxLTEuNDQxLS44MzJsLTEuNTE2Ljg3NUwuNzEgOS43MDdsMS41MTYtLjg3NWE1Ljg3OCA1Ljg3OCAwIDAxMC0xLjY2NEwuNzEgNi4yOTMgMi44NzcgMi41NGwxLjUxNi44NzZhNS44MjcgNS44MjcgMCAwMTEuNDQtLjgzM1YuODMzaDQuMzM0djEuNzVhNS44MyA1LjgzIDAgMDExLjQ0LjgzM2wxLjUxNy0uODc2IDIuMTY2IDMuNzUzek04IDEwLjMzM2EyLjMzMyAyLjMzMyAwIDEwMC00LjY2NiAyLjMzMyAyLjMzMyAwIDAwMCA0LjY2NnoiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjUiLz48L3N2Zz4=);background-size:1em;background-repeat:no-repeat;background-position:50%}.edd-plugin__active #edd-payment-gateways a.button.edd-settings__button-settings{display:block}.edd-settings__list--disc{list-style:disc;list-style-position:inside}.edd-hidden{display:none}.edd-clearfix:after{content:"";display:table;clear:both}.edd-notice .notice-dismiss,.edd-wrap a{text-decoration:none}.wp-core-ui .edd-delete,a.edd-delete{color:#a00}.wp-core-ui .edd-delete:hover,a.edd-delete:hover{color:red}body.post-type-download #contextual-help-link-wrap,body.post-type-download #screen-options-link-wrap{top:5px!important}body.post-type-download #screen-meta{margin:0 0 -1px -20px}#edd-header{border-top:5px solid #0c5d95;border-bottom:1px solid #c3c4c7;padding:20px 0;margin-left:-20px;background:#fff}#edd-header-wrapper{display:flex;justify-content:space-between;padding:0 20px;align-items:center}#edd-header img{display:block;max-width:300px;margin:0}.edd-header-page-title-wrap{font-size:1.75em;margin-top:-5px;margin-right:auto;padding-left:7px}.edd-header-separator{margin-top:-2px;opacity:.25}.edd-header-page-title{font-weight:400;font-size:1em;line-height:1.3em;display:inline}.edd-header-page-title-wrap .button{margin-left:5px}.no-js #edd-header-actions{display:none}#edd-header .edd-round{position:relative;background-color:#f3f4f5;border-radius:50%;width:40px;height:40px;display:flex;align-items:center;justify-content:center;margin-left:10px;cursor:pointer;transition:background-color .2s ease}button.edd-round{border:none}#edd-header button.edd-round:hover{background-color:#e5e5e5}button.edd-round:active,button.edd-round:focus{outline:2px solid #0c5d95}#edd-header .edd-number{position:absolute;background-color:#df2a4a;width:16px;height:16px;font-weight:600;font-size:10px;color:#fff;top:-8px;left:50%;transform:translateX(-50%);margin:0;animation:bounce 2s 5}#edd-header .edd-number.edd-hidden{display:none!important}#edd-header .edd-round svg{width:20px;height:20px}@media screen and (max-width:840px){#edd-header img,.edd-header-separator{display:none}}.edd_datepicker{height:29px}.edd-from-to-wrapper input{width:105px;margin:0;position:relative;z-index:1}.edd-from-to-wrapper input[name*=start],.edd-from-to-wrapper input[name=filter_from]{border-top-right-radius:0;border-bottom-right-radius:0}.edd-from-to-wrapper input[name*=end],.edd-from-to-wrapper input[name=filter_to]{margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.edd-from-to-wrapper input:focus{z-index:2;position:relative}.edd-settings-sub-nav{margin:0 0 10px;width:100%;border-bottom:1px solid #ccc;box-shadow:0 1px 1px rgba(0,0,0,.04)}.edd-settings-sub-nav a{padding:13px;display:block}.edd-settings-sub-nav a.current{border-bottom:4px solid #000;padding-bottom:9px}.admin-color-fresh .edd-settings-sub-nav a.current{border-bottom-color:#00a0d2}.admin-color-blue .edd-settings-sub-nav a.current{border-bottom-color:#096484}.admin-color-coffee .edd-settings-sub-nav a.current{border-bottom-color:#c7a589}.admin-color-ectoplasm .edd-settings-sub-nav a.current{border-bottom-color:#a3b745}.admin-color-midnight .edd-settings-sub-nav a.current{border-bottom-color:#e14d43}.admin-color-ocean .edd-settings-sub-nav a.current{border-bottom-color:#627c83}.admin-color-sunrise .edd-settings-sub-nav a.current{border-bottom-color:#be3631}.admin-color-light .edd-settings-sub-nav a.current{border-bottom-color:#888}.admin-color-evergreen .edd-settings-sub-nav a.current{border-bottom-color:#36533f}.admin-color-mint .edd-settings-sub-nav a.current{border-bottom-color:#4f6d59}.download_page_edd-settings .edd-check-wrapper{clear:both}.download_page_edd-settings .form-table tr>th>h3,.download_page_edd-settings .form-table tr>th>strong{font-size:1.2em;font-weight:600;margin:0 auto}.edd-sortable-list{margin:0;width:300px;position:relative}.edd-sortable-list li{margin:0;padding:0;position:relative;height:28px;cursor:move}.edd-sortable-list li label *{vertical-align:middle}.edd-sortable-list li label:after{display:block;width:17px;height:17px;position:absolute;right:6px;top:0;color:#aaa;font-family:dashicons;font-size:17px;content:"";cursor:move}.form-table .edd-sortable-list li label{display:block;height:28px;padding:0;margin:0}.edd-sortable-list .payment-icon{width:32px;height:24px;position:relative;top:-2px;margin-right:5px}.edd-help-tip{cursor:help;margin-top:-2px;font-size:24px;color:grey}.edd-ui-tooltip{position:absolute;background:#333!important;border-width:1px!important;border-radius:3px!important;box-shadow:1px 1px 2px 1px hsla(0,0%,83.9%,.5)!important;color:#dedede!important;max-width:300px!important;padding:7px!important;text-rendering:optimizeLegibility;text-shadow:none!important;z-index:9999!important}.download_page_edd-settings .edd-settings-payment-icon-wrapper{margin-top:5px}.download_page_edd-settings .edd-settings-payment-icon-wrapper input{margin-top:1px}.download_page_edd-settings .form-table .edd-settings-payment-icon-wrapper input[type=checkbox]+label{margin:0;display:inline-block}.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-icon-image{margin-right:5px;width:32px;display:inline-block;vertical-align:middle}.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-option-name{vertical-align:middle}.download_page_edd-settings .taxrates td,.download_page_edd-settings .taxrates th{padding:8px 10px}.download_page_edd-settings .taxrates td{line-height:1.5em;vertical-align:top;margin:0}.download_page_edd-settings .taxrates .regular-text{width:100%}#TB_window{overflow:hidden}#TB_title{padding:5px}#TB_ajaxContent{width:calc(100% - 30px)!important;padding:15px;margin:0;height:calc(100% - 118px)!important}#TB_ajaxWindowTitle{font-size:18px;font-weight:600;line-height:30px}#TB_closeWindowButton{right:6px;top:6px}#choose-download-wrapper{width:100%}#choose-download-wrapper .wrap{overflow-y:scroll;margin:0;padding:0;height:calc(100% - 50px)}#choose-download-wrapper .submit-wrapper{position:absolute;width:100%;bottom:0;padding:0;margin:0 0 0 -15px;text-align:right}#choose-download-wrapper .submit-wrapper div{background-color:#fafafa;padding:15px;border-top:1px solid #ddd}.wp-media-buttons .button.edd-thickbox{padding-left:0}.wp-media-buttons .button.edd-email-tags-inserter .dashicons{margin-top:-2px}.download_page_edd-payment-history .edit-post-editor-regions__header{flex-shrink:0;height:auto;border-bottom:1px solid #e2e4e7;z-index:30;position:sticky;top:32px;margin-left:-20px}@media screen and (max-width:782px){.download_page_edd-payment-history .edit-post-editor-regions__header{position:static;top:46px}}.download_page_edd-payment-history .edit-post-header{height:56px;background:#fff;display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;max-width:100%;box-sizing:border-box;padding:4px 20px}@media screen and (max-width:782px){.download_page_edd-payment-history .edit-post-header{padding-left:10px;padding-right:10px}}@media(min-width:280px){.download_page_edd-payment-history .edit-post-header{flex-wrap:nowrap}}.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar{order:0}.download_page_edd-payment-history .edit-post-header .edit-post-header__settings{order:1}.download_page_edd-payment-history .edit-post-header #publishing-action,.download_page_edd-payment-history .edit-post-header .edit-post-header__settings,.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar{display:flex;align-items:center}.download_page_edd-payment-history .edit-post-header #publishing-action .spinner{margin:0 5px 0 0}.download_page_edd-payment-history .edit-post-header .button-primary{margin:2px;height:34px;line-height:32px;font-size:13px}#edd-order-items .hndle{display:flex;align-items:center;justify-content:space-between}#edd-order-items .hndle .edd-toggle{font-weight:400}.edd-add-order-item td{vertical-align:middle}.edd-add-order-item input{width:80%}.edd-add-order-item input[readonly]{color:#555;background:none;border:1px solid transparent;box-shadow:none}.order-customer-info .customer-details-wrap{margin:15px 0;align-items:center}.order-customer-info .customer-details-wrap .spinner{margin:0}.order-customer-info .customer-details{display:flex;flex-direction:column}.order-customer-info .customer-details .customer-since{color:#666;display:block;margin:4px 0 6px}.order-customer-info .customer-details>span{margin-bottom:5px}.edd-order-add-download-select .spinner{display:none}table.edd-order-overview-summary{border-width:0;table-layout:fixed}table.edd-order-overview-summary--refund{border-width:0}@media screen and (min-width:782px){.edd-order-overview .column-right{text-align:right}}.edd-ml-auto{margin-left:auto!important}@media screen and (min-width:782px){.edd-ml-lg-auto{margin-left:auto!important}}.edd-ml-auto+.edd-ml-auto{margin-left:10px!important}.edd-order-overview-summary__items-name{align-self:flex-start}.edd-order-overview-summary__items>:nth-child(odd){background-color:#f9f9f9}@media screen and (min-width:782px){.edd-order-overview-summary__items tr:last-child td,.edd-order-overview-summary__items tr:last-child th{border-bottom:1px solid #e5e5e5}}@media screen and (max-width:782px){.edd-order-overview-summary .row-actions>*,.edd-order-overview-summary__items-name .row-actions{display:block!important}.edd-order-overview-summary .row-actions>:not(:first-child):before{display:none}}.edd-order-overview-summary th:not(.column-primary){width:100px}.edd-order-overview-summary .row-actions>:not(:first-child):before{color:#999;content:" | "}.edd-order-overview-summary .row-actions .text{color:#555}.edd-order-overview-summary .removable{display:flex;align-items:center;position:relative}.edd-order-overview-summary .removable .delete{display:inline-block;margin-right:10px;margin-left:-8px;padding:10px;border-right:1px solid #e5e5e5;color:#a00}.edd-order-overview-summary .removable .delete:hover{color:#dc3232}.edd-order-overview-summary__adjustments .column-primary{font-weight:600}.edd-order-overview-summary__adjustments td small{font-weight:400}.edd-order-overview-summary__subtotal .column-primary,.edd-order-overview-summary__tax tr:first-of-type .column-primary,.edd-order-overview-summary__total .column-primary{font-weight:600}.edd-order-overview-summary__adjustments td,.edd-order-overview-summary__subtotal td,.edd-order-overview-summary__tax td,.edd-order-overview-summary__total td{vertical-align:middle}.edd-order-overview-summary__tax td small,.edd-order-overview-summary__total td small{font-weight:400}.edd-order-overview-summary__total .total{color:#017d5c;display:inline-block}.edd-order-overview-summary__total .total.is-negative{color:#a00}@media screen and (min-width:783px){.edd-order-overview-summary__adjustments .removable .delete{margin-left:-50px}.edd-order-overview-summary__total .total{font-size:150%;padding-top:5px;padding-bottom:5px}}.edd-order-overview-summary__total tr:last-child td:not(:first-of-type),.edd-order-overview-summary__total tr:last-child th{border-top:1px solid #e5e5e5}.edd-order-overview-summary__total .notice{margin:-1px}.edd-order-overview-summary__total .notice p{font-weight:400;margin:.5em 0}.edd-order-overview-summary__refunds .column-primary{font-weight:600}.edd-order-overview-summary__refunds td small{font-weight:400}.edd-order-overview-summary__refunds tr:first-child td{border-top:1px solid #e5e5e5}#edd-order-overview-actions.inside{border-top:1px solid #ccd0d4;margin-top:0;display:flex;align-items:center;flex-wrap:wrap;justify-content:space-between}#edd-order-overview-actions.inside:empty{padding:0;border-top:0}#edd-order-overview-actions.inside>div{display:flex;align-items:center}#edd-order-overview-actions .edd-order-overview-actions__notice{flex-basis:100%;margin-top:15px}.edd-order-overview-actions .button{width:100%;margin-bottom:12px}.edd-order-overview-actions .button:last-of-type{margin-bottom:0}@media screen and (min-width:782px){.edd-order-overview-actions .button{width:auto;margin-left:12px;margin-bottom:0}.edd-order-overview-actions .button:first-of-type{margin-left:auto}}.edd-order-overview-actions__locked{font-style:italic;opacity:.8}@media screen and (max-width:782px){.edd-order-overview-actions__locked{margin-bottom:12px}}.edd-order-overview-actions__refund .dashicons{margin-right:8px}.edd-dialog .ui-button-icon-only{font-size:0}.download_page_edd-payment-history .ui-dialog,.download_page_edd-payment-history .ui-dialog-content{overflow:visible}.edd-order-overview-modal form>p{margin-top:0}.edd-order-overview-modal fieldset legend,.edd-order-overview-modal form label{display:block;margin-bottom:4px}.edd-order-overview-modal fieldset{margin-bottom:calc(1em - 3px)}.edd-order-overview-modal fieldset>p{margin:2px 0 3px}.edd-order-overview-modal form .submit{margin:0 -16px -16px;padding:16px;background:#fcfcfc;border-top:1px solid #dfdfdf;display:flex;align-items:center}.edd-order-overview-modal form .submit .spinner{margin:0}.edd-order-overview-add-item [for=auto-calculate]{display:flex;align-items:center}.edd-order-overview-add-item [for=auto-calculate] input[type=checkbox]{margin-top:0}.edd-order-overview-add-item [for=auto-calculate] .label{line-height:1.15;margin-left:8px}.edd-order-overview-add-item [for=auto-calculate] .label small{margin-top:4px;display:block;opacity:.75}.edd-order-overview-add-adjustment .notice,.edd-order-overview-add-item .notice{margin:0 0 1rem}.edd-order-overview-add-adjustment #description,.edd-order-overview-add-discount select{width:100%}.edd-order-overview-error{font-style:italic;color:#a00;display:block;margin:4px 0}.edd-order-copy-download-link textarea{width:100%}.edd-order-resend-email-chooser legend{font-weight:700;margin-bottom:4px}.edd-order-resend-email-chooser p{margin:4px 0}.edd-notes .edd-note{padding:10px;background-color:#ffe;border:1px solid #cc0;width:100%;position:relative;margin-bottom:10px;box-sizing:border-box;overflow:hidden}.edd-notes .edd-note.deleting{opacity:.5}.edd-notes .edd-note__header{display:flex;align-items:center}.edd-add-note .spinner{float:none;display:inline-block;margin:0}.edd-notes .edd-note time{font-size:11px;color:#aaa}.edd-notes .edd-note .edd-note-author{margin-right:5px}.edd-notes .edd-note .edd-delete-note{color:#a00;font-weight:700;text-decoration:none;margin-left:auto}.edd-notes .edd-note .edd-delete-note:hover{color:#888}.edd-notes .edd-note p:last-child{margin-bottom:0}.edd-notes .edd-no-notes{margin:4px 0 10px}textarea[name=edd-note]{width:100%;min-height:70px;margin-top:0}.edd-notes-wrapper{width:80%}.edd-note-pagination{float:right;margin:-35px 5px 15px}.edd-note-pagination a,.edd-note-pagination span.page-numbers{padding:5px 8px;margin:2px;text-decoration:none}.edd-note-pagination a{border:1px solid #e5e5e5;background:#fcfcfc}.edd-note-pagination a:last-child,.edd-note-pagination span.page-numbers:last-child{margin-right:0}#edd-products{height:100px;min-width:200px}#edd-add-discount input[type=text],#edd-edit-discount input[type=text]{width:300px}#edd-add-discount .edd-discount-datetime input,#edd-edit-discount .edd-discount-datetime input{vertical-align:middle}#edd-add-discount input[type=text].edd_datepicker,#edd-edit-discount input[type=text].edd_datepicker{display:inline-block;width:183px}#edd-edit-discount textarea{height:100px}.edd-amount-type-wrapper{position:relative;display:flex}.edd-amount-type-wrapper select{border-top-left-radius:0;border-bottom-left-radius:0;width:auto!important}.edd-amount-type-wrapper #edd-amount{border-top-right-radius:0;border-bottom-right-radius:0;margin-right:-2px;padding:0 8px;width:unset;max-width:125px}.edd-amount-type-wrapper input:focus{z-index:2}.post-type-download .tablenav.top .edd-select{margin-right:6px}.wp-list-table.addresses .column-primary strong,.wp-list-table.customers .column-primary strong,.wp-list-table.discounts .column-primary strong,.wp-list-table.emails .column-primary strong,.wp-list-table.orderadjustments .column-primary strong,.wp-list-table.orderitems .column-primary strong,.wp-list-table.orders .column-primary strong{font-size:14px}.wp-list-table.customers .column-primary .avatar,.wp-list-table.emails .column-customer .avatar{float:left;margin-right:10px;margin-top:1px;border-radius:5px}.wp-list-table.orders div.order-list-email{font-size:.85em;color:#888}.wp-list-table.orders th.column-amount{width:100px}.wp-list-table .row-actions span.activate a{color:green}.wp-list-table .row-actions span.refund a{color:#836fff}.wp-list-table .row-actions span.cancel a{color:#cc8c00}.wp-list-table .row-actions span.cancel a:hover,.wp-list-table .row-actions span.refund a:hover{opacity:.8}.wp-list-table .type-download .row-actions{color:#999}.no-js.edit-tags-php.post-type-download .wp-heading-inline{position:absolute;top:0}.no-js.edit-tags-php.post-type-download .nav-tab-wrapper{margin-top:50px}.download_page_edd-customers .wrap .nav-tab-wrapper .page-title-action,.download_page_edd-discounts .wrap .nav-tab-wrapper .page-title-action,.download_page_edd-payment-history .wrap .nav-tab-wrapper .page-title-action,.edit-tags-php.post-type-download .wrap .nav-tab-wrapper .page-title-action{top:3px;margin-left:10px;line-height:24px}#edd-payments-filter ul.subsubsub{margin-bottom:8px}tr.status-refunded td{background:#cecece;border-top-color:#ccc}marquee{padding:0;margin:0}@media handheld,only screen and (max-width:640px){.wp-list-table.downloads th{width:auto!important}}#edd-download-link-textarea{width:100%}.edd_files_name_label{width:225px;float:left}.edd_files_url_label{width:220px;float:left}#postbox-container-1 .edd_files_name_label,#postbox-container-1 .edd_files_url_label{width:80px}#edd_product_files .inside,#edd_product_prices .inside{margin-bottom:0}textarea#edd-payment-note{width:100%;height:4em;margin:0}#edd-order-items .row .edd-purchased-files-list-wrapper .download{line-height:1.4}#edd-order-items .edd-purchased-files-list-wrapper .edd-purchased-option{color:#666}input[class*=edd-price-field]{max-width:125px}#edd-order-download-quantity[type=number].small-text,#edd-order-download-tax[type=text].small-text,[class*=item_] [class*=edd-payment-details-download-][type=number].small-text{height:25px}#edd-order-download-quantity[type=number].small-text,.item_price .edd-payment-details-download-quantity[type=number].small-text{width:55px}#edd-order-download-tax[type=text].small-text,.item_tax .edd-payment-details-download-item-tax[type=number].small-text{width:80%;max-width:125px}#edd_product_notes_field{display:block;margin:12px 0 0;height:4em;width:100%}.edd-metabox-title-action{margin:0;float:right;padding:4px 8px;position:relative;top:-1px;text-decoration:none;border:1px solid #ccc;border-radius:2px;background:#f7f7f7;text-shadow:none;font-weight:600;font-size:10px;line-height:normal;color:#0073aa;cursor:pointer;outline:0}.edd-metabox-title-action:hover{border-color:#008ec2;background:#00a0d2;color:#fff}.edd-edit-purchase-element .tablenav{padding:2px 10px 8px}.edd-edit-purchase-element .edd-order-children-wrapper{margin:0 -1px}.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 table{border-top:none;border-bottom:none}.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 .tablenav{display:none}.edd-edit-purchase-element[class*=columns-] ul li{padding-right:1%}#edd-edit-order-form .column:nth-child(odd),#edd-edit-order-form .columns-4 .column:nth-child(odd),#edd-edit-order-form .columns-5 .column:nth-child(3n+1){margin-right:0}#edd-edit-order-form input.large-text{width:90%}.edd-edit-purchase-element ul li.item_price{width:15%}.edd-edit-purchase-element ul li.item_price.item_quantity{width:25%}.edd-edit-purchase-element ul li.item_tax{width:15%}.edd-edit-purchase-element ul li.price{width:20%}.edd-admin-box-inside{border-bottom:1px solid #f1f1f1;clear:both;padding:12px;margin:0;word-wrap:break-word}.edd-admin-box-inside--row{display:flex;flex-wrap:wrap;word-break:break-all;justify-content:space-between;align-items:center}.edd-admin-box-inside>p{margin:8px 3px}.edd-admin-box-inside .strong{font-weight:600}.edd-admin-box div:not(.edd-admin-box-inside--row) .label{display:block;margin-bottom:4px;margin-right:0}.edd-admin-box .label--has-tip{display:flex;align-items:center}.edd-admin-box .label--has-tip .edd-help-tip{margin-top:0;font-size:20px}.edd-admin-box div:not(.edd-admin-box-inside--row) .label--has-checkbox{margin-bottom:0}.edd-payment-fees .fee-label{color:#666;font-weight:400}.edd-admin-box .right{float:right}#edd-order-refunds-list{padding-left:25px}#poststuff .edd-order-data .inside{margin:0;padding:0}.edd-order-data .edd-select-chosen{width:130px!important}.edd-order-data input.edd_datepicker{width:180px}.edd-order-data input[type=number].edd-payment-time-hour,.edd-order-data input[type=number].edd-payment-time-min{width:50px}.edd-order-data .edd-tax-rate{color:#9c9c9c;font-style:italic;padding:5px}#edd_general_logs p{margin:0;padding:0}.edd-admin-box-inside span.label{margin-right:10px}#edd-order-resend-receipt .inside{margin-top:11px}.edd-order-resend-receipt-header{font-size:14px;line-height:1.4}.edd-admin-box-inside:last-child{border-bottom:0}#edd-edit-order-form .data-payment-key{word-break:break-all}.edd-order-update-box #major-publishing-actions .button-secondary{margin-right:10px}.edd-order-update-box .button-primary{margin-right:0}.edd-edit-purchase-element .edd-select-chosen{width:196px}.edd-edit-purchase-element ul{clear:both;display:block}#edd-customer-details .actions{float:right}.order-data-address h3{margin:0 0 10px}.order-data-address #edd-order-address-country-wrap,.order-data-address #edd-order-address-state-wrap{display:inline-block;width:50%;max-width:300px}.edd-order-data input.small-text{margin:0}.edd-order-data input.med-text{margin:0;width:100px}.edd-edit-purchase-element ul li{display:block;line-height:1.4;position:relative;margin:0;vertical-align:middle;font-size:13px}.edd-edit-purchase-element .row{padding:12px}.edd-edit-purchase-element .row:not(:last-child){border-bottom:1px solid #eee}.edd-edit-purchase-element .row:nth-child(odd):not(.header){background-color:#f9f9f9}.edd-edit-purchase-element .row.header{padding:6px 12px;font-weight:600;vertical-align:top}.edd-edit-purchase-element ul{margin:0 0 15px}.edd-edit-purchase-element ul:last-of-type{margin-bottom:0}#edd-order-data .data span{color:#666;font-weight:600}.edd-edit-purchase-element .inside{padding:12px}.edd-edit-purchase-element .edd-purchased-download-title{font-size:14px;font-weight:500}.edd-edit-purchase-element .edd-purchased-download-title .deleted{color:#777}.edd-edit-purchase-element .edd-purchased-download-actions{color:#777;line-height:1.4}.edd-edit-purchase-element .edd-purchased-download-actions .edd-purchased-download-actions-label{font-weight:500}.edd-edit-purchase-element .edd-purchased-download-actions a{color:#777;font-size:12px}.edd-edit-purchase-element .edd-purchased-download-actions a:hover{color:#444}.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download{color:#a00}.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download:hover{color:red}.edd-add-adjustment-to-purchase,.edd-add-download-to-purchase{padding:15px;border-top:1px solid #e5e5e5;background-color:#f5f5f5}.edd-add-adjustment-to-purchase .chosen-container,.edd-add-download-to-purchase .chosen-container{width:90%!important;max-width:220px!important}.edd-add-adjustment-to-purchase .spinner,.edd-add-download-to-purchase .spinner{margin:0;float:none}.edd-add-download-to-purchase .edd-add-order-quantity{width:40px;height:29px;vertical-align:middle}.edd-add-adjustment-to-purchase .edd-add-adjustment-button,.edd-add-adjustment-to-purchase input[type=text],.edd-add-download-to-purchase .edd-add-order-item-button{height:29px}@media screen and (max-width:1284px){.edd-edit-purchase-element .edd-purchased-download-title{font-size:16px}.edd-edit-purchase-element ul li.item_price{width:22%}.edd-edit-purchase-element ul li.item_price.item_quantity{width:35%}.edd-edit-purchase-element ul li.item_tax{width:25%}.edd-edit-purchase-element ul li.price{width:20%}.edd-edit-purchase-element .edd-purchased-download-actions{padding-top:10px}}@media screen and (max-width:1024px){.edd-edit-purchase-element ul li.item_price.item_quantity{width:40%}.edd-edit-purchase-element ul li.price{width:24%}.edd-edit-purchase-element .edd-purchased-download-actions{padding-top:15px}.edd-edit-purchase-element .edd-purchased-download-actions,.edd-edit-purchase-element .edd-purchased-download-actions a{font-size:14px}}@media screen and (max-width:782px){.edd-edit-purchase-element ul li.item_price,.edd-edit-purchase-element ul li.item_price.item_quantity{padding-bottom:10px}.edd-edit-purchase-element ul li.item_price.item_quantity{width:35%}.edd-edit-purchase-element ul li.item_tax,.edd-edit-purchase-element ul li.price{width:20%;padding-bottom:10px}.edd-payment-details-download-amount,.edd-price-currency{font-size:16px}.order-data-column input[type=email]{padding:6px 10px}.edd-refund-submit-line-total td:last-of-type{flex:0 0 120px}#edd-item-tables-wrapper .addresses tbody tr{display:-ms-grid;display:grid}#edd-item-tables-wrapper .addresses tbody td:not(.no-items){padding-left:35%}}@media screen and (max-width:600px){.edd-edit-purchase-element ul li.item_price,.edd-edit-purchase-element ul li.item_price.item_quantity,.edd-edit-purchase-element ul li.item_tax{width:100%;padding-bottom:20px}.edd-edit-purchase-element .edd-add-download-to-purchase ul li.item_tax,.edd-edit-purchase-element ul li.price{width:100%;padding-bottom:0}.edd-edit-purchase-element .edd-add-download-to-purchase-actions{padding-top:15px}}#edd_product_stats .label{display:inline-block}#edd_product_stats .product-earnings-stats:before,#edd_product_stats .product-sales-stats:before{color:#82878c;font:normal 20px/1 dashicons;display:inline-block;padding:0 2px 0 0;position:relative;top:0;left:-1px;speak:none;text-decoration:none!important;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#edd_product_stats .product-sales-stats:before{content:""}#edd_product_stats .product-earnings-stats:before{content:""}body.dashboard_page_edd-upgrades.js .postbox .hndle{cursor:default}.edd_dashboard_widget table thead td{border-bottom:1px solid #ececec;color:#777}.edd_dashboard_widget .table_left{float:left;width:45%}.edd_dashboard_widget .table_right{float:right;width:45%}.edd_dashboard_widget .inside{font-size:12px}.edd_dashboard_widget td{padding:3px 0}.edd_dashboard_widget .b,.edd_dashboard_widget .t{line-height:1.5;vertical-align:middle}.edd_dashboard_widget .b{padding-right:6px;width:auto}.edd_dashboard_widget .t{font-size:12px;padding-right:12px;color:#777;width:100%}.edd_dashboard_widget .label_heading{border-top:1px solid #ececec;color:#8f8f8f;font-family:Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;display:block;padding-top:10px;margin:0 0 8px 12px}.edd_dashboard_widget .edd_dashboard_widget_subheading{border-top:1px solid #ececec;color:#8f8f8f;font-size:14px;padding-top:10px;margin:1em 0 0}.edd_dashboard_widget .edd_dashboard_widget_subheading+.table{margin:8px 0 0}.edd_dashboard_widget .edd_price_label{background:#00769c;border-radius:3px;color:#fff;font-size:10px;padding:2px 4px;margin-right:2px}.edd_dashboard_widget table{width:100%;margin-left:0;margin-bottom:1em}td.edd_order_label{width:80%}td.edd_order_price{text-align:right}@media handheld,only screen and (max-width:1000px){.edd_dashboard_widget .edd-recent-email{display:none}}body.download_page_edd-reports{overflow-y:scroll}.edd-chip{font-size:10px;font-weight:700;text-transform:uppercase;line-height:1;padding:3px;border-radius:3px;color:#fff;background-color:#444}.edd-reports-wrapper .postbox h2,.edd-reports-wrapper .postbox h3{font-size:1.3em}#edd-dashboard-widgets-wrap .metabox-holder{padding-top:0}.edd-reports-wrapper .postbox .edd-select{max-width:200px;vertical-align:baseline;margin-right:4px;margin-bottom:16px}.download_page_edd-reports #edd-item-wrapper{margin:0}#edd-dashboard-widgets-wrap .postbox h2,#edd-dashboard-widgets-wrap .postbox h3{cursor:default}.edd-date-range-options .edd_datepicker{width:105px}.edd-report-wrap{clear:both}.edd-report-wrap h3{clear:both;margin:0 0 20px}.edd-reports-chart,.edd-reports-table{margin-bottom:20px}.edd-admin--has-grid{display:grid;display:-ms-grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));grid-gap:20px}.edd-admin--has-grid .postbox{margin-bottom:0}.edd-admin--has-grid .edd-from-to-wrapper{display:flex;margin-bottom:16px;width:100%}.edd-admin--has-grid .edd-from-to-wrapper input{width:100%}.edd-admin--has-grid .edd-from-to-wrapper span{flex-grow:1}.edd-admin--has-grid form{display:flex;flex-direction:column;flex-wrap:wrap;position:relative}fieldset.edd-to-and-from-container{display:flex;gap:8px}fieldset.edd-to-and-from-container select{flex:0 0 calc(50% - 6px)}span.edd-to-and-from--separator{line-height:normal;-ms-grid-row-align:center;align-self:center;margin-bottom:16px}.edd-admin--has-grid .postbox .edd-select{max-width:100%;margin-right:0}.edd-admin--has-grid .button.updated-message:before,.edd-admin--has-grid .button.updating-message:before{vertical-align:text-bottom;margin:0 5px 0 0}.edd-import-export-form .edd-progress{background:#ddd;border-radius:15px;height:15px;flex-basis:100%}.edd-import-export-form .edd-progress div{background:#ccc;border-radius:15px;height:100%;width:0}.edd-import-export-form .notice-wrap{background-color:#f4f4f4;border-color:#eae9e9;border-style:solid;border-width:1px 0;padding:12px;overflow:auto;margin:20px -12px -23px;position:relative;width:100%;display:flex;justify-content:space-between;align-items:center}.notice-wrap div.notice{margin:0}.admin-color-fresh .edd-import-export-form .edd-progress div{background:#0073aa}.admin-color-light .edd-import-export-form .edd-progress div{background:#888}.admin-color-blue .edd-import-export-form .edd-progress div{background:#096484}.admin-color-coffee .edd-import-export-form .edd-progress div{background:#c7a589}.admin-color-ectoplasm .edd-import-export-form .edd-progress div{background:#a3b745}.admin-color-midnight .edd-import-export-form .edd-progress div{background:#e14d43}.admin-color-sunrise .edd-import-export-form .edd-progress div{background:#dd823b}.graph-option-section{float:left}.edd-report-filters-title span{display:block;padding:20px}#edd-graphs-filter form{padding:20px}#edd-graphs-filter label{vertical-align:inherit}#edd-graphs-filter .graph-option-section{display:inline-block;line-height:2em;margin:0 5px 0 0;padding:0}.download_page_edd-reports .section-content #post-body-content{float:none}.download_page_edd-reports .section-content select[name=range]{display:none}.edd-mix-totals{background-color:#fff;border:1px solid #e5e5e5;box-shadow:0 1px 1px rgba(0,0,0,.04);padding:10px}.edd-mix-chart{display:inline-block;width:49%;vertical-align:top}.edd-graph-notes{color:#9c9c9c}.edd-graph-notes span{display:block}.edd-pie-graph .legend{display:none}.edd-pie-legend{overflow:auto;margin-top:10px}.edd-legend-item-wrapper{color:#333;display:inline-block;font-size:8pt;padding:2px 5px 0;width:48%;height:20px}.edd-legend-color{border:1px solid #cfcfcf;display:inline-block;margin-right:5px;width:20px;height:15px}.edd-pie-legend-item{display:inline-block;vertical-align:top;width:80%}#edd-reports-tiles-wrap .metabox-holder{padding:0}#edd-reports-tiles-wrap #dashboard-widgets{overflow:auto}#edd-reports-tiles-wrap #dashboard-widgets .postbox-container{width:33.3%}.download_page_edd-reports .section-content .tablenav.top{display:none}#edd_tax_rates{margin:1em 0 0}[id*=edd-recapture-].button{font-size:16px;height:auto;padding:8px 14px;margin:6px 0 0}[id*=edd-recapture-].button .dashicons{line-height:29px;margin-right:8px}[id*=edd-recapture-].button .edd-loading,[id*=edd-recapture-].button .edd-loading:after{border-radius:50%;display:inline-block;width:14px;height:14px}[id*=edd-recapture-].button .edd-loading{position:relative;top:3px;margin-left:4px;box-shadow:0 0 2px rgba(0,0,0,.2);animation:edd-spinning 1.1s linear infinite;border:2px solid hsla(0,0%,100%,.5);border-left-color:#fff;font-size:14px;filter:alpha(opacity=0);transform:translateZ(0)}#edd-recapture-disconnect.button .edd-loading.dark{border-color:rgba(0,0,0,.2) rgba(0,0,0,.2) rgba(0,0,0,.2) #666;box-shadow:none}.recapture-notice{position:relative}@keyframes edd-spinning{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}#edd-chartjs-tooltip{position:absolute;background-color:#fff;border-radius:7px;transition:all .1s ease;pointer-events:none;transform:translate(-50%);font-size:12px;box-shadow:0 0 0 1px rgba(89,94,100,.1),0 15px 35px 0 rgba(89,94,100,.1),0 5px 15px 0 rgba(0,0,0,.12);min-width:120px;opacity:0}.edd-chartjs-tooltip-key{display:inline-block;width:10px;height:10px;margin-right:5px}#edd-send-test-summary-save-changes-notice .notice p{font-size:13px}#edd-send-test-summary-notice,#edd-send-test-summary-save-changes-notice{display:flex;margin-top:5px}.edd-graph .y1Axis{color:#edc240!important}.edd-graph .y2Axis{color:#afd8f8!important}.wp-list-table.apikeys input.code{width:100%;font-size:10px;cursor:text;background:#fff;border:1px solid #ddd;box-shadow:none;color:#555}.edd-toggle{position:relative;display:inline-block;overflow:visible}.edd-toggle input[type=checkbox]{display:inline-block;vertical-align:middle;position:relative;margin:0;padding:0;width:42px;min-width:42px;height:24px;background-color:#ccc;transition:background .2s ease;border-radius:34px;box-shadow:none;border:none}.edd-toggle .label{display:inline-block;vertical-align:middle;white-space:nowrap}.edd-toggle input[type=checkbox]:before{position:absolute;content:"";height:18px;width:18px;left:3px;bottom:3px;background-color:#fff;transition:transform .1s ease;border-radius:50%}@media only screen and (max-width:782px){.edd-toggle input[type=checkbox]:checked:before{margin:-.1875rem 0 0 -.25rem}}.edd-toggle input[type=checkbox]:checked{background-color:#007cba;background-color:var(--wp-admin-theme-color)}.edd-toggle input[type=checkbox]:active,.edd-toggle input[type=checkbox]:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px #7e8993}.edd-toggle input[type=checkbox]:checked:active,.edd-toggle input[type=checkbox]:checked:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #007cba;box-shadow:0 0 0 1px #fff,0 0 0 3px var(--wp-admin-theme-color)}.edd-toggle input[type=checkbox]:checked:before{transform:translateX(22px)}.edd-toggle .label+input,.edd-toggle input+.label{margin-left:5px}.download_page_edd-tools .tablenav .actions{overflow:visible}.edd_user_search_wrap{position:relative;overflow:visible}.edd_user_search_wrap .spinner{position:absolute;margin:0;padding:0;right:4px;top:-2px}.edd_user_search_wrap.loading .spinner{visibility:visible}.edd_user_search_results{position:absolute;left:0;top:20px}.edd_user_search_results a.edd-ajax-user-cancel{position:absolute;right:6px;top:2px}.edd_user_search_results ul{background:#fafafa;border:1px solid #dfdfdf;overflow-y:scroll;padding:0;margin:0;height:150px;width:185px;box-shadow:0 3px 5px rgba(0,0,0,.1)}.edd_user_search_results li{margin:0}.edd_user_search_results li a{display:block;text-decoration:none;padding:6px 10px}.edd_user_search_results li a:hover{background:#f5f5f5}.edd_user_search_results li.no-users{text-align:center;vertical-align:middle;display:block;line-height:150px;color:#bbb;text-transform:uppercase;font-size:11px}@media screen and (max-width:1100px){.edd-mix-chart{display:block;width:100%}}@media screen and (max-width:782px){.license-expiration-date-notice,.license-lifetime-notice,.license-null{padding-left:0}}@media screen and (max-width:600px){#edd-edit-order-form input.large-text{width:100%}}#edd-item-wrapper{background:#fff;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04);position:relative;margin-top:15px;display:flex}#edd-item-wrapper.full-width{max-width:100%}#edd-item-wrapper:after{content:"";display:block;clear:both;visibility:hidden;font-size:0;height:0}.edd-sections-wrap{clear:both;width:100%}.edd-sections-wrap .section-wrap{background-color:#fff;display:inline-block;z-index:2}.js .edd-sections-wrap .edd-vertical-sections:not(.meta-box) .section-wrap>div{min-height:500px;height:100%}.edd-sections-wrap .section-wrap .customer-section:not(:last-child){border-bottom:1px solid #eee}.edd-sections-wrap .section-wrap .customer-section table{margin-bottom:20px}.edd-sections-wrap .section-wrap{border-left:1px solid #e5e5e5}.edd-sections-wrap .section-wrap .section-content>*{padding:20px}.edd-sections-wrap .section-wrap .section-content h2{margin:0;padding-bottom:0}.edd-sections-wrap .section-wrap .avatar-wrap{float:left;padding-right:10px;text-align:center}.edd-sections-wrap .section-wrap img.avatar{border-radius:5px}.edd-sections-wrap .section-wrap .customer-id{position:absolute;right:0;top:0;padding:10px;background-color:#fafafa;border-bottom-left-radius:20%;border:1px solid #eee;border-top:none;border-right:none;font-family:monospace;font-size:18px;font-weight:600}.edd-item-info.customer-info input[type=password],.edd-item-info.customer-info input[type=text],.edd-item-info.customer-info select{width:200px;height:auto;box-shadow:none;transition:none;border:1px solid #ddd;margin:-5px 0 4px -2px;font-size:13px;padding:2px 4px}.edd-sections-wrap .section-wrap .customer-main-wrapper{float:left}.edd-sections-wrap .section-wrap .customer-main-wrapper input[name="customerinfo[name]"]{font-size:24px}.edd-sections-wrap .section-wrap .customer-address-wrapper{float:right;margin-top:-3px;margin-right:50px;width:202px}.edd-sections-wrap .section-wrap .info-wrapper{min-height:125px;overflow:visible}.edd-sections-wrap .section-wrap .customer-address span[data-key=address2],.edd-sections-wrap .section-wrap .customer-address span[data-key=address],.edd-sections-wrap .section-wrap .customer-address span[data-key=country]{display:block}.edd-sections-wrap .section-wrap a.delete{color:red;margin-right:5px;text-decoration:none}.customer-info{min-height:185px}.customer-info .customer-name{font-size:24px;font-weight:600}.customer-info .customer-name.editable{margin-bottom:6px}.customer-edit-link a{font-weight:400;text-decoration:none}.disconnect-user a{color:#aaa;font-size:20px}#customer-edit-actions{padding:3px;line-height:28px;text-align:center}#customer-edit-actions .button-secondary{margin-right:5px}#customer-edit-actions .cancel{padding:5px}.edd-sections-wrap .section-wrap .row-title{width:30%}.edd-sections-wrap .section-wrap .editable{display:block;padding:3px}.edd-sections-wrap .section-wrap div.edit-item{margin-left:-4px;margin-top:-20px}.edd-sections-wrap .section-wrap .customer-address.edit-item{margin-top:3px}.edd-sections-wrap .section-wrap span.edit-item{display:none}.edd-sections-wrap .section-wrap .edit-item input{font-size:13px}.edd-sections-wrap .section-wrap .customer-name.edit-item input{margin-top:-5px}.edd-sections-wrap .section-wrap .edd_user_search_results{left:-2px;top:18px}.edd-sections-wrap .section-wrap .edd_user_search_results ul{width:198px}#edd-item-stats-wrapper{margin:0 auto;text-align:center}#edd-item-stats-wrapper ul{display:flex;margin:0}#edd-item-stats-wrapper li{font-size:14px;margin-bottom:0;width:50%}#edd-item-stats-wrapper a{text-decoration:none}#edd-item-stats-wrapper .dashicons{color:#888;margin-top:-2px}#edd-item-tables-wrapper table{width:100%}#edd-item-tables-wrapper .no-items{text-align:left}#edd-item-tables-wrapper .emails .add-customer-email-row{background-color:#f4f4f4;border-top:1px solid #e5e5e5}#edd-item-tables-wrapper .add-customer-email-wrapper{display:flex;flex-wrap:wrap;align-items:center;margin:12px 0}#edd-item-tables-wrapper .edd-form-group{margin-bottom:0}#edd-item-tables-wrapper .edd-make-email-primary{flex-grow:1;margin-left:12px}#edd-item-tables-wrapper .emails .spinner{float:none;margin:0 10px;-ms-grid-row-align:center;align-self:center}#edd-item-tables-wrapper .notice-error{background-color:#fff5f5}#edd-item-notes-wrapper{min-height:50px}.customer-note-input{margin-bottom:5px;width:100%}.customer-note-wrapper{border-bottom:1px solid #f9f9f9;min-height:38px;padding:7px 0 7px 7px}.customer-note-wrapper span{display:block}.note-content-wrap{padding-top:7px}.edd-sections-wrap .section-wrap .notice-container{padding-left:20px;padding-right:20px;margin-left:-20px;margin-right:-20px}@media screen and (max-width:810px)and (min-width:656px){.customer-info .customer-name{font-size:16px}.edd-sections-wrap .section-wrap .widefat td,.widefat th{max-width:100%!important;display:table-cell}}@media screen and (max-width:781px){#edd-item-tab-wrapper,.edd-sections-wrap .section-wrap{margin:0;width:100%}#edd-item-tab-wrapper-list .dashicons{font-size:18px}.edd-item-has-tabs .edd-sections-wrap .section-wrap{border-top:1px solid #e5e5e5;border-left:0;margin-top:-1px}}@media screen and (max-width:656px){.edd-item-info.customer-info{position:relative}.edd-sections-wrap .section-wrap .customer-address-wrapper{float:none;position:absolute;top:84px;left:165px;max-width:200px}.edd-sections-wrap .section-wrap .customer-main-wrapper{float:none;position:absolute;left:165px}.customer-info .customer-name{font-size:16px}.edd-sections-wrap .section-wrap #edd-item-stats-wrapper{padding-left:0;padding-right:0}.edd-sections-wrap .section-wrap .customer-section{margin-bottom:0}.edd-sections-wrap .section-wrap .widefat td.column-primary,.edd-sections-wrap .section-wrap .widefat td.no-items,.edd-sections-wrap .section-wrap .widefat th.column-primary{width:100px!important;display:table-cell;overflow:hidden;text-align:left}.edd-sections-wrap .section-wrap .customer-id{display:none}#edd-item-tables-wrapper .emails td.column-primary{padding-right:10px;width:100%!important}#edd-item-tables-wrapper .edd-form-group{margin:0 0 16px}}@media screen and (max-width:480px){#edd-item-tab-wrapper-list li{width:50%}#edd-item-tab-wrapper-list li:nth-child(3n+3){border-width:0 1px 1px 0}#edd-item-tab-wrapper-list li:nth-child(2n){border-width:0 0 1px}.download_page_edd-reports .button{text-align:center}#edd-payment-date-filters span{display:block}#edd-payment-date-filters span>input{float:right}#edd-add-discount select[multiple] option,#edd-edit-discount select[multiple] option{height:20px}.download_page_edd-reports .inside .button,.download_page_edd-reports .inside input[type=submit],.download_page_edd-reports .inside input[type=text],.download_page_edd-reports .inside select,.download_page_edd-settings .inside input[type=button],.download_page_edd-tools .inside input[type=submit],.download_page_edd-tools .inside input[type=text],.download_page_edd-tools .inside select{width:100%}#edd-add-discount select[multiple],#edd-edit-discount select[multiple],.download_page_edd-tools select[multiple]{height:200px!important}.download_page_edd-settings input[type=checkbox]{margin:2px 0}.post-type-download input[type=checkbox]{margin-left:2px}}.inside .edd-tools-textarea{background:#32373c;color:rgba(240,245,250,.7);font-size:12px;font-family:Menlo,Monaco,monospace;display:block;overflow:auto;white-space:pre;width:100%;height:450px;padding:10px;outline:none}#system-info-textarea::selection{background:#555;color:#fff}#edd-system-info .edd-inline-button{margin-left:5px}.recount-stats-controls form{display:inline}.edd-recount-stats-descriptions span{display:none;line-height:24px}.edd-vertical-sections{overflow:visible;display:block;display:flex}#edd-item-tab-wrapper,.edd-vertical-sections .section-nav{position:relative;width:20%;line-height:1em;margin:0 -1px 0 0;padding:0;background-color:#f5f5f5;border-right:1px solid #e5e5e5;box-sizing:border-box;max-width:200px}#edd-item-tab-wrapper-list{margin:0}#edd-item-tab-wrapper li,.edd-vertical-sections .section-nav li{display:block;position:relative;margin:0;padding:0;background-color:#fcfcfc}.edd-vertical-sections .section-title:last-of-type{margin-bottom:24px}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap,#edd-item-tab-wrapper li a,.edd-vertical-sections .section-nav li a{display:flex;margin:0;padding:9px;text-decoration:none;border-bottom:1px solid #e5e5e5;box-shadow:none;position:relative;align-items:center}#edd-item-tab-wrapper li a:focus,#edd-item-tab-wrapper li a:hover,.edd-vertical-sections .section-nav li a:focus,.edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0;outline:0;transition:all .25s}.edd-vertical-sections .section-nav .section-title--is-active a:after{content:"";width:1px;height:100%;background:#fff;position:absolute;right:0;top:0;bottom:0;z-index:3}#edd-item-tab-wrapper li>.edd-item-tab-label-wrap{background-color:#fff}.edd-vertical-sections .section-nav li a>.dashicons,.edd-vertical-sections .section-nav li a>span{display:inline-block}.edd-vertical-sections .section-nav li a>span{max-width:76%}.edd-vertical-sections .section-nav li a .dashicons{line-height:20px;margin-right:3px;color:#888}.edd-vertical-sections .section-nav .section-title--is-active a{font-weight:700;color:#555;background-color:#fff;border-right:none;margin-right:-1px}.edd-vertical-sections.use-js .section-content,.no-js .edd-vertical-sections.use-js.edd-item-header-small,.no-js .edd-vertical-sections.use-js .section-nav{display:none}.no-js .edd-vertical-sections.use-js .section-content{display:block}.admin-color-fresh .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-fresh .edd-vertical-sections .section-nav li a:focus,.admin-color-fresh .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #0073aa}.admin-color-blue .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-blue .edd-vertical-sections .section-nav li a:focus,.admin-color-blue .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #096484}.admin-color-coffee .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-coffee .edd-vertical-sections .section-nav li a:focus,.admin-color-coffee .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #c7a589}.admin-color-ectoplasm .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:focus,.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #a3b745}.admin-color-midnight .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-midnight .edd-vertical-sections .section-nav li a:focus,.admin-color-midnight .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #e14d43}.admin-color-ocean .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-ocean .edd-vertical-sections .section-nav li a:focus,.admin-color-ocean .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #627c83}.admin-color-sunrise .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-sunrise .edd-vertical-sections .section-nav li a:focus,.admin-color-sunrise .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #be3631}.admin-color-light .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-light .edd-vertical-sections .section-nav li a:focus,.admin-color-light .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #888}.admin-color-evergreen .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-evergreen .edd-vertical-sections .section-nav li a:focus,.admin-color-evergreen .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #36533f}.admin-color-mint .edd-vertical-sections .section-nav .section-title--is-active a,.admin-color-mint .edd-vertical-sections .section-nav li a:focus,.admin-color-mint .edd-vertical-sections .section-nav li a:hover{box-shadow:inset 5px 0 #4f6d59}.edd-vertical-sections .section-nav .section-title--is-active .dashicons{color:#555}@media only screen and (max-width:782px){#edd-item-tab-wrapper,.edd-vertical-sections .section-nav{width:48px}.edd-vertical-sections .section-nav li a{justify-content:center}.edd-vertical-sections .section-nav li a .dashicons{width:24px;height:24px;font-size:24px;line-height:24px;margin:0}.section-nav li .dashicons:before{width:24px;height:24px}#edd-item-tab-wrapper .edd-item-tab-label,.section-nav li .label{overflow:hidden;position:absolute;top:-1000em;left:-1000em;width:1px;height:1px}}#edd-item-card-wrapper,.edd-vertical-sections .section-wrap{width:80%}#edd-item-card-wrapper .item-section{background:#fff;overflow:hidden;box-sizing:border-box}:not(#edd-item-tab-wrapper)+#edd-item-card-wrapper .item-section{margin:25px 0;padding:20px;border:1px solid #e5e5e5;box-shadow:0 1px 1px rgba(0,0,0,.04)}#edd-item-tab-wrapper+#edd-item-card-wrapper{padding:20px;border-left:1px solid #e5e5e5;box-sizing:border-box}@media only screen and (min-width:1200px){#edd-graphs-filter,#edd-item-card-wrapper,.edd-vertical-sections:not(.meta-box) .section-wrap{width:calc(100% - 200px)}}@media only screen and (max-width:782px){#edd-graphs-filter,#edd-item-card-wrapper,.edd-vertical-sections .section-wrap{width:calc(100% - 48px)}}#edd-debug-log .edd-inline-button{margin-left:5px}.edd-settings-sidebar{padding-top:27px}.edd-settings-sidebar-content{background-color:#fff;text-align:center;border:1px solid #ddd;box-sizing:border-box;max-width:300px}.edd-settings-sidebar-content p{font-size:14px;line-height:1.5;margin-top:0}.edd-sidebar-header-section{background-color:#35495c;line-height:1;padding:26px 20px 24px;border-bottom:3px dashed #fafafa}.edd-sidebar-description-section{background-color:#fafafa;padding:16px 20px;border-bottom:1px solid #ddd}.edd-sidebar-description-section .edd-sidebar-description{margin:0}.edd-sidebar-coupon-section{font-size:14px;padding:16px 20px}.edd-sidebar-coupon-section label{display:block;line-height:1.4;margin-bottom:6px}.edd-sidebar-coupon-section label strong{color:#253b51;font-weight:700}.edd-sidebar-coupon-section input{background:#f4f7fa;font-size:22px;font-weight:600;text-align:center;padding:10px;border:2px dashed #2794da;border-radius:4px;margin-bottom:16px;box-shadow:none;width:100%}.edd-sidebar-coupon-section input:focus{border:2px dashed #2794da;box-shadow:none}.edd-settings-sidebar-content .edd-coupon-note{color:#6c7883;font-size:13px;font-style:italic;margin:0}.edd-settings-sidebar-content .edd-coupon-note a{color:#253b51}.edd-settings-sidebar-content .edd-coupon-note a:hover{text-decoration:none}.edd-sidebar-footer-section{background-color:#fafafa;padding:16px 20px;border-top:1px solid #ddd}.edd-sidebar-footer-section .edd-cta-button{display:block;background-color:#2794da;color:#fff;text-decoration:none;font-size:20px;font-weight:700;text-transform:uppercase;padding:17px 10px;border:none;border-radius:4px;width:100%;box-sizing:border-box;box-shadow:none;transition:background-color .2s}.edd-sidebar-footer-section .edd-cta-button:hover{background-color:#2386c5}@media (min-width:1080px){.edd-has-sidebar .edd-settings-content{float:left;width:67%}.edd-has-sidebar .edd-settings-sidebar{float:right;width:31%}}@media (min-width:1240px){.edd-has-sidebar .edd-settings-content{width:74%}.edd-has-sidebar .edd-settings-sidebar{width:23%}}.taxes-tab .edd-has-sidebar .edd-settings-content,.taxes-tab .edd-has-sidebar .edd-settings-sidebar{float:none;width:100%}.bfcm-promo-img-container{background-color:#35495c;width:100%;height:160px}.bfcm-code{color:#2794da;font-weight:700}.sale-ends{position:absolute;bottom:9px;right:14px;display:inline-block;color:#6c7883;font-size:12px;text-align:right;font-style:italic;width:150px} \ No newline at end of file diff --git a/assets/css/edd-admin.min.css.map b/assets/css/edd-admin.min.css.map new file mode 100644 index 00000000000..882848f0389 --- /dev/null +++ b/assets/css/edd-admin.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///style.scss"],"names":[],"mappings":"AAAA;;;;;;;EAOE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;;EAGE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;;;;EAKE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;;EAGE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;EACE,aAAa;EACb,uBAAuB;EACvB,mBAAmB;EACnB,qBAAqB;EACrB,sBAAsB;EACtB,WAAW,EAAE;;AAEf;EACE,cAAc;EACd,iBAAiB;EACjB,iDAAiD,EAAE;EACnD;IACE,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,iBAAiB,EAAE;EACrB;IACE,aAAa;IACb,SAAS;IACT,kBAAkB,EAAE;EACtB;IACE,mBAAmB,EAAE;;AAEzB;EACE,WAAW,EAAE;EACb;IACE,WAAW,EAAE;;AAEjB;EACE,oBAAoB;EACpB,qBAAqB,EAAE;;AAEzB;EACE,aAAa;EACb,eAAe;EACf,SAAS,EAAE;EACX;IACE,oBAAoB;IACpB,sBAAsB;IACtB,yBAAyB,EAAE;IAC3B;MACE,gBAAgB,EAAE;;AAExB;EACE,cAAc,EAAE;;AAElB;EACE,YAAY,EAAE;;AAEhB;EACE,sCAAsC;EACtC,cAAc,EAAE;;AAElB;EACE,aAAa;EACb,mBAAmB;EACnB,QAAQ,EAAE;EACV;IACE,SAAS,EAAE;;AAEf;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;;EAGE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;;;;EAKE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;;;EAGE;AACF;;EAEE;AACF;;EAEE;AACF;;EAEE;AACF;EACE,aAAa;EACb,SAAS;EACT,aAAa;EACb,8BAA8B;EAC9B,eAAe;EACf,QAAQ,EAAE;EACV;IACE,aAAa;IACb,mBAAmB;IACnB,eAAe;IACf,QAAQ;IACR,WAAW;IACX,YAAY,EAAE;IACd;MACE,qBAAqB;MACrB,cAAc,EAAE;EACpB;IACE,cAAc,EAAE;EAClB;;IAEE,gBAAgB,EAAE;EACpB;;IAEE,gBAAgB,EAAE;EACpB;IACE,SAAS,EAAE;EACb;IACE;MACE,aAAa,EAAE,EAAE;;AAEvB;EACE,kBAAkB,EAAE;EACpB;IACE,WAAW;IACX,kBAAkB;IAClB,SAAS;IACT,QAAQ;IACR,yBAAyB;IACzB,UAAU;IACV,gBAAgB;IAChB,wCAAwC;IACxC,gBAAgB;IAChB,UAAU;IACV,kBAAkB,EAAE;EACtB;IACE,cAAc;IACd,uBAAuB;IACvB,cAAc,EAAE;IAChB;MACE,gCAAgC,EAAE;IACpC;MACE,mBAAmB,EAAE;IACvB;;;;MAIE,cAAc;MACd,kBAAkB,EAAE;EACxB;IACE,mBAAmB,EAAE;EACvB;IACE,mBAAmB;IACnB,qBAAqB;IACrB,mDAAmD;IAEnD,0BAA0B,EAAE;EAC9B;IACE,mBAAmB;IACnB,UAAU;IAIV,gCAAgC,EAAE;;AAEtC;EACE,mBAAmB;EACnB,gBAAgB,EAAE;EAClB;IACE;MACE,MAAM,EAAE,EAAE;;AAEhB;EACE,iBAAiB;EACjB,gBAAgB,EAAE;;AAEpB;EACE,iBAAiB,EAAE;EACnB;IACE,qBAAqB,EAAE;IACvB;MACE,qBAAqB;MACrB,mCAAmC;MACnC,kCAAkC;MAClC,mBAAmB;MACnB,iBAAiB;MACjB,UAAU,EAAE;IACd;MACE,gBAAgB;MAChB,cAAc;MACd,kBAAkB,EAAE;IACtB;MACE,gBAAgB,EAAE;;AAExB;EACE,kBAAkB;EAClB,gBAAgB,EAAE;EAClB;IACE,sBAAsB;IACtB,gBAAgB;IAChB,sBAAsB;IACtB,WAAW;IACX,mBAAmB;IACnB,WAAW,EAAE;EACf;IACE,gBAAgB;IAChB,yBAAyB;IACzB,kBAAkB,EAAE;EACtB;IACE,cAAc;IACd,mBAAmB,EAAE;IACrB;MACE,gBAAgB;MAChB,yBAAyB,EAAE;;AAEjC;;EAEE,wBAAwB,EAAE;;AAE5B;;;;;;EAME,WAAW,EAAE;;AAEf;EACE,aAAa;EACb,mBAAmB,EAAE;EACrB;IACE,6BAA6B;IAC7B,SAAS;IACT,wBAAwB;IACxB,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,WAAW,EAAE;IACb;MACE,mBAAmB,EAAE;IACvB;MACE,8DAA8D;MAC9D,yDAAyD,EAAE;EAC/D;IACE,gBAAgB,EAAE;;AAEtB;EACE,iCAAiC,EAAE;EACnC;IACE,iBAAiB,EAAE;;AAEvB;EACE,qBAAqB;EACrB,iBAAiB;EACjB,gBAAgB;EAChB,WAAW,EAAE;;AAEf;EACE,6BAA6B,EAAE;;AAEjC;EACE;IACE,mBAAmB,EAAE;EACvB;IACE,mBAAmB;IACnB,YAAY,EAAE,EAAE;;AAEpB;EACE,gBAAgB,EAAE;;AAEpB;;EAEE,aAAa,EAAE;;AAEjB;;;;EAIE;AACF;EACE,aAAa,EAAE;;AAEjB;EACE,WAAW;EACX,cAAc;EACd,WAAW,EAAE;;AAEf;;EAEE,qBAAqB,EAAE;;AAEzB;;;EAGE;AACF;;EAEE,WAAW,EAAE;;AAEf;;EAEE,WAAW,EAAE;;AAEf;gEACgE;AAChE;EACE,YAAY,EAAE;;AAEhB;EACE,YAAY;EACZ,SAAS;EACT,kBAAkB;EAClB,UAAU,EAAE;;AAEd;;EAEE,0BAA0B;EAC1B,6BAA6B,EAAE;;AAEjC;;EAEE,iBAAiB;EACjB,yBAAyB;EACzB,4BAA4B,EAAE;;AAEhC;EACE,UAAU;EACV,kBAAkB,EAAE;;AAEtB;EACE,oBAAoB;EACpB,WAAW;EACX,6BAA6B;EAC7B,yCAAyC,EAAE;;AAE7C;EACE,aAAa;EACb,cAAc,EAAE;;AAElB;EACE,6BAA6B;EAC7B,mBAAmB,EAAE;;AAEvB;EACE,4BAA4B,EAAE;;AAEhC,SAAS;AACT;EACE,4BAA4B,EAAE;;AAEhC,WAAW;AACX;EACE,4BAA4B,EAAE;;AAEhC,cAAc;AACd;EACE,4BAA4B,EAAE;;AAEhC,aAAa;AACb;EACE,4BAA4B,EAAE;;AAEhC,UAAU;AACV;EACE,4BAA4B,EAAE;;AAEhC,YAAY;AACZ;EACE,4BAA4B,EAAE;;AAEhC,UAAU;AACV;EACE,yBAAyB,EAAE;;AAE7B,0BAA0B;AAC1B,cAAc;AACd;EACE,4BAA4B,EAAE;;AAEhC,SAAS;AACT;EACE,4BAA4B,EAAE;;AAEhC;EACE,WAAW,EAAE;;AAEf;EACE,oBAAoB;EACpB,qBAAqB;EACrB,uBAAuB,EAAE;;AAE3B;;EAEE,gBAAgB;EAChB,gBAAgB;EAChB,cAAc,EAAE;;AAElB;EACE,SAAS;EACT,YAAY;EACZ,kBAAkB,EAAE;;AAEtB;EACE,SAAS;EACT,UAAU;EACV,kBAAkB;EAClB,YAAY;EACZ,YAAY,EAAE;;AAEhB;EACE,sBAAsB,EAAE;;AAE1B;EACE,cAAc;EACd,WAAW;EACX,YAAY;EACZ,kBAAkB;EAClB,UAAU;EACV,QAAQ;EACR,WAAW;EACX,sBAAsB;EACtB,eAAe;EACf,gBAAgB;EAChB,YAAY,EAAE;;AAEhB;EACE,cAAc;EACd,YAAY;EACZ,UAAU;EACV,SAAS,EAAE;;AAEb;EACE,WAAW;EACX,YAAY;EACZ,kBAAkB;EAClB,SAAS;EACT,iBAAiB,EAAE;;AAErB,aAAa;AACb;EACE,YAAY;EACZ,gBAAgB;EAChB,eAAe;EACf,WAAW,EAAE;;AAEf;EACE,kBAAkB;EAClB,2BAA2B;EAC3B,4BAA4B;EAC5B,6BAA6B;EAC7B,+DAA+D;EAC/D,yBAAyB;EACzB,2BAA2B;EAC3B,uBAAuB;EACvB,kCAAkC;EAClC,4BAA4B;EAC5B,wBAAwB,EAAE;;AAE5B;gEACgE;AAChE;EACE,eAAe,EAAE;;AAEnB;EACE,eAAe,EAAE;;AAEnB;EACE,SAAS;EACT,qBAAqB,EAAE;;AAEzB;EACE,iBAAiB;EACjB,WAAW;EACX,qBAAqB;EACrB,sBAAsB,EAAE;;AAE1B;EACE,sBAAsB,EAAE;;AAE1B;gEACgE;AAChE;;EAEE,iBAAiB,EAAE;;AAErB;EACE,kBAAkB;EAClB,mBAAmB;EACnB,SAAS,EAAE;;AAEb;EACE,WAAW,EAAE;;AAEf;gEACgE;AAChE;EACE,iBAAiB,EAAE;;AAErB;EACE,eAAe,EAAE;;AAEnB;EACE,WAAW;EACX,iBAAiB,EAAE;;AAErB;EACE,sBAAsB;EACtB,UAAU;EACV,WAAW,EAAE;;AAEf;EACE,gBAAgB;EAChB,sBAAsB;EACtB,WAAW;EACX,aAAa;EACb,kBAAkB;EAClB,qBAAqB;EACrB,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,+BAA+B;EAC/B,eAAe,EAAE;;AAEnB;EACE,sBAAsB;EACtB,UAAU;EACV,sBAAsB;EACtB,UAAU,EAAE;;AAEd;EACE,eAAe;EACf,eAAe,EAAE;;AAEnB;EACE,kBAAkB;EAClB,YAAY;EACZ,UAAU,EAAE;;AAEd;EACE,WAAW;EACX,WAAW,EAAE;;AAEf;EACE,aAAa,EAAE;;AAEjB;EACE,yBAAyB;EACzB,qBAAqB;EACrB,WAAW;EACX,wCAAwC,EAAE;;AAE5C;EACE,WAAW,EAAE;;AAEf,mDAAmD;AACnD;EACE,mBAAmB;EACnB,qCAAqC;EACrC,2BAA2B;EAC3B,WAAW;EACX,qBAAqB;EACrB,yFAAyF,EAAE;;AAE7F;EACE,WAAW;EACX,YAAY;EACZ,sBAAsB,EAAE;;AAE1B;gEACgE;AAChE;EACE,gBAAgB,EAAE;;AAEpB;EACE,YAAY,EAAE;;AAEhB;EACE,mCAAmC;EACnC,aAAa;EACb,SAAS;EACT,qCAAqC,EAAE;;AAEzC;EACE,eAAe;EACf,gBAAgB;EAChB,iBAAiB,EAAE;;AAErB;EACE,UAAU;EACV,QAAQ,EAAE;;AAEZ;EACE,WAAW,EAAE;;AAEf;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;EACV,yBAAyB,EAAE;;AAE7B;EACE,kBAAkB;EAClB,WAAW;EACX,SAAS;EACT,UAAU;EACV,mBAAmB;EACnB,iBAAiB,EAAE;;AAErB;EACE,yBAAyB;EACzB,aAAa;EACb,0BAA0B,EAAE;;AAE9B;gEACgE;AAChE;EACE,eAAe,EAAE;;AAEnB;EACE,gBAAgB,EAAE;;AAEpB;gEACgE;AAChE,4DAA4D;AAC5D;EACE,cAAc;EACd,YAAY;EACZ,gCAAgC;EAChC,WAAW;EACX,wBAAwB;EACxB,gBAAgB;EAChB,SAAS;EACT,kBAAkB;EAClB,kBAAkB,EAAE;;AAEtB;EACE;IACE,iBAAiB;IACjB,SAAS,EAAE,EAAE;;AAEjB;EACE,YAAY;EACZ,gBAAgB;EAChB,gBAAgB;EAChB,aAAa;EACb,eAAe;EACf,8BAA8B;EAC9B,mBAAmB;EACnB,kBAAkB;EAClB,eAAe;EACf,sBAAsB;EACtB,kBAAkB;EAClB,mBAAmB,EAAE;;AAEvB;EACE;IACE,kBAAkB;IAClB,mBAAmB,EAAE,EAAE;;AAE3B;EACE;IACE,iBAAiB,EAAE,EAAE;;AAEzB;EACE,QAAQ,EAAE;;AAEZ;EACE,QAAQ,EAAE;;AAEZ;;;EAGE,aAAa;EACb,mBAAmB,EAAE;;AAEvB;EACE,iBAAiB,EAAE;;AAErB;EACE,WAAW;EACX,YAAY;EACZ,iBAAiB;EACjB,eAAe,EAAE;;AAEnB;EACE,aAAa;EACb,mBAAmB;EACnB,8BAA8B,EAAE;;AAElC;EACE,mBAAmB,EAAE;;AAEvB;EACE,sBAAsB,EAAE;;AAE1B;EACE,UAAU,EAAE;;AAEd;EACE,WAAW;EACX,gBAAgB;EAChB,6BAA6B;EAC7B,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,mBAAmB,EAAE;;AAEvB;EACE,SAAS,EAAE;;AAEb;EACE,aAAa;EACb,sBAAsB,EAAE;;AAE1B;EACE,WAAW;EACX,cAAc;EACd,iBAAiB,EAAE;;AAErB;EACE,kBAAkB,EAAE;;AAEtB;EACE,aAAa,EAAE;;AAEjB,cAAc;AACd;EACE,eAAe;EACf,mBAAmB,EAAE;;AAEvB;EACE,eAAe,EAAE;;AAEnB;EACE;IACE,iBAAiB,EAAE,EAAE;;AAEzB;EACE,4BAA4B,EAAE;;AAEhC;EACE;IACE,4BAA4B,EAAE,EAAE;;AAEpC;EACE,4BAA4B,EAAE;;AAEhC,WAAW;AACX;EACE,sBAAsB,EAAE;;AAE1B;EACE,yBAAyB,EAAE;;AAE7B;EACE;;IAEE,gCAAgC,EAAE,EAAE;;AAExC;EACE;;IAEE,yBAAyB,EAAE;EAC7B;IACE,aAAa,EAAE,EAAE;;AAErB;EACE,YAAY,EAAE;;AAEhB;EACE,WAAW;EACX,cAAc,EAAE;;AAElB;EACE,WAAW,EAAE;;AAEf;EACE,aAAa;EACb,mBAAmB;EACnB,kBAAkB,EAAE;;AAEtB;EACE,qBAAqB;EACrB,kBAAkB;EAClB,iBAAiB;EACjB,aAAa;EACb,+BAA+B;EAC/B,WAAW,EAAE;;AAEf;EACE,cAAc,EAAE;;AAElB,iBAAiB;AACjB;EACE,gBAAgB,EAAE;;AAEpB;EACE,mBAAmB,EAAE;;AAEvB,YAAY;AACZ;;;EAGE,gBAAgB,EAAE;;AAEpB;;;;EAIE,sBAAsB,EAAE;;AAE1B;;EAEE,mBAAmB,EAAE;;AAEvB;EACE,cAAc;EACd,qBAAqB,EAAE;;AAEzB;EACE,WAAW,EAAE;;AAEf;EACE;IACE,kBAAkB,EAAE;EACtB;IACE,eAAe;IACf,gBAAgB;IAChB,mBAAmB,EAAE,EAAE;;AAE3B;;EAEE,6BAA6B,EAAE;;AAEjC;EACE,YAAY,EAAE;;AAEhB;EACE,mBAAmB;EACnB,eAAe,EAAE;;AAEnB,aAAa;AACb;EACE,gBAAgB,EAAE;;AAEpB;EACE,mBAAmB,EAAE;;AAEvB;EACE,6BAA6B,EAAE;;AAEjC,aAAa;AACb;EACE,6BAA6B;EAC7B,aAAa;EACb,aAAa;EACb,mBAAmB;EACnB,eAAe;EACf,8BAA8B,EAAE;;AAElC;EACE,UAAU;EACV,aAAa,EAAE;;AAEjB;EACE,aAAa;EACb,mBAAmB,EAAE;;AAEvB;EACE,WAAW;EACX,mBAAmB,EAAE;;AAEvB;EACE,gBAAgB,EAAE;;AAEpB;EACE;IACE,WAAW;IACX,iBAAiB;IACjB,gBAAgB,EAAE;EACpB;IACE,iBAAiB,EAAE,EAAE;;AAEzB;EACE,kBAAkB;EAClB,aAAa,EAAE;;AAEjB;EACE;IACE,mBAAmB,EAAE,EAAE;;AAE3B;EACE,iBAAiB,EAAE;;AAErB,YAAY;AACZ;EACE,YAAY,EAAE;;AAEhB;;EAEE,iBAAiB,EAAE;;AAErB;EACE,aAAa,EAAE;;AAEjB;;EAEE,cAAc;EACd,kBAAkB,EAAE;;AAEtB;EACE,8BAA8B,EAAE;;AAElC;EACE,iBAAiB,EAAE;;AAErB;EACE,qBAAqB;EACrB,aAAa;EACb,mBAAmB;EACnB,6BAA6B;EAC7B,aAAa;EACb,mBAAmB,EAAE;;AAEvB;EACE,SAAS,EAAE;;AAEb;EACE,aAAa;EACb,mBAAmB,EAAE;;AAEvB;EACE,aAAa,EAAE;;AAEjB;EACE,iBAAiB;EACjB,gBAAgB,EAAE;;AAEpB;EACE,eAAe;EACf,cAAc;EACd,aAAa,EAAE;;AAEjB;;EAEE,gBAAgB,EAAE;;AAEpB;;EAEE,WAAW,EAAE;;AAEf;EACE,kBAAkB;EAClB,WAAW;EACX,cAAc;EACd,aAAa,EAAE;;AAEjB;EACE,WAAW,EAAE;;AAEf,aAAa;AACb;EACE,cAAc,EAAE;;AAElB,mBAAmB;AACnB;EACE,gBAAgB;EAChB,kBAAkB;EAClB,mBAAmB;EACnB,oBAAoB;EACpB,mBAAmB,EAAE;;AAEvB;EACE,aAAa;EACb,oBAAoB,EAAE;;AAExB;EACE,eAAe;EACf,WAAW;EACX,YAAY,EAAE;;AAEhB;EACE,cAAc;EACd,mBAAmB,EAAE;;AAEvB;EACE,cAAc;EACd,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,mBAAmB,EAAE;;AAEvB;EACE,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,mBAAmB,EAAE;;AAEvB;EACE,cAAc;EACd,mBAAmB,EAAE;;AAEvB;EACE,iBAAiB,EAAE;;AAErB;EACE,iBAAiB;EACjB,kBAAkB,EAAE;;AAEtB;EACE,aAAa,EAAE;;AAEjB;gEACgE;AAChE;EACE,aAAa;EACb,yBAAyB;EACzB,yBAAyB;EACzB,WAAW;EACX,kBAAkB;EAClB,mBAAmB;EACnB,sBAAsB;EACtB,gBAAgB,EAAE;;AAEpB;EACE,YAAY,EAAE;;AAEhB;EACE,aAAa;EACb,mBAAmB,EAAE;;AAEvB;EACE,WAAW;EACX,qBAAqB;EACrB,SAAS,EAAE;;AAEb;EACE,eAAe;EACf,WAAW,EAAE;;AAEf;EACE,iBAAiB,EAAE;;AAErB;EACE,WAAW;EACX,iBAAiB;EACjB,qBAAqB;EACrB,iBAAiB,EAAE;;AAErB;EACE,WAAW,EAAE;;AAEf;EACE,gBAAgB,EAAE;;AAEpB;EACE,oBAAoB,EAAE;;AAExB;EACE,WAAW;EACX,gBAAgB;EAChB,aAAa,EAAE;;AAEjB;EACE,UAAU,EAAE;;AAEd;EACE,YAAY;EACZ,0BAA0B,EAAE;;AAE9B;;EAEE,gBAAgB;EAChB,WAAW;EACX,qBAAqB,EAAE;;AAEzB;EACE,yBAAyB;EACzB,mBAAmB,EAAE;;AAEvB;;EAEE,eAAe,EAAE;;AAEnB;gEACgE;AAChE;EACE,aAAa;EACb,gBAAgB,EAAE;;AAEpB;;EAEE,YAAY,EAAE;;AAEhB;;EAEE,sBAAsB,EAAE;;AAE1B;;EAEE,qBAAqB;EACrB,YAAY,EAAE;;AAEhB;EACE,aAAa,EAAE;;AAEjB;EACE,kBAAkB;EAClB,aAAa,EAAE;;AAEjB;EACE,yBAAyB;EACzB,4BAA4B;EAC5B,sBAAsB,EAAE;;AAE1B;EACE,0BAA0B;EAC1B,6BAA6B;EAC7B,kBAAkB;EAClB,cAAc;EACd,YAAY;EACZ,gBAAgB,EAAE;;AAEpB;EACE,UAAU,EAAE;;AAEd;gEACgE;AAChE;;;;;;;EAOE,eAAe,EAAE;;AAEnB;;EAEE,WAAW;EACX,kBAAkB;EAClB,eAAe;EACf,kBAAkB,EAAE;;AAEtB;gEACgE;AAChE;EACE,YAAY,EAAE;;AAEhB;EACE,cAAc,EAAE;;AAElB;EACE,cAAc,EAAE;;AAElB;;EAEE,YAAY,EAAE;;AAEhB;EACE,WAAW,EAAE;;AAEf;gEACgE;AAChE;EACE,yBAAyB,EAAE;;AAE7B;EACE,kBAAkB;EAClB,MAAM,EAAE;;AAEV;EACE,gBAAgB,EAAE;;AAEpB;;;;;EAKE,QAAQ;EACR,iBAAiB;EACjB,iBAAiB,EAAE;;AAErB;;EAEE,0BAA0B;EAC1B,6BAA6B;EAC7B,yBAAyB;EACzB,cAAc;EACd,gBAAgB;EAChB,kBAAkB;EAClB,iBAAiB,EAAE;;AAErB;;EAEE,gBAAgB,EAAE;;AAEpB;;EAEE,YAAY;EACZ,eAAe;EACf,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,eAAe,EAAE;;AAEnB;gEACgE;AAChE;EACE,kBAAkB,EAAE;;AAEtB;EACE,mBAAmB;EACnB,sBAAsB,EAAE;;AAE1B;EACE,UAAU;EACV,SAAS,EAAE;;AAEb;EACE;IACE,sBAAsB,EAAE,EAAE;;AAE9B;EACE,WAAW,EAAE;;AAEf;gEACgE;AAChE;EACE,YAAY;EACZ,WAAW,EAAE;;AAEf;EACE,YAAY;EACZ,WAAW,EAAE;;AAEf;EACE,WAAW,EAAE;;AAEf;EACE,WAAW,EAAE;;AAEf;;EAEE,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,UAAU;EACV,SAAS;EACT,sBAAsB;EACtB,uBAAuB;EACvB,8BAA8B,EAAE;;AAElC;EACE,0BAA0B;EAC1B,mBAAmB;EACnB,aAAa;EACb,8BAA8B;EAC9B,iBAAiB,EAAE;;AAErB;EACE,WAAW,EAAE;;AAEf;EACE,YAAY,EAAE;;AAEhB;EACE,gBAAgB,EAAE;;AAEpB;;EAEE,gBAAgB,EAAE;;AAEpB;EACE,aAAa,EAAE;;AAEjB;EACE,WAAW;EACX,eAAe,EAAE;;AAEnB;EACE,qBAAqB;EACrB,eAAe;EACf,iBAAiB;EACjB,WAAW;EACX,eAAe;EACf,sBAAsB,EAAE;;AAE1B;;EAEE,WAAW;EACX,mBAAmB;EACnB,yBAAyB,EAAE;;AAE7B;EACE,YAAY,EAAE;;AAEhB;;EAEE,kBAAkB,EAAE;;AAEtB;;;;EAIE,WAAW;EACX,cAAc,EAAE;;AAElB;;EAEE,WAAW,EAAE;;AAEf;EACE,WAAW;EACX,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,wBAAwB;EACxB,gBAAgB;EAChB,eAAe;EACf,gBAAgB;EAChB,gBAAgB;EAChB,mBAAmB;EACnB,qBAAqB,EAAE;;AAEzB;EACE,WAAW,EAAE;;AAEf;;EAEE,YAAY;EACZ,sBAAsB,EAAE;;AAE1B;EACE,YAAY;EACZ,iBAAiB;EACjB,YAAY,EAAE;;AAEhB;;EAEE,eAAe;EACf,WAAW;EACX,eAAe,EAAE;;AAEnB;;EAEE,mBAAmB;EACnB,YAAY;EACZ,uBAAuB;EACvB,mBAAmB;EACnB,qBAAqB;EACrB,aAAa;EACb,8BAA8B;EAC9B,SAAS,EAAE;;AAEb,sEAAsE;AACtE;;EAEE,gBAAgB;EAChB,oBAAoB;EACpB,sBAAsB;EACtB,YAAY;EACZ,8BAA8B,EAAE;;AAElC;EACE,cAAc;EACd,kBAAkB,EAAE;;AAEtB;EACE,qBAAqB;EACrB,gBAAgB,EAAE;;AAEpB;EACE,gBAAgB;EAChB,YAAY,EAAE;;AAEhB;EACE,eAAe,EAAE;;AAEnB;EACE,eAAe;EACf,gBAAgB;EAChB,mBAAmB;EACnB,YAAY,EAAE;;AAEhB;EACE,iBAAiB;EACjB,0BAAkB;MAAlB,kBAAkB,EAAE;;AAEtB;EACE,kBAAkB;EAClB,WAAW,EAAE;;AAEf;EACE,WAAW,EAAE;;AAEf;EACE,mBAAmB,EAAE;;AAEvB;EACE,mBAAmB;EACnB,YAAY;EACZ,8BAA8B;EAC9B,cAAc;EACd,UAAU;EACV,kBAAkB;EAClB,wBAAwB;EACxB,WAAW;EACX,gBAAgB;EAChB,QAAQ;EACR,UAAU;EACV,oBAAoB;EACpB,uBAAuB;EACvB,mBAAmB,EAAE;;AAEvB;EACE,WAAW;EACX,WAAW;EACX,SAAS,EAAE;;AAEb;EACE,gBAAgB,EAAE;;AAEpB;EACE,WAAW,EAAE;;AAEf;EACE,gBAAgB,EAAE;;AAEpB;;;EAGE,YAAY,EAAE;;AAEhB;;EAEE,WAAW,EAAE;;AAEf;;EAEE,UAAU;EACV,gBAAgB,EAAE;;AAEpB;;EAEE,eAAe,EAAE;;AAEnB;EACE,cAAc;EACd,gBAAgB;EAChB,WAAW;EACX,WAAW,EAAE;;AAEf,+EAA+E;AAC/E;EACE,YAAY;EACZ,eAAe;EACf,qBAAqB;EACrB,UAAU;EACV,gBAAgB;EAChB,iBAAiB;EACjB,oBAAoB;EACpB,WAAW;EACX,YAAY,EAAE;;AAEhB;;;EAGE,uCAAuC,EAAE;;AAE3C;gEACgE;AAChE;EACE,SAAS;EACT,YAAY;EACZ,gBAAgB;EAChB,kBAAkB;EAClB,SAAS;EACT,qBAAqB;EACrB,YAAY;EACZ,sBAAsB;EACtB,kBAAkB;EAClB,mBAAmB;EACnB,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EACf,mBAAmB;EACnB,cAAc;EACd,eAAe;EACf,UAAU,EAAE;;AAEd;EACE,qBAAqB;EACrB,mBAAmB;EACnB,WAAW,EAAE;;AAEf;EACE,0BAA0B,EAAE;;AAE9B;EACE,cAAc,EAAE;;AAElB;EACE,gBAAgB;EAChB,mBAAmB,EAAE;;AAEvB;EACE,aAAa,EAAE;;AAEjB;EACE,iBAAiB,EAAE;;AAErB;;;EAGE,eAAe,EAAE;;AAEnB;EACE,UAAU,EAAE;;AAEd;EACE,UAAU,EAAE;;AAEd;EACE,UAAU,EAAE;;AAEd;EACE,UAAU,EAAE;;AAEd;EACE,UAAU,EAAE;;AAEd;EACE,gCAAgC;EAChC,WAAW;EACX,aAAa;EACb,SAAS;EACT,qBAAqB,EAAE;;AAEzB;EACE,aAAa;EACb,eAAe;EACf,qBAAqB;EACrB,8BAA8B;EAC9B,mBAAmB,EAAE;;AAEvB;EACE,eAAe,EAAE;;AAEnB;EACE,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,kBAAkB;EAClB,eAAe,EAAE;;AAEnB;EACE,aAAa;EACb,mBAAmB,EAAE;;AAEvB;EACE,aAAa;EACb,eAAe,EAAE;;AAEnB;EACE,gBAAgB,EAAE;;AAEpB;EACE,WAAW;EACX,mBAAmB,EAAE;;AAEvB;EACE,YAAY,EAAE;;AAEhB;EACE,kBAAkB,EAAE;;AAEtB;EACE,SAAS;EACT,UAAU,EAAE;;AAEd;EACE,uBAAuB,EAAE;;AAE3B;EACE,YAAY,EAAE;;AAEhB;;EAEE,WAAW,EAAE;;AAEf;EACE,cAAc;EACd,kBAAkB;EAClB,YAAY,EAAE;;AAEhB;EACE,SAAS;EACT,UAAU,EAAE;;AAEd;EACE,kBAAkB,EAAE;;AAEtB;EACE,gBAAgB,EAAE;;AAEpB;EACE,gBAAgB,EAAE;;AAEpB;EACE,eAAe;EACf,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,mBAAmB,EAAE;;AAEvB;EACE,mBAAmB,EAAE;;AAEvB;EACE,gBAAgB,EAAE;;AAEpB;EACE,qBAAqB,EAAE;;AAEzB;EACE,kBAAkB,EAAE;;AAEtB;EACE,eAAe,EAAE;;AAEnB;EACE,YAAY,EAAE;;AAEhB;EACE,WAAW;EACX,cAAc,EAAE;;AAElB;EACE,YAAY,EAAE;;AAEhB;EACE,kBAAkB,EAAE;;AAEtB;;EAEE,qBAAqB;EACrB,UAAU;EACV,gBAAgB,EAAE;;AAEpB;EACE,SAAS,EAAE;;AAEb;EACE,SAAS;EACT,YAAY,EAAE;;AAEhB;EACE,cAAc;EACd,gBAAgB;EAChB,kBAAkB;EAClB,SAAS;EACT,sBAAsB;EACtB,eAAe,EAAE;;AAEnB;EACE,aAAa,EAAE;;AAEjB;EACE,6BAA6B,EAAE;;AAEjC;EACE,yBAAyB,EAAE;;AAE7B;EACE,iBAAiB;EACjB,gBAAgB;EAChB,mBAAmB,EAAE;;AAEvB;EACE,gBAAgB,EAAE;;AAEpB;EACE,gBAAgB,EAAE;;AAEpB;EACE,WAAW;EACX,gBAAgB,EAAE;;AAEpB;EACE,aAAa,EAAE;;AAEjB;EACE,eAAe;EACf,gBAAgB,EAAE;;AAEpB;EACE,WAAW,EAAE;;AAEf;EACE,WAAW;EACX,gBAAgB,EAAE;;AAEpB;EACE,gBAAgB,EAAE;;AAEpB;EACE,WAAW;EACX,eAAe,EAAE;;AAEnB;EACE,WAAW,EAAE;;AAEf;EACE,WAAW,EAAE;;AAEf;EACE,WAAW,EAAE;;AAEf;;;EAGE,eAAe;EACf,gBAAgB,EAAE;;AAEpB;EACE,iBAAiB,EAAE;;AAErB;;EAEE,aAAa;EACb,6BAA6B;EAC7B,yBAAyB,EAAE;;AAE7B;;EAEE,qBAAqB;EACrB,2BAA2B,EAAE;;AAE/B;;EAEE,SAAS;EACT,WAAW,EAAE;;AAEf;EACE,WAAW;EACX,YAAY;EACZ,sBAAsB,EAAE;;AAE1B;;;EAGE,YAAY,EAAE;;AAEhB;EACE;IACE,eAAe,EAAE;EACnB;IACE,UAAU,EAAE;EACd;IACE,UAAU,EAAE;EACd;IACE,UAAU,EAAE;EACd;IACE,UAAU,EAAE;EACd;IACE,iBAAiB,EAAE,EAAE;;AAEzB;EACE;IACE,UAAU,EAAE;EACd;IACE,UAAU,EAAE;EACd;IACE,iBAAiB,EAAE;EACrB;;IAEE,eAAe,EAAE,EAAE;;AAEvB;EACE;;IAEE,oBAAoB,EAAE;EACxB;IACE,UAAU,EAAE;EACd;;IAEE,UAAU;IACV,oBAAoB,EAAE;EACxB;;IAEE,eAAe,EAAE;EACnB;IACE,iBAAiB,EAAE;EACrB;IACE,eAAe,EAAE;EACnB;IACE,iBAAa;IAAb,aAAa,EAAE;EACjB;IACE,iBAAiB,EAAE,EAAE;;AAEzB;EACE;;;IAGE,WAAW;IACX,oBAAoB,EAAE;EACxB;;IAEE,WAAW;IACX,iBAAiB,EAAE;EACrB;IACE,iBAAiB,EAAE,EAAE;;AAEzB,WAAW;AACX;EACE,qBAAqB,EAAE;;AAEzB;;EAEE,cAAc;EACd,+BAA+B;EAC/B,qBAAqB;EACrB,kBAAkB;EAClB,kBAAkB;EAClB,MAAM;EACN,UAAU;EACV,WAAW;EACX,gCAAgC;EAChC,mBAAmB;EACnB,mCAAmC;EACnC,kCAAkC,EAAE;;AAEtC;EACE,gBAAgB,EAAE;;AAEpB;EACE,gBAAgB,EAAE;;AAEpB;gEACgE;AAChE;EACE,eAAe,EAAE;;AAEnB;gEACgE;AAChE;EACE,gCAAgC;EAChC,WAAW,EAAE;;AAEf;EACE,WAAW;EACX,UAAU,EAAE;;AAEd;EACE,YAAY;EACZ,UAAU,EAAE;;AAEd;EACE,eAAe,EAAE;;AAEnB;EACE,cAAc,EAAE;;AAElB;;EAEE,gBAAgB;EAChB,sBAAsB,EAAE;;AAE1B;EACE,kBAAkB;EAClB,WAAW,EAAE;;AAEf;EACE,eAAe;EACf,mBAAmB;EACnB,WAAW;EACX,WAAW,EAAE;;AAEf;EACE,6BAA6B;EAC7B,cAAc;EACd,yCAAyC;EACzC,eAAe;EACf,mBAAmB;EACnB,cAAc;EACd,iBAAiB;EACjB,oBAAoB,EAAE;;AAExB;EACE,6BAA6B;EAC7B,cAAc;EACd,eAAe;EACf,iBAAiB;EACjB,iBAAiB,EAAE;;AAErB;EACE,iBAAiB,EAAE;;AAErB;EACE,mBAAmB;EACnB,kBAAkB;EAClB,YAAY;EACZ,eAAe;EACf,gBAAgB;EAChB,iBAAiB,EAAE;;AAErB;EACE,WAAW;EACX,cAAc;EACd,kBAAkB,EAAE;;AAEtB;EACE,UAAU,EAAE;;AAEd;EACE,iBAAiB,EAAE;;AAErB;EACE;IACE,aAAa,EAAE,EAAE;;AAErB;gEACgE;AAChE,4HAA4H;AAC5H;EACE,kBAAkB,EAAE;;AAEtB;EACE,eAAe;EACf,iBAAiB;EACjB,yBAAyB;EACzB,cAAc;EACd,YAAY;EACZ,kBAAkB;EAClB,WAAW;EACX,sBAAsB,EAAE;;AAE1B;EACE,qBAAqB;EACrB,kBAAkB;EAClB,SAAS;EACT,UAAU,EAAE;;AAEd,qDAAqD;AACrD;;EAEE,gBAAgB,EAAE;;AAEpB;EACE,cAAc,EAAE;;AAElB;EACE,gBAAgB;EAChB,wBAAwB;EACxB,iBAAiB;EACjB,mBAAmB,EAAE;;AAEvB;EACE,SAAS,EAAE;;AAEb;;EAEE,eAAe,EAAE;;AAEnB;EACE,YAAY,EAAE;;AAEhB;EACE,WAAW,EAAE;;AAEf;EACE,WAAW;EACX,gBAAgB,EAAE;;AAEpB;;EAEE,mBAAmB,EAAE;;AAEvB;EACE,aAAa;EACb,iBAAiB;EACjB,4DAA4D;EAC5D,cAAc,EAAE;;AAElB;EACE,gBAAgB,EAAE;;AAEpB;EACE,aAAa;EACb,mBAAmB;EACnB,WAAW,EAAE;;AAEf;EACE,WAAW,EAAE;;AAEf;EACE,YAAY,EAAE;;AAEhB;EACE,aAAa;EACb,sBAAsB;EACtB,eAAe;EACf,kBAAkB,EAAE;;AAEtB;EACE,aAAa;EACb,iBAAiB;EACjB,yBAA8B;EAA9B,8BAA8B;EAC9B,aAAa,EAAE;;AAEjB;EACE,mBAAmB;EACnB,0BAAkB;MAAlB,kBAAkB;EAClB,mBAAmB,EAAE;;AAEvB;EACE,eAAe;EACf,eAAe,EAAE;;AAEnB;;EAEE,2BAA2B;EAC3B,iBAAiB,EAAE;;AAErB;EACE,gBAAgB;EAChB,mBAAmB;EACnB,YAAY;EACZ,gBAAgB,EAAE;;AAEpB;EACE,gBAAgB;EAChB,mBAAmB;EACnB,YAAY;EACZ,QAAQ,EAAE;;AAEZ;EACE,yBAAyB;EACzB,mBAAmB;EACnB,mBAAmB;EACnB,qBAAqB;EACrB,aAAa;EACb,cAAc;EACd,wBAAwB;EACxB,kBAAkB;EAClB,WAAW;EACX,aAAa;EACb,8BAA8B;EAC9B,mBAAmB,EAAE;;AAEvB;EACE,SAAS,EAAE;;AAEb;EACE,mBAAmB,EAAE;;AAEvB;EACE,gBAAgB,EAAE;;AAEpB;EACE,mBAAmB,EAAE;;AAEvB;EACE,mBAAmB,EAAE;;AAEvB;EACE,mBAAmB,EAAE;;AAEvB;EACE,mBAAmB,EAAE;;AAEvB;EACE,mBAAmB,EAAE;;AAEvB;EACE,WAAW,EAAE;;AAEf;EACE,cAAc;EACd,aAAa,EAAE;;AAEjB;EACE,aAAa,EAAE;;AAEjB;EACE,uBAAuB,EAAE;;AAE3B;EACE,qBAAqB;EACrB,gBAAgB;EAChB,iBAAiB;EACjB,UAAU,EAAE;;AAEd;EACE,WAAW,EAAE;;AAEf;EACE,aAAa,EAAE;;AAEjB;EACE,sBAAsB;EACtB,yBAAyB;EACzB,yCAAyC;EACzC,aAAa,EAAE;;AAEjB;EACE,qBAAqB;EACrB,UAAU;EACV,mBAAmB,EAAE;;AAEvB;EACE,cAAc,EAAE;;AAElB;EACE,cAAc,EAAE;;AAElB;EACE,aAAa,EAAE;;AAEjB;EACE,cAAc;EACd,gBAAgB,EAAE;;AAEpB;EACE,WAAW;EACX,qBAAqB;EACrB,cAAc;EACd,wBAAwB;EACxB,UAAU;EACV,YAAY,EAAE;;AAEhB;EACE,yBAAyB;EACzB,qBAAqB;EACrB,iBAAiB;EACjB,WAAW;EACX,YAAY,EAAE;;AAEhB;EACE,qBAAqB;EACrB,mBAAmB;EACnB,UAAU,EAAE;;AAEd;EACE,UAAU,EAAE;;AAEd;EACE,cAAc,EAAE;;AAElB;EACE,YAAY,EAAE;;AAEhB,0CAA0C;AAC1C;EACE,aAAa,EAAE;;AAEjB;EACE,eAAe,EAAE;;AAEnB;;EAEE,eAAe;EACf,YAAY;EACZ,iBAAiB;EACjB,eAAe,EAAE;;AAEnB;;EAEE,iBAAiB;EACjB,iBAAiB,EAAE;;AAErB;;;;EAIE,kBAAkB;EAClB,qBAAqB;EACrB,WAAW;EACX,YAAY,EAAE;;AAEhB;;EAEE,kBAAkB;EAClB,QAAQ;EACR,gBAAgB;EAChB,sCAAsC,EAAE;;AAE1C;;EAGE,4CAA4C;EAC5C,8CAA8C;EAC9C,gDAAgD;EAChD,iDAAiD;EACjD,2BAA2B;EAC3B,eAAe;EACf,wBAAwB;EAExB,wBAAwB,EAAE;;AAE5B;;EAEE,oCAAoC;EACpC,sCAAsC;EACtC,uCAAuC;EACvC,uBAAuB;EACvB,gBAAgB,EAAE;;AAEpB;EACE,kBAAkB,EAAE;;AAUtB;EACE;IAEE,uBAAuB,EAAE;EAC3B;IAEE,yBAAyB,EAAE,EAAE;;AAEjC;EACE,UAAU;EACV,kBAAkB;EAClB,cAAc;EACd,sBAAsB;EACtB,aAAa,EAAE;;AAEjB;EACE,cAAc,EAAE;;AAElB;EACE,eAAe,EAAE;;AAEnB;EACE,iBAAiB;EACjB,aAAa;EACb,4DAA4D;EAC5D,cAAc,EAAE;;AAElB;EACE,kBAAkB;EAClB,kBAAkB;EAClB,aAAa;EACb,sBAAsB;EACtB,uBAAuB;EACvB,yBAAyB;EACzB,mBAAmB;EACnB,kBAAkB;EAClB,sBAAsB,EAAE;;AAE1B;EACE,qBAAqB;EACrB,eAAe;EACf,iBAAiB;EACjB,YAAY;EACZ,WAAW;EACX,kBAAkB;EAClB,QAAQ;EACR,UAAU;EACV,iBAAiB;EACjB,WAAW,EAAE;;AAEf;EACE,kBAAkB;EAClB,yBAAyB;EACzB,eAAe;EACf,mBAAmB;EACnB,WAAW;EACX,QAAQ;EACR,eAAe,EAAE;;AAEnB;EACE,QAAQ;EACR,WAAW;EACX,cAAc;EACd,cAAc;EACd,+BAA+B,EAAE;;AAEnC;EACE,sBAAsB,EAAE;;AAE1B;EACE,sBAAsB,EAAE;;AAE1B;EACE,cAAc,EAAE;;AAElB;EACE,cAAc,EAAE;;AAElB;;EAEE,WAAW,EAAE;;AAEf;EACE,WAAW,EAAE;;AAEf;EACE,gBAAgB,EAAE;;AAEpB;EACE,kBAAkB;EAClB,QAAQ;EACR,SAAS;EACT,WAAW;EACX,eAAe;EACf,gBAAgB;EAChB,sBAAsB;EACtB,8BAA8B;EAC9B,6BAA6B;EAC7B,6BAA6B;EAC7B,4BAA4B;EAC5B,2BAA2B;EAC3B,oBAAoB;EACpB,qBAAqB,EAAE;;AAEzB;EACE,2BAA2B;EAC3B,0BAA0B;EAC1B,WAAW,EAAE;;AAEf;EACE;IACE,iBAAiB;IACjB,aAAa;IACb,yCAAoD;IAApD,oDAAoD;IACpD,cAAc,EAAE;EAClB;IACE,gBAAgB,EAAE;EACpB;IACE,kBAAuB;IAAvB,uBAAuB;IAAvB,uBAAuB,EAAE,EAAE;;AAE/B;EACE,kBAAkB;EAClB,sBAAsB;EAGtB,kBAAkB;EAElB,wBAAwB;EACxB,oBAAoB;EAEpB,6BAA6B;EAC7B,eAAe;EACf,oHAAoH;EACpH,gBAAgB;EAChB,UAAU,EAAE;;AAEd;EACE,qBAAqB;EACrB,WAAW;EACX,YAAY;EACZ,iBAAiB,EAAE;;AAErB;gEACgE;AAChE;gEACgE;AAChE;;;;;;;;EAQE,cAAc,EAAE;;AAElB;EACE,WAAW;EACX,qBAAqB;EACrB,gBAAgB;EAChB,sBAAsB;EACtB,YAAY;EACZ,gBAAgB;EAChB,aAAa;EACb,iBAAiB;EACjB,kBAAkB;EAClB,sBAAsB,EAAE;;AAE1B;EACE,mBAAmB;EACnB,aAAa;EACb,6BAA6B;EAC7B,wBAAwB;EACxB,WAAW,EAAE;;AAEf;EACE,UAAU,EAAE;;AAEd;EACE,eAAe;EACf,WAAW,EAAE;;AAEf;EACE,kBAAkB;EAClB,mBAAmB;EACnB,aAAa;EACb,0BAA0B;EAC1B,wBAAwB;EACxB,gBAAgB;EAChB,WAAW;EACX,YAAY;EACZ,sBAAsB,EAAE;;AAE1B;EACE,WAAW,EAAE;;AAEf;EACE,qBAAqB,EAAE;;AAEzB;EACE,yBAAyB;EACzB,WAAW;EACX,qBAAqB,EAAE;;AAEzB;EACE,yBAAyB;EACzB,WAAW;EACX,qBAAqB,EAAE;;AAEzB;;;;;EAKE,yBAAyB;EACzB,qBAAqB,EAAE;;AAEzB;EACE,eAAe;EACf,aAAa,EAAE;;AAEjB;;EAEE,WAAW,EAAE;;AAEf;;EAEE,qBAAqB,EAAE;;AAEzB;EACE,WAAW,EAAE;;AAEf;gEACgE;AAChE;EACE,yBAAyB,EAAE;;AAE7B;EACE,yBAAyB,EAAE;;AAE7B;gEACgE;AAChE;EACE,WAAW;EACX,eAAe;EACf,YAAY;EACZ,gBAAgB;EAChB,sBAAsB;EACtB,gBAAgB;EAChB,WAAW,EAAE;;AAEf;gEACgE;AAChE;EACE,kBAAkB;EAClB,qBAAqB;EACrB,iBAAiB,EAAE;;AAErB;EACE,qBAAqB;EACrB,sBAAsB;EACtB,kBAAkB;EAClB,SAAS;EACT,UAAU;EACV,WAAW;EACX,YAAY;EACZ,sBAAsB;EAEtB,gCAAgC;EAChC,mBAAmB;EACnB,gBAAgB;EAChB,YAAY,EAAE;;AAEhB;EACE,qBAAqB;EACrB,sBAAsB;EACtB,mBAAmB,EAAE;;AAEvB;EACE,kBAAkB;EAClB,WAAW;EACX,YAAY;EACZ,WAAW;EACX,SAAS;EACT,WAAW;EACX,uBAAuB;EAEvB,+BAA+B;EAC/B,kBAAkB,EAAE;;AAEtB;EACE,yBAAyB,EAAE;;AAE7B;;EAEE,UAAU;EACV,6CAA6C,EAAE;;AAEjD;;EAEE,6CAA6C,EAAE;;AAEjD;EAGE,2BAA2B,EAAE;;AAE/B;;EAEE,gBAAgB,EAAE;;AAEpB;gEACgE;AAChE;EACE,iBAAiB,EAAE;;AAErB;EACE,kBAAkB;EAClB,iBAAiB,EAAE;;AAErB;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;EACV,UAAU;EACV,SAAS,EAAE;;AAEb;EACE,mBAAmB,EAAE;;AAEvB;EACE,kBAAkB;EAClB,OAAO;EACP,SAAS,EAAE;;AAEb;EACE,kBAAkB;EAClB,UAAU;EACV,QAAQ,EAAE;;AAEZ;EACE,mBAAmB;EACnB,yBAAyB;EACzB,kBAAkB;EAClB,UAAU;EACV,SAAS;EACT,aAAa;EACb,YAAY;EACZ,wCAAwC,EAAE;;AAE5C;EACE,SAAS,EAAE;;AAEb;EACE,cAAc;EACd,qBAAqB;EACrB,iBAAiB,EAAE;;AAErB;EACE,mBAAmB,EAAE;;AAEvB;EACE,kBAAkB;EAClB,sBAAsB;EACtB,cAAc;EACd,kBAAkB;EAClB,WAAW;EACX,yBAAyB;EACzB,eAAe,EAAE;;AAEnB;EACE;IACE,cAAc;IACd,WAAW,EAAE;EACf;IACE,UAAU;IACV,eAAe;IACf,iBAAiB,EAAE,EAAE;;AAEzB;EACE;;;IAGE,eAAe,EAAE;EACnB;IACE,kBAAkB,EAAE,EAAE;;AAE1B;EACE;IACE,WAAW;IACX,iBAAiB,EAAE;EACrB;IACE,WAAW,EAAE,EAAE;;AAEnB;gEACgE;AAChE;EACE,gBAAgB;EAChB,yBAAyB;EAEzB,yCAAyC;EACzC,kBAAkB;EAClB,gBAAgB;EAChB,aAAa,EAAE;;AAEjB;EACE,eAAe,EAAE;;AAEnB;EACE,WAAW;EACX,cAAc;EACd,WAAW;EACX,kBAAkB;EAClB,YAAY;EACZ,SAAS,EAAE;;AAEb;EACE,WAAW;EACX,WAAW,EAAE;;AAEf;EACE,sBAAsB;EACtB,qBAAqB;EACrB,UAAU,EAAE;;AAEd;EACE,iBAAiB;EACjB,YAAY,EAAE;;AAEhB;EACE,6BAA6B,EAAE;;AAEjC;EACE,mBAAmB,EAAE;;AAEvB;EACE,8BAA8B,EAAE;;AAElC;EACE,aAAa,EAAE;;AAEjB;EACE,WAAW;EACX,mBAAmB;EACnB,kBAAkB,EAAE;;AAEtB;EACE,kBAAkB,EAAE;;AAEtB;EACE,kBAAkB;EAClB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,yBAAyB;EACzB,sBAAsB;EACtB,8BAA8B;EAC9B,gBAAgB;EAChB,kBAAkB;EAClB,sBAAsB;EACtB,eAAe;EACf,gBAAgB,EAAE;;AAEpB;;;EAGE,YAAY;EACZ,YAAY;EACZ,gBAAgB;EAChB,gBAAgB;EAChB,sBAAsB;EACtB,uBAAuB;EACvB,eAAe;EACf,gBAAgB,EAAE;;AAEpB;EACE,WAAW,EAAE;;AAEf;EACE,eAAe,EAAE;;AAEnB;EACE,YAAY;EACZ,gBAAgB;EAChB,kBAAkB;EAClB,YAAY,EAAE;;AAEhB;EACE,iBAAiB;EACjB,iBAAiB,EAAE;;AAErB;;;EAGE,cAAc,EAAE;;AAElB;EACE,cAAc;EACd,iBAAiB;EACjB,qBAAqB,EAAE;;AAEzB;EACE,iBAAiB,EAAE;;AAErB;EACE,eAAe;EACf,gBAAgB,EAAE;;AAEpB;EACE,kBAAkB,EAAE;;AAEtB;EACE,mBAAmB;EACnB,qBAAqB,EAAE;;AAEzB;EACE,WAAW;EACX,eAAe,EAAE;;AAEnB;EACE,YAAY;EACZ,iBAAiB;EACjB,kBAAkB,EAAE;;AAEtB;EACE,iBAAiB,EAAE;;AAErB;EACE,YAAY,EAAE;;AAEhB;EACE,UAAU,EAAE;;AAEd;EACE,cAAc;EACd,YAAY,EAAE;;AAEhB;EACE,iBAAiB;EACjB,iBAAiB,EAAE;;AAErB;EACE,eAAe,EAAE;;AAEnB;EACE,aAAa,EAAE;;AAEjB;EACE,eAAe,EAAE;;AAEnB;EACE,gBAAgB,EAAE;;AAEpB;EACE,UAAU;EACV,SAAS,EAAE;;AAEb;EACE,YAAY,EAAE;;AAEhB;EACE,cAAc;EACd,kBAAkB,EAAE;;AAEtB;EACE,aAAa;EACb,SAAS,EAAE;;AAEb;EACE,eAAe;EACf,gBAAgB;EAChB,UAAU,EAAE;;AAEd;EACE,qBAAqB,EAAE;;AAEzB;EACE,WAAW;EACX,gBAAgB,EAAE;;AAEpB;EACE,WAAW,EAAE;;AAEf;EACE,gBAAgB,EAAE;;AAEpB;EACE,yBAAyB;EACzB,6BAA6B,EAAE;;AAEjC;EACE,aAAa;EACb,eAAe;EACf,mBAAmB;EACnB,cAAc,EAAE;;AAElB;EACE,gBAAgB,EAAE;;AAEpB;EACE,YAAY;EACZ,iBAAiB,EAAE;;AAErB;EACE,WAAW;EACX,cAAc;EACd,0BAAkB;MAAlB,kBAAkB,EAAE;;AAEtB;EACE,yBAAyB,EAAE;;AAE7B;EACE,gBAAgB,EAAE;;AAEpB;EACE,YAAY;EACZ,oBAAoB;EACpB,gCAAgC,EAAE;;AAEpC;EACE,gBAAgB;EAChB,iBAAiB;EACjB,eAAe;EACf,qBAAqB;EACrB,mBAAmB,EAAE;;AAEvB;;EAEE,sBAAsB,EAAE;;AAE1B;EACE,iBAAiB,EAAE;;AAErB;EACE,kBAAkB;EAClB,WAAW,EAAE;;AAEf;EACE,gCAAgC;EAChC,gBAAgB;EAChB,sBAAsB,EAAE;;AAE1B;EACE,cAAc,EAAE;;AAElB;EACE,gBAAgB,EAAE;;AAEpB;EACE,kBAAkB;EAClB,mBAAmB;EACnB,kBAAkB;EAClB,mBAAmB,EAAE;;AAEvB;EACE;IACE,eAAe,EAAE;EACnB;IACE,0BAA0B;IAC1B,mBAAmB,EAAE,EAAE;;AAE3B;EACE;;IAEE,SAAS;IACT,WAAW,EAAE;EACf;IACE,SAAS;IACT,eAAe,EAAE;EACnB;IACE,6BAA6B;IAC7B,cAAc;IACd,gBAAgB,EAAE,EAAE;;AAExB;EACE;IACE,kBAAkB,EAAE;EACtB;IACE,WAAW;IACX,kBAAkB;IAClB,SAAS;IACT,WAAW;IACX,gBAAgB,EAAE;EACpB;IACE,WAAW;IACX,kBAAkB;IAClB,WAAW,EAAE;EACf;IACE,eAAe,EAAE;EACnB;IACE,eAAe;IACf,gBAAgB,EAAE;EACpB;IACE,gBAAgB,EAAE;EACpB;;;IAGE,uBAAuB;IACvB,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB,EAAE;EACpB;IACE,aAAa,EAAE;EACjB;IACE,mBAAmB;IACnB,sBAAsB,EAAE;EAC1B;IACE,kBAAkB,EAAE,EAAE;;AAE1B;EACE;IACE,UAAU,EAAE;EACd;IACE,yBAAyB,EAAE;EAC7B;IACE,uBAAuB,EAAE;EAC3B;;IAEE,gBAAgB;IAChB,WAAW,EAAE;EACf;IACE,iBAAiB,EAAE;EACrB;;IAEE,eAAe,EAAE;EACnB;;IAEE,yBAAyB;IACzB,mBAAmB,EAAE;EACvB;IACE,UAAU,EAAE;EACd;IACE,kBAAkB,EAAE;EACtB;IACE,cAAc,EAAE;EAClB;IACE,YAAY,EAAE;EAChB;;IAEE,YAAY,EAAE;EAChB;;;;;;;;IAQE,WAAW,EAAE;EACf;;;IAGE,wBAAwB,EAAE;EAC5B;IACE,aAAa,EAAE;EACjB;IACE,gBAAgB,EAAE,EAAE;;AAExB;gEACgE;AAChE;EACE,mBAAmB;EACnB,+BAA+B;EAC/B,eAAe;EACf,qCAAqC;EACrC,cAAc;EACd,cAAc;EACd,gBAAgB;EAChB,WAAW;EACX,aAAa;EACb,aAAa;EACb,aAAa,EAAE;;AAEjB;EACE,gBAAgB;EAChB,WAAW,EAAE;;AAEf;EACE,gBAAgB,EAAE;;AAEpB;gEACgE;AAChE;EACE,eAAe,EAAE;;AAEnB;EACE,aAAa;EACb,iBAAiB,EAAE;;AAErB;EACE,gBAAgB,EAAE;;AAEpB;gEACgE;AAChE;EACE,iBAAiB;EACjB,cAAc;EACd,aAAa,EAAE;;AAEjB;;EAEE,kBAAkB;EAClB,UAAU;EACV,gBAAgB;EAChB,kBAAkB;EAClB,UAAU;EACV,yBAAyB;EACzB,+BAA+B;EAC/B,sBAAsB;EACtB,gBAAgB,EAAE;;AAEpB;EACE,SAAS;EACT,SAAS,EAAE;;AAEb;;EAEE,cAAc;EACd,kBAAkB;EAClB,SAAS;EACT,UAAU;EACV,yBAAyB,EAAE;;AAE7B;EACE,mBAAmB,EAAE;;AAEvB;;;EAGE,aAAa;EACb,SAAS;EACT,YAAY;EACZ,qBAAqB;EACrB,gCAAgC;EAChC,gBAAgB;EAChB,kBAAkB;EAClB,mBAAmB,EAAE;;AAEvB;;;;EAIE,uBAAuB;EACvB,UAAU;EACV,oBAAoB,EAAE;;AAExB;EACE,WAAW;EACX,UAAU;EACV,YAAY;EACZ,gBAAgB;EAChB,kBAAkB;EAClB,QAAQ;EACR,MAAM;EACN,SAAS;EACT,UAAU,EAAE;;AAEd;EACE,SAAS;EACT,sBAAsB,EAAE;;AAE1B;;EAEE,qBAAqB,EAAE;;AAEzB;EACE,cAAc,EAAE;;AAElB;EACE,iBAAiB;EACjB,iBAAiB;EACjB,WAAW,EAAE;;AAEf;EACE,iBAAiB;EACjB,WAAW;EACX,sBAAsB;EACtB,kBAAkB;EAClB,kBAAkB,EAAE;;AAEtB;;;EAGE,aAAa,EAAE;;AAEjB;EACE,cAAc,EAAE;;AAElB,UAAU;AACV;;;EAGE,+BAA+B,EAAE;;AAEnC,SAAS;AACT;;;EAGE,+BAA+B,EAAE;;AAEnC,WAAW;AACX;;;EAGE,+BAA+B,EAAE;;AAEnC,cAAc;AACd;;;EAGE,+BAA+B,EAAE;;AAEnC,aAAa;AACb;;;EAGE,+BAA+B,EAAE;;AAEnC,UAAU;AACV;;;EAGE,+BAA+B,EAAE;;AAEnC,YAAY;AACZ;;;EAGE,+BAA+B,EAAE;;AAEnC,UAAU;AACV;;;EAGE,4BAA4B,EAAE;;AAEhC,0BAA0B;AAC1B,cAAc;AACd;;;EAGE,+BAA+B,EAAE;;AAEnC,SAAS;AACT;;;EAGE,+BAA+B,EAAE;;AAEnC;EACE,WAAW,EAAE;;AAEf;EACE;;IAEE,WAAW,EAAE;EACf;IACE,uBAAuB,EAAE;EAC3B;IACE,WAAW;IACX,YAAY;IACZ,eAAe;IACf,iBAAiB;IACjB,SAAS,EAAE;EACb;IACE,WAAW;IACX,YAAY,EAAE;EAChB;;IAEE,gBAAgB;IAChB,kBAAkB;IAClB,YAAY;IACZ,aAAa;IACb,UAAU;IACV,WAAW,EAAE,EAAE;;AAEnB,oBAAoB;AACpB;;EAEE,UAAU,EAAE;;AAEd;EACE,SAAS;EACT,gBAAgB;EAChB,gBAAgB;EAChB,sBAAsB,EAAE;;AAE1B;EACE,SAAS;EACT,cAAc;EACd,aAAa;EACb,yBAAyB;EACzB,yCAAyC,EAAE;;AAE7C;EACE,SAAS;EACT,aAAa;EACb,8BAA8B;EAC9B,sBAAsB,EAAE;;AAE1B;EACE;;;IAGE,0BAA0B,EAAE,EAAE;;AAElC;EACE;;;IAGE,yBAAyB,EAAE,EAAE;;AAEjC;EACE,gBAAgB,EAAE;;AAEpB;gEACgE;AAChE,qBAAqB;AACrB;EACE,iBAAiB,EAAE;;AAErB;EACE,sBAAsB;EACtB,kBAAkB;EAClB,sBAAsB;EACtB,sBAAsB;EACtB,gBAAgB,EAAE;;AAEpB;EACE,eAAe;EACf,gBAAgB;EAChB,aAAa,EAAE;;AAEjB,oCAAoC;AACpC;EACE,yBAAyB;EACzB,cAAc;EACd,uBAAuB;EACvB,iCAAiC,EAAE;;AAErC,yCAAyC;AACzC;EACE,yBAAyB;EACzB,kBAAkB;EAClB,6BAA6B,EAAE;;AAEjC;EACE,SAAS,EAAE;;AAEb,oCAAoC;AACpC;EACE,eAAe;EACf,kBAAkB,EAAE;;AAEtB;EACE,cAAc;EACd,gBAAgB;EAChB,kBAAkB,EAAE;;AAEtB;EACE,cAAc;EACd,gBAAgB,EAAE;;AAEpB;EACE,mBAAmB;EACnB,eAAe;EACf,gBAAgB;EAChB,kBAAkB;EAClB,aAAa;EACb,0BAA0B;EAC1B,kBAAkB;EAClB,mBAAmB;EACnB,gBAAgB;EAChB,WAAW,EAAE;;AAEf;EACE,0BAA0B;EAC1B,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,eAAe;EACf,kBAAkB;EAClB,SAAS,EAAE;;AAEb;EACE,cAAc,EAAE;;AAElB;EACE,qBAAqB,EAAE;;AAEzB,oCAAoC;AACpC;EACE,yBAAyB;EACzB,kBAAkB;EAClB,0BAA0B,EAAE;;AAE9B;EACE,cAAc;EACd,yBAAyB;EACzB,WAAW;EACX,qBAAqB;EACrB,eAAe;EACf,gBAAgB;EAChB,yBAAyB;EACzB,kBAAkB;EAClB,YAAY;EACZ,kBAAkB;EAClB,WAAW;EACX,sBAAsB;EACtB,gBAAgB;EAChB,gCAAgC,EAAE;;AAEpC;EACE,yBAAyB,EAAE;;AAE7B,yCAAyC;AACzC;EACE;IACE,WAAW;IACX,UAAU,EAAE;EACd;IACE,YAAY;IACZ,UAAU,EAAE,EAAE;;AAElB;EACE;IACE,UAAU,EAAE;EACd;IACE,UAAU,EAAE,EAAE;;AAElB,4DAA4D;AAC5D;;EAEE,WAAW;EACX,WAAW,EAAE;;AAEf,kDAAkD;AAClD;EACE,yBAAyB;EACzB,WAAW;EACX,aAAa,EAAE;;AAEjB;EACE,cAAc;EACd,gBAAgB,EAAE;;AAEpB;EACE,kBAAkB;EAClB,WAAW;EACX,WAAW;EACX,qBAAqB;EACrB,cAAc;EACd,eAAe;EACf,iBAAiB;EACjB,kBAAkB;EAClB,YAAY,EAAE;;AAEhB;;+DAE+D;AAC/D;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BE;AACF;EACE,mBAAmB,EAAE;;AAEvB;EACE,gBAAgB,EAAE;;AAEpB;EACE,cAAc;EACd,kBAAkB;EAClB,UAAU,EAAE;;AAEd;EACE,kBAAkB,EAAE;;AAEtB;;EAEE,eAAe,EAAE;;AAEnB;EACE,gBAAgB,EAAE;;AAEpB;EACE,eAAe,EAAE;;AAEnB;;EAEE,aAAa,EAAE;;AAEjB;;EAEE,cAAc,EAAE;;AAElB;EACE,eAAe,EAAE;;AAEnB;EACE,WAAW;EACX,eAAe;EACf,kBAAkB;EAClB,oBAAoB;EACpB,eAAe,EAAE","file":"assets/css/edd-admin.min.css","sourcesContent":["/**\n * EDD Admin CSS\n *\n * @package EDD\n * @subpackage Admin CSS\n * @copyright Copyright (c) 2015, Pippin Williamson\n * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License\n */\n/**\n * Colors\n */\n/**\n * Colors\n */\n/**\n * Fonts & basic variables.\n */\n/**\n * Grid System.\n * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/\n */\n/**\n * Dimensions.\n */\n/**\n * Shadows.\n */\n/**\n * Editor widths.\n */\n/**\n * Block UI.\n */\n/**\n * Border radii.\n */\n/**\n * Block paddings.\n */\n/**\n * Breakpoint mixins\n */\n/**\n * Long content fade mixin\n *\n * Creates a fading overlay to signify that the content is longer\n * than the space allows.\n */\n/**\n * Focus styles.\n */\n/**\n * Applies editor left position to the selector passed as argument\n */\n/**\n * Styles that are reused verbatim in a few places\n */\n/**\n * Allows users to opt-out of animations via OS-level preferences.\n */\n/**\n * Reset default styles for JavaScript UI based pages.\n * This is a WP-admin agnostic reset\n */\n/**\n * Reset the WP Admin page styles for Gutenberg-like pages.\n */\n/**\n * Breakpoints & Media Queries\n */\n/**\n * WordPress Core colors current as of 5.5.1.\n */\n.edd-custom-price-option-sections-wrap {\n display: none;\n border-width: 0 1px 1px;\n border-style: solid;\n border-color: #e5e5e5;\n box-sizing: border-box;\n width: 100%; }\n\n.edd-custom-price-option-section {\n display: block;\n padding: 10px 8px;\n border-bottom: 1px solid rgba(222, 222, 222, 0.3); }\n .edd-custom-price-option-section-title {\n display: block;\n font-size: 14px;\n font-weight: 600;\n padding: 0 0 10px; }\n .edd-custom-price-option-section-content {\n display: flex;\n gap: 12px;\n margin-bottom: 6px; }\n .edd-custom-price-option-section:last-child {\n border-bottom: none; }\n\n.toggle-custom-price-option-section {\n color: #777; }\n .toggle-custom-price-option-section:hover {\n color: #444; }\n\n.edd-form-group__control--is-inline {\n display: inline-flex;\n align-items: flex-end; }\n\n.edd-form-row {\n display: flex;\n flex-wrap: wrap;\n gap: 12px; }\n .edd-form-row__column {\n display: inline-flex;\n flex-direction: column;\n justify-content: flex-end; }\n .edd-form-row__column.edd-form-group {\n margin-bottom: 0; }\n\n#edd-migration-progress .dashicons-minus {\n color: #949494; }\n\n#edd-migration-progress .dashicons-yes {\n color: green; }\n\n#edd-migration-progress .dashicons-update:before {\n animation: rotation 2s infinite linear;\n display: block; }\n\n#edd-v3-migration-remove-legacy-data-submit-wrap {\n display: flex;\n align-items: center;\n gap: 6px; }\n #edd-v3-migration-remove-legacy-data-submit-wrap .button {\n margin: 0; }\n\n/**\n * Colors\n */\n/**\n * Colors\n */\n/**\n * Fonts & basic variables.\n */\n/**\n * Grid System.\n * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/\n */\n/**\n * Dimensions.\n */\n/**\n * Shadows.\n */\n/**\n * Editor widths.\n */\n/**\n * Block UI.\n */\n/**\n * Border radii.\n */\n/**\n * Block paddings.\n */\n/**\n * Breakpoint mixins\n */\n/**\n * Long content fade mixin\n *\n * Creates a fading overlay to signify that the content is longer\n * than the space allows.\n */\n/**\n * Focus styles.\n */\n/**\n * Applies editor left position to the selector passed as argument\n */\n/**\n * Styles that are reused verbatim in a few places\n */\n/**\n * Allows users to opt-out of animations via OS-level preferences.\n */\n/**\n * Reset default styles for JavaScript UI based pages.\n * This is a WP-admin agnostic reset\n */\n/**\n * Reset the WP Admin page styles for Gutenberg-like pages.\n */\n/**\n * Breakpoints & Media Queries\n */\n/**\n * WordPress Core colors current as of 5.5.1.\n */\n#edd-filters {\n padding: 10px;\n margin: 0;\n display: flex;\n justify-content: space-between;\n flex-wrap: wrap;\n gap: 8px; }\n #edd-filters .filter-items {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n float: none;\n flex-grow: 1; }\n #edd-filters .filter-items .edd-date-range-options {\n display: inline-block;\n margin: 10px 0; }\n #edd-filters > p {\n color: #757575; }\n #edd-filters input[type=\"text\"].edd_datepicker,\n #edd-filters input[type=\"number\"] {\n max-width: 105px; }\n #edd-filters input[type=\"number\"],\n #edd-filters .button-secondary {\n margin-bottom: 0; }\n #edd-filters .search-form {\n margin: 0; }\n @media screen and (max-width: 480px) {\n #edd-filters span {\n margin: 2px 0; } }\n\n#edd-advanced-filters {\n position: relative; }\n #edd-advanced-filters .inside {\n z-index: 99;\n position: absolute;\n top: 29px;\n right: 0;\n border: 1px solid #e0e0e0;\n padding: 0;\n background: #fff;\n box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);\n min-width: 285px;\n opacity: 0;\n visibility: hidden; }\n #edd-advanced-filters fieldset {\n display: block;\n padding: 10px 15px 15px;\n margin: 10px 0; }\n #edd-advanced-filters fieldset:not(:last-of-type) {\n border-bottom: 1px solid #e0e0e0; }\n #edd-advanced-filters fieldset:last-of-type {\n padding-bottom: 5px; }\n #edd-advanced-filters fieldset.edd-add-on-filters label,\n #edd-advanced-filters fieldset.edd-add-on-filters span,\n #edd-advanced-filters fieldset.edd-add-on-filters p,\n #edd-advanced-filters fieldset.edd-add-on-filters div {\n display: block;\n margin-bottom: 2px; }\n #edd-advanced-filters div.edd-select-chosen:not(:last-child) {\n margin-bottom: 10px; }\n #edd-advanced-filters.open .edd-advanced-filters-button {\n background: #e0e0e0;\n border-color: #949494;\n box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);\n -webkit-transform: translateY(1px);\n transform: translateY(1px); }\n #edd-advanced-filters.open .inside {\n visibility: visible;\n opacity: 1;\n -webkit-transition: opacity 0.2s ease-in;\n -moz-transition: opacity 0.2s ease-in;\n -o-transition: opacity 0.2s ease-in;\n transition: opacity 0.2s ease-in; }\n\n.download_page_edd-reports #edd-filters {\n margin-bottom: -1px;\n box-shadow: none; }\n @media screen and (max-width: 782px) {\n .download_page_edd-reports #edd-filters {\n gap: 0; } }\n\n.edd-old-log-filters {\n margin-top: -30px;\n margin-left: 2px; }\n\n.edd-mobile-link {\n line-height: 32px; }\n .edd-mobile-link a {\n text-decoration: none; }\n .edd-mobile-link a:before, .edd-mobile-link a:after {\n display: inline-block;\n -webkit-font-smoothing: antialiased;\n font: normal 20px/30px \"dashicons\";\n vertical-align: top;\n margin: 1px 0 0 0;\n padding: 0; }\n .edd-mobile-link a:before {\n content: \"\\f470\";\n color: #757575;\n margin-right: -3px; }\n .edd-mobile-link a:after {\n content: \"\\f504\"; }\n\n#edd-submit-refund-status {\n text-align: center;\n font-size: 1.2em; }\n #edd-submit-refund-status .edd-submit-refund-message:before {\n font-family: dashicons;\n font-size: 1.5em;\n vertical-align: middle;\n color: #fff;\n border-radius: 16px;\n margin: 5px; }\n #edd-submit-refund-status .edd-submit-refund-message.success:before {\n content: \"\\f147\";\n background-color: #008a20;\n padding-right: 1px; }\n #edd-submit-refund-status .edd-submit-refund-message.fail {\n display: block;\n margin-bottom: 16px; }\n #edd-submit-refund-status .edd-submit-refund-message.fail::before {\n content: \"\\f335\";\n background-color: #d63638; }\n\n.refunditems td,\n.refunditems th.check-column {\n vertical-align: baseline; }\n\n.refunditems .column-amount,\n.refunditems .column-quantity,\n.refunditems .column-subtotal,\n.refunditems .column-tax,\n.refunditems .column-discount,\n.refunditems .column-total {\n width: 80px; }\n\n.refunditems .edd-form-group__control {\n display: flex;\n align-items: center; }\n .refunditems .edd-form-group__control input {\n background-color: transparent;\n border: 0;\n border-bottom: 1px solid;\n border-radius: 0;\n box-shadow: none;\n text-align: right;\n width: 100%; }\n .refunditems .edd-form-group__control input:disabled {\n border-bottom: none; }\n .refunditems .edd-form-group__control input:focus {\n border-bottom: 1px solid var(--wp-admin-theme-color-darker-10);\n box-shadow: 0 1px 0 var(--wp-admin-theme-color-darker-10); }\n .refunditems .edd-form-group__control .is-before + span > input {\n text-align: left; }\n\n.refunditems .edd-refund-submit-line-total {\n background-color: #fff !important; }\n .refunditems .edd-refund-submit-line-total td {\n text-align: right; }\n\n.refunditems .edd-refund-submit-line-total-amount {\n display: inline-block;\n margin-left: 20px;\n text-align: left;\n width: 80px; }\n\n.refunditems #edd-refund-submit-subtotal td {\n border-top: 2px solid #c3c4c7; }\n\n@media screen and (max-width: 782px) {\n .refunditems td.column-total {\n margin-bottom: 16px; }\n .refunditems .edd-refund-submit-line-total-amount {\n padding-right: 16px;\n width: unset; } }\n\n.edd-submit-refund-actions {\n margin: 16px 0 0; }\n\n.did-refund .refunditems,\n.did-refund .edd-submit-refund-actions {\n display: none; }\n\n/**\n * Notes:\n *\n * [1] Backwards compatibility for vertical tabs < 3.0\n */\n.edd-hidden {\n display: none; }\n\n.edd-clearfix:after {\n content: \"\";\n display: table;\n clear: both; }\n\n.edd-wrap a,\n.edd-notice .notice-dismiss {\n text-decoration: none; }\n\n/**\n * Tag specificity should not be needed, but cannot\n * safely be removed for fear of breaking even more things.\n */\n.wp-core-ui .edd-delete,\na.edd-delete {\n color: #a00; }\n\n.wp-core-ui .edd-delete:hover,\na.edd-delete:hover {\n color: #f00; }\n\n/* General Settings Styles\n-------------------------------------------------------------- */\n.edd_datepicker {\n height: 29px; }\n\n.edd-from-to-wrapper input {\n width: 105px;\n margin: 0;\n position: relative;\n z-index: 1; }\n\n.edd-from-to-wrapper input[name*=\"start\"],\n.edd-from-to-wrapper input[name=\"filter_from\"] {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0; }\n\n.edd-from-to-wrapper input[name*=\"end\"],\n.edd-from-to-wrapper input[name=\"filter_to\"] {\n margin-left: -1px;\n border-top-left-radius: 0;\n border-bottom-left-radius: 0; }\n\n.edd-from-to-wrapper input:focus {\n z-index: 2;\n position: relative; }\n\n.edd-settings-sub-nav {\n margin: 0 0px 10px 0;\n width: 100%;\n border-bottom: 1px solid #ccc;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); }\n\n.edd-settings-sub-nav a {\n padding: 13px;\n display: block; }\n\n.edd-settings-sub-nav a.current {\n border-bottom: 4px solid #000;\n padding-bottom: 9px; }\n\n.admin-color-fresh .edd-settings-sub-nav a.current {\n border-bottom-color: #00a0d2; }\n\n/* Blue */\n.admin-color-blue .edd-settings-sub-nav a.current {\n border-bottom-color: #096484; }\n\n/* Coffee */\n.admin-color-coffee .edd-settings-sub-nav a.current {\n border-bottom-color: #c7a589; }\n\n/* Ectoplasm */\n.admin-color-ectoplasm .edd-settings-sub-nav a.current {\n border-bottom-color: #a3b745; }\n\n/* Midnight */\n.admin-color-midnight .edd-settings-sub-nav a.current {\n border-bottom-color: #e14d43; }\n\n/* Ocean */\n.admin-color-ocean .edd-settings-sub-nav a.current {\n border-bottom-color: #627c83; }\n\n/* Sunrise */\n.admin-color-sunrise .edd-settings-sub-nav a.current {\n border-bottom-color: #be3631; }\n\n/* Light */\n.admin-color-light .edd-settings-sub-nav a.current {\n border-bottom-color: #888; }\n\n/* bbPress Color Schemes */\n/* Evergreen */\n.admin-color-evergreen .edd-settings-sub-nav a.current {\n border-bottom-color: #36533f; }\n\n/* Mint */\n.admin-color-mint .edd-settings-sub-nav a.current {\n border-bottom-color: #4f6d59; }\n\n.download_page_edd-settings .edd-check-wrapper {\n clear: both; }\n\n.download_page_edd-settings .edd-check-wrapper label {\n margin: -2px 0 3px 0;\n display: inline-block;\n vertical-align: initial; }\n\n.download_page_edd-settings .form-table tr > th > strong,\n.download_page_edd-settings .form-table tr > th > h3 {\n font-size: 1.2em;\n font-weight: 600;\n margin: 0 auto; }\n\n.edd-sortable-list {\n margin: 0;\n width: 300px;\n position: relative; }\n\n.edd-sortable-list li {\n margin: 0;\n padding: 0;\n position: relative;\n height: 28px;\n cursor: move; }\n\n.edd-sortable-list li label * {\n vertical-align: middle; }\n\n.edd-sortable-list li label:after {\n display: block;\n width: 17px;\n height: 17px;\n position: absolute;\n right: 6px;\n top: 0px;\n color: #aaa;\n font-family: dashicons;\n font-size: 17px;\n content: '\\f228';\n cursor: move; }\n\n.form-table .edd-sortable-list li label {\n display: block;\n height: 28px;\n padding: 0;\n margin: 0; }\n\n.edd-sortable-list .payment-icon {\n width: 32px;\n height: 24px;\n position: relative;\n top: -2px;\n margin-right: 5px; }\n\n/* Tooltips */\n.edd-help-tip {\n cursor: help;\n margin-top: -2px;\n font-size: 24px;\n color: grey; }\n\n.edd-ui-tooltip {\n position: absolute;\n background: #333 !important;\n border-width: 1px !important;\n border-radius: 3px !important;\n box-shadow: 1px 1px 2px 1px rgba(214, 214, 214, 0.5) !important;\n color: #dedede !important;\n max-width: 300px !important;\n padding: 7px !important;\n text-rendering: optimizeLegibility;\n text-shadow: none !important;\n z-index: 9999 !important; }\n\n/* =Payment Icon Styling\n-------------------------------------------------------------- */\n.download_page_edd-settings .edd-settings-payment-icon-wrapper {\n margin-top: 5px; }\n\n.download_page_edd-settings .edd-settings-payment-icon-wrapper input {\n margin-top: 1px; }\n\n.download_page_edd-settings .form-table .edd-settings-payment-icon-wrapper input[type=\"checkbox\"] + label {\n margin: 0;\n display: inline-block; }\n\n.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-icon-image {\n margin-right: 5px;\n width: 32px;\n display: inline-block;\n vertical-align: middle; }\n\n.download_page_edd-settings .edd-settings-payment-icon-wrapper .payment-option-name {\n vertical-align: middle; }\n\n/* =Tax Settings Style\n-------------------------------------------------------------- */\n.download_page_edd-settings .taxrates th,\n.download_page_edd-settings .taxrates td {\n padding: 8px 10px; }\n\n.download_page_edd-settings .taxrates td {\n line-height: 1.5em;\n vertical-align: top;\n margin: 0; }\n\n.download_page_edd-settings .taxrates .regular-text {\n width: 100%; }\n\n/* =Add Ons Styles\n-------------------------------------------------------------- */\n.edd-add-ons-footer {\n padding-top: 10px; }\n\n#edd-add-ons .subsubsub .dashicons {\n margin-top: 3px; }\n\n#edd-add-ons .edd-add-ons-container {\n clear: both;\n padding-top: 10px; }\n\n#edd-add-ons .search-box .button-secondary span {\n margin: 2px -5px 0 4px;\n padding: 0;\n color: #aaa; }\n\n#edd-add-ons .edd-extension {\n background: #fff;\n border: 1px solid #ccc;\n float: left;\n padding: 14px;\n position: relative;\n margin: 0 15px 16px 0;\n width: 320px;\n height: 315px;\n opacity: 0.9;\n transition: all .2s ease-in-out;\n cursor: default; }\n\n#edd-add-ons .edd-extension:hover {\n border: 1px solid #bbb;\n opacity: 1;\n transform: scale(1.05);\n z-index: 5; }\n\n#edd-add-ons .edd-extension h3 {\n font-size: 13px;\n margin: 0 0 8px; }\n\n#edd-add-ons .edd-extension .button-secondary {\n position: absolute;\n bottom: 14px;\n left: 14px; }\n\n#edd-add-ons .edd-browse-all {\n clear: both;\n width: 100%; }\n\n#edd-add-ons .edd-extension .third-party {\n display: none; }\n\n#edd-add-ons .edd-add-ons-container .edd-extension:first-child {\n background-color: #85c0e5;\n border-color: #62a9d7;\n color: #fff;\n box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2); }\n\n#edd-add-ons .edd-add-ons-container .edd-extension:first-child h3 {\n color: #fff; }\n\n/* Mock blue \"Primary\" styling on Starter Package */\n#edd-add-ons .edd-add-ons-container .edd-extension:first-child .button-secondary {\n background: #0085ba;\n border-color: #0073aa #006799 #006799;\n box-shadow: 0 1px 0 #006799;\n color: #fff;\n text-decoration: none;\n text-shadow: 0 -1px 1px #006799, 1px 0 1px #006799, 0 1px 1px #006799, -1px 0 1px #006799; }\n\n#edd-add-ons .edd-extension .wp-post-image {\n width: 100%;\n height: auto;\n vertical-align: bottom; }\n\n/* Insert Download\n-------------------------------------------------------------- */\n#TB_window {\n overflow: hidden; }\n\n#TB_title {\n padding: 5px; }\n\n#TB_ajaxContent {\n width: calc(100% - 30px) !important;\n padding: 15px;\n margin: 0;\n height: calc(100% - 118px) !important; }\n\n#TB_ajaxWindowTitle {\n font-size: 18px;\n font-weight: 600;\n line-height: 30px; }\n\n#TB_closeWindowButton {\n right: 6px;\n top: 6px; }\n\n#choose-download-wrapper {\n width: 100%; }\n\n#choose-download-wrapper .wrap {\n overflow-y: scroll;\n margin: 0;\n padding: 0;\n height: calc(100% - 50px); }\n\n#choose-download-wrapper .submit-wrapper {\n position: absolute;\n width: 100%;\n bottom: 0;\n padding: 0;\n margin: 0 0 0 -15px;\n text-align: right; }\n\n#choose-download-wrapper .submit-wrapper div {\n background-color: #fafafa;\n padding: 15px;\n border-top: 1px solid #ddd; }\n\n/* Media Buttons Styles\n-------------------------------------------------------------- */\n.wp-media-buttons .button.edd-thickbox {\n padding-left: 0; }\n\n.wp-media-buttons .button.edd-email-tags-inserter .dashicons {\n margin-top: -2px; }\n\n/* Add/View Order Styles\n-------------------------------------------------------------- */\n/** Mimic WordPress 5.0 block-editor header region styles. */\n.download_page_edd-payment-history .edit-post-editor-regions__header {\n flex-shrink: 0;\n height: auto;\n border-bottom: 1px solid #e2e4e7;\n z-index: 30;\n position: -webkit-sticky;\n position: sticky;\n top: 32px;\n /** EDD-specific */\n margin-left: -20px; }\n\n@media screen and (max-width: 782px) {\n .download_page_edd-payment-history .edit-post-editor-regions__header {\n position: initial;\n top: 46px; } }\n\n.download_page_edd-payment-history .edit-post-header {\n height: 56px;\n padding: 4px 2px;\n background: #fff;\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: center;\n /** EDD-specific */\n max-width: 100%;\n box-sizing: border-box;\n padding-left: 20px;\n padding-right: 20px; }\n\n@media screen and (max-width: 782px) {\n .download_page_edd-payment-history .edit-post-header {\n padding-left: 10px;\n padding-right: 10px; } }\n\n@media (min-width: 280px) {\n .download_page_edd-payment-history .edit-post-header {\n flex-wrap: nowrap; } }\n\n.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar {\n order: 0; }\n\n.download_page_edd-payment-history .edit-post-header .edit-post-header__settings {\n order: 1; }\n\n.download_page_edd-payment-history .edit-post-header #publishing-action,\n.download_page_edd-payment-history .edit-post-header .edit-post-header__toolbar,\n.download_page_edd-payment-history .edit-post-header .edit-post-header__settings {\n display: flex;\n align-items: center; }\n\n.download_page_edd-payment-history .edit-post-header #publishing-action .spinner {\n margin: 0 5px 0 0; }\n\n.download_page_edd-payment-history .edit-post-header .button-primary {\n margin: 2px;\n height: 34px;\n line-height: 32px;\n font-size: 13px; }\n\n#edd-order-items .hndle {\n display: flex;\n align-items: center;\n justify-content: space-between; }\n\n#edd-order-items .hndle .edd-toggle {\n font-weight: normal; }\n\n.edd-add-order-item td {\n vertical-align: middle; }\n\n.edd-add-order-item input {\n width: 80%; }\n\n.edd-add-order-item input[readonly] {\n color: #555;\n background: none;\n border: 1px solid transparent;\n box-shadow: none; }\n\n.order-customer-info .customer-details-wrap {\n margin: 15px 0;\n align-items: center; }\n\n.order-customer-info .customer-details-wrap .spinner {\n margin: 0; }\n\n.order-customer-info .customer-details {\n display: flex;\n flex-direction: column; }\n\n.order-customer-info .customer-details .customer-since {\n color: #666;\n display: block;\n margin: 4px 0 6px; }\n\n.order-customer-info .customer-details > span {\n margin-bottom: 5px; }\n\n.edd-order-add-download-select .spinner {\n display: none; }\n\n/** Overview */\ntable.edd-order-overview-summary {\n border-width: 0;\n table-layout: fixed; }\n\ntable.edd-order-overview-summary--refund {\n border-width: 0; }\n\n@media screen and (min-width: 782px) {\n .edd-order-overview .column-right {\n text-align: right; } }\n\n.edd-ml-auto {\n margin-left: auto !important; }\n\n@media screen and (min-width: 782px) {\n .edd-ml-lg-auto {\n margin-left: auto !important; } }\n\n.edd-ml-auto + .edd-ml-auto {\n margin-left: 10px !important; }\n\n/** Items */\n.edd-order-overview-summary__items-name {\n align-self: flex-start; }\n\n.edd-order-overview-summary__items > :nth-child(odd) {\n background-color: #f9f9f9; }\n\n@media screen and (min-width: 782px) {\n .edd-order-overview-summary__items tr:last-child th,\n .edd-order-overview-summary__items tr:last-child td {\n border-bottom: 1px solid #e5e5e5; } }\n\n@media screen and (max-width: 782px) {\n .edd-order-overview-summary .row-actions > *,\n .edd-order-overview-summary__items-name .row-actions {\n display: block !important; }\n .edd-order-overview-summary .row-actions > *:not(:first-child):before {\n display: none; } }\n\n.edd-order-overview-summary th:not(.column-primary) {\n width: 100px; }\n\n.edd-order-overview-summary .row-actions > *:not(:first-child):before {\n color: #999;\n content: \" | \"; }\n\n.edd-order-overview-summary .row-actions .text {\n color: #555; }\n\n.edd-order-overview-summary .removable {\n display: flex;\n align-items: center;\n position: relative; }\n\n.edd-order-overview-summary .removable .delete {\n display: inline-block;\n margin-right: 10px;\n margin-left: -8px;\n padding: 10px;\n border-right: 1px solid #e5e5e5;\n color: #a00; }\n\n.edd-order-overview-summary .removable .delete:hover {\n color: #dc3232; }\n\n/** Adjustments */\n.edd-order-overview-summary__adjustments .column-primary {\n font-weight: 600; }\n\n.edd-order-overview-summary__adjustments td small {\n font-weight: normal; }\n\n/** Totals */\n.edd-order-overview-summary__subtotal .column-primary,\n.edd-order-overview-summary__tax tr:first-of-type .column-primary,\n.edd-order-overview-summary__total .column-primary {\n font-weight: 600; }\n\n.edd-order-overview-summary__subtotal td,\n.edd-order-overview-summary__adjustments td,\n.edd-order-overview-summary__tax td,\n.edd-order-overview-summary__total td {\n vertical-align: middle; }\n\n.edd-order-overview-summary__tax td small,\n.edd-order-overview-summary__total td small {\n font-weight: normal; }\n\n.edd-order-overview-summary__total .total {\n color: #017d5c;\n display: inline-block; }\n\n.edd-order-overview-summary__total .total.is-negative {\n color: #a00; }\n\n@media screen and (min-width: 783px) {\n .edd-order-overview-summary__adjustments .removable .delete {\n margin-left: -50px; }\n .edd-order-overview-summary__total .total {\n font-size: 150%;\n padding-top: 5px;\n padding-bottom: 5px; } }\n\n.edd-order-overview-summary__total tr:last-child th,\n.edd-order-overview-summary__total tr:last-child td:not(:first-of-type) {\n border-top: 1px solid #e5e5e5; }\n\n.edd-order-overview-summary__total .notice {\n margin: -1px; }\n\n.edd-order-overview-summary__total .notice p {\n font-weight: normal;\n margin: 0.5em 0; }\n\n/** Refunds */\n.edd-order-overview-summary__refunds .column-primary {\n font-weight: 600; }\n\n.edd-order-overview-summary__refunds td small {\n font-weight: normal; }\n\n.edd-order-overview-summary__refunds tr:first-child td {\n border-top: 1px solid #e5e5e5; }\n\n/** Actions */\n#edd-order-overview-actions.inside {\n border-top: 1px solid #ccd0d4;\n margin-top: 0;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n justify-content: space-between; }\n\n#edd-order-overview-actions.inside:empty {\n padding: 0;\n border-top: 0; }\n\n#edd-order-overview-actions.inside > div {\n display: flex;\n align-items: center; }\n\n.edd-order-overview-actions .button {\n width: 100%;\n margin-bottom: 12px; }\n\n.edd-order-overview-actions .button:last-of-type {\n margin-bottom: 0; }\n\n@media screen and (min-width: 782px) {\n .edd-order-overview-actions .button {\n width: auto;\n margin-left: 12px;\n margin-bottom: 0; }\n .edd-order-overview-actions .button:first-of-type {\n margin-left: auto; } }\n\n.edd-order-overview-actions__locked {\n font-style: italic;\n opacity: 0.80; }\n\n@media screen and (max-width: 782px) {\n .edd-order-overview-actions__locked {\n margin-bottom: 12px; } }\n\n.edd-order-overview-actions__refund .dashicons {\n margin-right: 8px; }\n\n/** Dialog */\n.edd-dialog .ui-button-icon-only {\n font-size: 0; }\n\n.download_page_edd-payment-history .ui-dialog,\n.download_page_edd-payment-history .ui-dialog-content {\n overflow: visible; }\n\n.edd-order-overview-modal form > p {\n margin-top: 0; }\n\n.edd-order-overview-modal fieldset legend,\n.edd-order-overview-modal form label {\n display: block;\n margin-bottom: 4px; }\n\n.edd-order-overview-modal fieldset {\n margin-bottom: calc(1em - 3px); }\n\n.edd-order-overview-modal fieldset > p {\n margin: 2px 0 3px; }\n\n.edd-order-overview-modal form .submit {\n margin: 0 -16px -16px;\n padding: 16px;\n background: #fcfcfc;\n border-top: 1px solid #dfdfdf;\n display: flex;\n align-items: center; }\n\n.edd-order-overview-modal form .submit .spinner {\n margin: 0; }\n\n.edd-order-overview-add-item [for=\"auto-calculate\"] {\n display: flex;\n align-items: center; }\n\n.edd-order-overview-add-item [for=\"auto-calculate\"] input[type=\"checkbox\"] {\n margin-top: 0; }\n\n.edd-order-overview-add-item [for=\"auto-calculate\"] .label {\n line-height: 1.15;\n margin-left: 8px; }\n\n.edd-order-overview-add-item [for=\"auto-calculate\"] .label small {\n margin-top: 4px;\n display: block;\n opacity: 0.75; }\n\n.edd-order-overview-add-adjustment .notice,\n.edd-order-overview-add-item .notice {\n margin: 0 0 1rem; }\n\n.edd-order-overview-add-adjustment #description,\n.edd-order-overview-add-discount select {\n width: 100%; }\n\n.edd-order-overview-error {\n font-style: italic;\n color: #a00;\n display: block;\n margin: 4px 0; }\n\n.edd-order-copy-download-link textarea {\n width: 100%; }\n\n/** Columns */\n.wp-list-table.orders .column-number .row-title {\n display: block; }\n\n/** Status labels */\n.edd-admin-order-status-badge {\n padding: 2px 7px;\n border-radius: 4px;\n background: #ececec;\n display: inline-flex;\n align-items: center; }\n\n.edd-admin-order-status-badge__icon {\n opacity: 0.80;\n margin: 0 -2px 0 2px; }\n\n.edd-admin-order-status-badge--refunded .edd-admin-order-status-badge__icon {\n font-size: 16px;\n width: 16px;\n height: 16px; }\n\n.edd-admin-order-status-badge--failed {\n color: #ac3d3d;\n background: #ffd6d6; }\n\n.edd-admin-order-status-badge--failed .edd-admin-order-status-badge__icon {\n margin-left: 0;\n margin-top: -1px; }\n\n.edd-admin-order-status-badge--complete {\n color: #017d5c;\n background: #e5f5f0; }\n\n.edd-admin-order-status-badge--complete .edd-admin-order-status-badge__icon {\n margin-left: 0px; }\n\n.edd-admin-order-status-badge--pending {\n color: #7d6e01;\n background: #f5f2e5; }\n\n.edd-admin-order-status-badge--processing {\n color: #015a7d;\n background: #e5f1f5; }\n\n.wp-list-table.orderitems .refunded .edd-admin-order-status-badge {\n margin-left: 10px; }\n\n.edd-order-resend-email-chooser legend {\n font-weight: bold;\n margin-bottom: 4px; }\n\n.edd-order-resend-email-chooser p {\n margin: 4px 0; }\n\n/* Note Styles\n-------------------------------------------------------------- */\n.edd-notes .edd-note {\n padding: 10px;\n background-color: #ffffee;\n border: 1px solid #cccc00;\n width: 100%;\n position: relative;\n margin-bottom: 10px;\n box-sizing: border-box;\n overflow: hidden; }\n\n.edd-notes .edd-note.deleting {\n opacity: 0.5; }\n\n.edd-notes .edd-note__header {\n display: flex;\n align-items: center; }\n\n.edd-add-note .spinner {\n float: none;\n display: inline-block;\n margin: 0; }\n\n.edd-notes .edd-note time {\n font-size: 11px;\n color: #aaa; }\n\n.edd-notes .edd-note .edd-note-author {\n margin-right: 5px; }\n\n.edd-notes .edd-note .edd-delete-note {\n color: #a00;\n font-weight: bold;\n text-decoration: none;\n margin-left: auto; }\n\n.edd-notes .edd-note .edd-delete-note:hover {\n color: #888; }\n\n.edd-notes .edd-note p:last-child {\n margin-bottom: 0; }\n\n.edd-notes .edd-no-notes {\n margin: 4px 0 10px 0; }\n\ntextarea[name=\"edd-note\"] {\n width: 100%;\n min-height: 70px;\n margin-top: 0; }\n\n.edd-notes-wrapper {\n width: 80%; }\n\n.edd-note-pagination {\n float: right;\n margin: -35px 5px 15px 5px; }\n\n.edd-note-pagination a,\n.edd-note-pagination span.page-numbers {\n padding: 5px 8px;\n margin: 2px;\n text-decoration: none; }\n\n.edd-note-pagination a {\n border: 1px solid #e5e5e5;\n background: #fcfcfc; }\n\n.edd-note-pagination a:last-child,\n.edd-note-pagination span.page-numbers:last-child {\n margin-right: 0; }\n\n/* Discount Code Styles\n-------------------------------------------------------------- */\n#edd-products {\n height: 100px;\n min-width: 200px; }\n\n#edd-add-discount input[type=\"text\"],\n#edd-edit-discount input[type=\"text\"] {\n width: 300px; }\n\n#edd-add-discount .edd-discount-datetime input,\n#edd-edit-discount .edd-discount-datetime input {\n vertical-align: middle; }\n\n#edd-add-discount input[type=\"text\"].edd_datepicker,\n#edd-edit-discount input[type=\"text\"].edd_datepicker {\n display: inline-block;\n width: 183px; }\n\n#edd-edit-discount textarea {\n height: 100px; }\n\n.edd-amount-type-wrapper {\n position: relative;\n display: flex; }\n\n.edd-amount-type-wrapper select {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n width: auto !important; }\n\n.edd-amount-type-wrapper #edd-amount {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n margin-right: -2px;\n padding: 0 8px;\n width: unset;\n max-width: 125px; }\n\n.edd-amount-type-wrapper input:focus {\n z-index: 2; }\n\n/* List Tables\n-------------------------------------------------------------- */\n.wp-list-table.customers .column-primary strong,\n.wp-list-table.emails .column-primary strong,\n.wp-list-table.addresses .column-primary strong,\n.wp-list-table.discounts .column-primary strong,\n.wp-list-table.orders .column-primary strong,\n.wp-list-table.orderitems .column-primary strong,\n.wp-list-table.orderadjustments .column-primary strong {\n font-size: 14px; }\n\n.wp-list-table.emails .column-customer .avatar,\n.wp-list-table.customers .column-primary .avatar {\n float: left;\n margin-right: 10px;\n margin-top: 1px;\n border-radius: 5px; }\n\n/* Row Actions\n-------------------------------------------------------------- */\n.wp-list-table .row-actions span.activate a {\n color: green; }\n\n.wp-list-table .row-actions span.refund a {\n color: #836fff; }\n\n.wp-list-table .row-actions span.cancel a {\n color: #cc8c00; }\n\n.wp-list-table .row-actions span.cancel a:hover,\n.wp-list-table .row-actions span.refund a:hover {\n opacity: 0.8; }\n\n.wp-list-table .type-download .row-actions {\n color: #999; }\n\n/* Nav Tab Styles\n-------------------------------------------------------------- */\n#edd-add-ons {\n margin: 9px 20px -9px 2px; }\n\n.no-js.edit-tags-php.post-type-download .wp-heading-inline {\n position: absolute;\n top: 0; }\n\n.no-js.edit-tags-php.post-type-download .nav-tab-wrapper {\n margin-top: 50px; }\n\n.edit-tags-php.post-type-download .wrap .nav-tab-wrapper .page-title-action,\n.edit-php.post-type-download .wrap .nav-tab-wrapper .page-title-action,\n.download_page_edd-customers .wrap .nav-tab-wrapper .page-title-action,\n.download_page_edd-discounts .wrap .nav-tab-wrapper .page-title-action,\n.download_page_edd-payment-history .wrap .nav-tab-wrapper .page-title-action {\n top: 3px;\n margin-left: 10px;\n line-height: 24px; }\n\n#edd_product_settings .edd-product-options__title,\n#edd_product_settings .inside strong {\n border-top: 1px solid #eee;\n border-bottom: 1px solid #eee;\n background-color: #f9f9f9;\n display: block;\n font-weight: 600;\n margin: 12px -12px;\n padding: 8px 12px; }\n\n#edd_product_settings .edd-product-settings-wrapper:first-of-type .edd-product-options__title,\n#edd_product_settings .inside div:first-child strong {\n margin-top: -8px; }\n\n#edd_product_settings .edd-product-options__title .edd-help-tip,\n#edd_product_settings .inside strong .edd-help-tip {\n float: right;\n font-size: 20px;\n line-height: 1.3; }\n\n#edd_product_settings .label--block {\n display: block;\n margin: 0 0 4px; }\n\n/* Payment History Styles\n-------------------------------------------------------------- */\n#edd-payments-filter ul.subsubsub {\n margin-bottom: 8px; }\n\ntr.status-refunded td {\n background: #cecece;\n border-top-color: #ccc; }\n\nmarquee {\n padding: 0;\n margin: 0; }\n\n@media handheld, only screen and (max-width: 640px) {\n .wp-list-table.downloads th {\n width: auto !important; } }\n\n#edd-download-link-textarea {\n width: 100%; }\n\n/* Metabox Styles\n-------------------------------------------------------------- */\n.edd_files_name_label {\n width: 225px;\n float: left; }\n\n.edd_files_url_label {\n width: 220px;\n float: left; }\n\n#postbox-container-1 .edd_files_name_label {\n width: 80px; }\n\n#postbox-container-1 .edd_files_url_label {\n width: 80px; }\n\n#edd_product_prices .inside,\n#edd_product_files .inside {\n margin-bottom: 0; }\n\n.edd_repeatable_row.ui-sortable-placeholder {\n line-height: 0;\n padding: 0;\n margin: 0;\n box-sizing: border-box;\n border: 1px dashed #ddd;\n visibility: visible !important; }\n\n.edd-add-repeatable-row {\n border-top: 1px solid #ddd;\n background: #fafafa;\n padding: 12px;\n margin: 15px -12px -12px -12px;\n text-align: right; }\n\n.edd_repeatable_row input[type=\"text\"].large-text {\n width: 100%; }\n\n.edd_repeatable_row input[type=\"text\"] {\n height: 28px; }\n\n.edd-add-repeatable-row button {\n text-align: left; }\n\n.edd_variable_prices_wrapper:not(:first-child),\n.edd_repeatable_upload_wrapper:not(:first-child) {\n margin-top: 12px; }\n\n.edd_repeatable_row.ui-sortable-helper .edd-repeatable-row-actions .edd-remove-row {\n display: none; }\n\n.edd-repeatable-row-actions {\n color: #777;\n font-size: 12px; }\n\n.edd-repeatable-row-actions a {\n text-decoration: none;\n font-size: 11px;\n line-height: 11px;\n width: auto;\n cursor: pointer;\n vertical-align: middle; }\n\n.edd-repeatable-row-header,\n.edd-bundle-products-header {\n clear: both;\n background: #f1f1f1;\n border: 1px solid #e5e5e5; }\n\n.edd-repeatable-row-header {\n cursor: move; }\n\n.edd_repeatable_row:hover .edd-repeatable-row-header,\n.edd_repeatable_row:hover .edd-repeatable-row-standard-fields {\n border-color: #ccc; }\n\n.edd-repeatable-row-header:before,\n.edd-repeatable-row-header:after,\n.edd-bundled-product-row:before,\n.edd-bundled-product-row:after {\n content: '';\n display: table; }\n\n.edd-repeatable-row-header:after,\n.edd-bundled-product-row:after {\n clear: both; }\n\n.edd-repeatable-row-title {\n float: left;\n font-weight: 600; }\n\n.edd-bundled-product-item-reorder .edd-product-file-reorder {\n color: #e5e5e5;\n font-family: \"dashicons\";\n content: \"\\f545\";\n font-size: 18px;\n font-weight: 300;\n margin-left: 4px;\n vertical-align: top;\n transition: .2s color; }\n\n.edd-bundled-product-item-reorder .edd-product-file-reorder:hover {\n color: #bbb; }\n\n.edd-repeatable-row-title,\n.edd-repeatable-row-actions {\n padding: 8px;\n box-sizing: border-box; }\n\n.edd-repeatable-row-actions {\n float: right;\n text-align: right;\n padding: 8px; }\n\n.edd-repeatable-row-actions .edd-remove-row,\n.edd-bundled-product-row .edd-remove-row {\n font-size: 12px;\n width: auto;\n cursor: pointer; }\n\n.edd-repeatable-row-standard-fields,\n.edd-bundled-product-row {\n background: #f9f9f9;\n padding: 8px;\n border-width: 0 1px 1px;\n border-style: solid;\n border-color: #e5e5e5;\n display: flex;\n justify-content: space-between;\n gap: 18px; }\n\n/* @todo: remove these when .edd-form-row has been fully implemented */\n.edd-repeatable-row-standard-fields .edd-form-group,\n.edd-bundled-product-row .edd-form-group {\n margin-bottom: 0;\n display: inline-flex;\n flex-direction: column;\n flex-grow: 1;\n justify-content: space-between; }\n\n.edd-repeatable-row-setting-label {\n display: block;\n margin-bottom: 4px; }\n\n.edd-repeatable-row-setting-label .edd-help-tip {\n display: inline-block;\n margin-left: 4px; }\n\n.edd-bundle-products-header {\n font-weight: 600;\n padding: 8px; }\n\n.edd-bundled-product-row .edd-bundled-product-item-reorder {\n min-width: 30px; }\n\n.edd-bundled-product-row .edd-bundled-product-item-reorder .edd-product-file-reorder {\n font-size: 20px;\n font-weight: 300;\n padding: 16px 4px 0;\n cursor: move; }\n\n.edd-bundled-product-row .edd-bundled-product-actions {\n margin-left: 24px;\n align-self: center; }\n\n.edd_repeatable_upload_wrapper .edd_repeatable_upload_field_container {\n position: relative;\n width: 100%; }\n\n.edd_repeatable_upload_wrapper .edd_repeatable_upload_field_container + span:first-child {\n width: 100%; }\n\n.edd_repeatable_upload_field {\n padding-right: 32px; }\n\n.edd_upload_file button {\n background: #f9f9f9;\n border: none;\n border-left: 1px solid #e5e5e5;\n display: block;\n padding: 0;\n position: absolute;\n height: calc(100% - 4px);\n width: 26px;\n overflow: hidden;\n top: 2px;\n right: 2px;\n display: inline-flex;\n justify-content: center;\n align-items: center; }\n\ntextarea#edd-payment-note {\n width: 100%;\n height: 4em;\n margin: 0; }\n\n#edd-order-items .row .edd-purchased-files-list-wrapper .download {\n line-height: 1.4; }\n\n#edd-order-items .edd-purchased-files-list-wrapper .edd-purchased-option {\n color: #666; }\n\ninput[class*=\"edd-price-field\"] {\n max-width: 125px; }\n\n[class*=\"item_\"] [class*=\"edd-payment-details-download-\"][type=\"number\"].small-text,\n#edd-order-download-quantity[type=\"number\"].small-text,\n#edd-order-download-tax[type=\"text\"].small-text {\n height: 25px; }\n\n.item_price .edd-payment-details-download-quantity[type=\"number\"].small-text,\n#edd-order-download-quantity[type=\"number\"].small-text {\n width: 55px; }\n\n.item_tax .edd-payment-details-download-item-tax[type=\"number\"].small-text,\n#edd-order-download-tax[type=\"text\"].small-text {\n width: 80%;\n max-width: 125px; }\n\n.edd_repeatable_upload_wrapper .pricing select,\n.edd_repeatable_product_wrapper .edd-select {\n min-width: 100%; }\n\n#edd_product_notes_field {\n display: block;\n margin: 12px 0 0;\n height: 4em;\n width: 100%; }\n\n/* still used by extensions - Software Licensing upgrade paths, Custom Prices */\n.edd_remove_repeatable {\n border: none;\n cursor: pointer;\n display: inline-block;\n padding: 0;\n overflow: hidden;\n margin: 8px 0 0 0;\n text-indent: -9999px;\n width: 10px;\n height: 10px; }\n\n.edd_remove_repeatable:active,\n.edd_remove_repeatable:hover,\n.edd_remove_repeatable:focus {\n background-position: -10px 0 !important; }\n\n/* Payment Details\n-------------------------------------------------------------- */\n.edd-metabox-title-action {\n margin: 0;\n float: right;\n padding: 4px 8px;\n position: relative;\n top: -1px;\n text-decoration: none;\n border: none;\n border: 1px solid #ccc;\n border-radius: 2px;\n background: #f7f7f7;\n text-shadow: none;\n font-weight: 600;\n font-size: 10px;\n line-height: normal;\n color: #0073aa;\n cursor: pointer;\n outline: 0; }\n\n.edd-metabox-title-action:hover {\n border-color: #008EC2;\n background: #00a0d2;\n color: #fff; }\n\n.edd-edit-purchase-element .tablenav {\n padding: 2px 10px 8px 10px; }\n\n.edd-edit-purchase-element .edd-order-children-wrapper {\n margin: 0 -1px; }\n\n.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 table {\n border-top: none;\n border-bottom: none; }\n\n.edd-edit-purchase-element .edd-order-children-wrapper.child-count-0 .tablenav {\n display: none; }\n\n.edd-edit-purchase-element[class*=\"columns-\"] ul li {\n padding-right: 1%; }\n\n#edd-edit-order-form .columns-4 .column:nth-child(2n+1),\n#edd-edit-order-form .columns-5 .column:nth-child(3n+1),\n#edd-edit-order-form .column:nth-child(2n+1) {\n margin-right: 0; }\n\n#edd-edit-order-form input.large-text {\n width: 90%; }\n\n.edd-edit-purchase-element ul li.item_price {\n width: 15%; }\n\n.edd-edit-purchase-element ul li.item_price.item_quantity {\n width: 25%; }\n\n.edd-edit-purchase-element ul li.item_tax {\n width: 15%; }\n\n.edd-edit-purchase-element ul li.price {\n width: 20%; }\n\n.edd-admin-box-inside {\n border-bottom: 1px solid #f1f1f1;\n clear: both;\n padding: 12px;\n margin: 0;\n word-wrap: break-word; }\n\n.edd-admin-box-inside--row {\n display: flex;\n flex-wrap: wrap;\n word-break: break-all;\n justify-content: space-between;\n align-items: center; }\n\n.edd-admin-box-inside > p {\n margin: 8px 3px; }\n\n.edd-admin-box-inside .strong {\n font-weight: 600; }\n\n.edd-admin-box div:not(.edd-admin-box-inside--row) .label {\n display: block;\n margin-bottom: 4px;\n margin-right: 0; }\n\n.edd-admin-box .label--has-tip {\n display: flex;\n align-items: center; }\n\n.edd-admin-box .label--has-tip .edd-help-tip {\n margin-top: 0;\n font-size: 20px; }\n\n.edd-admin-box div:not(.edd-admin-box-inside--row) .label--has-checkbox {\n margin-bottom: 0; }\n\n.edd-payment-fees .fee-label {\n color: #666;\n font-weight: normal; }\n\n.edd-admin-box .right {\n float: right; }\n\n#edd-order-refunds-list {\n padding-left: 25px; }\n\n#poststuff .edd-order-data .inside {\n margin: 0;\n padding: 0; }\n\n.edd-order-data .edd-select-chosen {\n width: 130px !important; }\n\n.edd-order-data input.edd_datepicker {\n width: 180px; }\n\n.edd-order-data input[type=\"number\"].edd-payment-time-hour,\n.edd-order-data input[type=\"number\"].edd-payment-time-min {\n width: 50px; }\n\n.edd-order-data .edd-tax-rate {\n color: #9c9c9c;\n font-style: italic;\n padding: 5px; }\n\n#edd_general_logs p {\n margin: 0;\n padding: 0; }\n\n.edd-admin-box-inside span.label {\n margin-right: 10px; }\n\n#edd-order-resend-receipt .inside {\n margin-top: 11px; }\n\n#edd-order-resend-receipt .edd-order-resend-receipt-addresses {\n margin-top: 10px; }\n\n.edd-order-resend-receipt-header {\n font-size: 14px;\n line-height: 1.4; }\n\n.edd-order-resend-receipt-addresses label {\n display: block;\n line-height: 1.75em; }\n\n.edd-order-resend-receipt-addresses label:last-child {\n margin-bottom: 10px; }\n\n.edd-admin-box-inside:last-child {\n border-bottom: 0; }\n\n#edd-edit-order-form .data-payment-key {\n word-break: break-all; }\n\n.edd-order-update-box #major-publishing-actions .button-secondary {\n margin-right: 10px; }\n\n.edd-order-update-box .button-primary {\n margin-right: 0; }\n\n.edd-edit-purchase-element .edd-select-chosen {\n width: 196px; }\n\n.edd-edit-purchase-element ul {\n clear: both;\n display: block; }\n\n#edd-customer-details .actions {\n float: right; }\n\n.order-data-address h3 {\n margin: 0 0 10px 0; }\n\n.order-data-address #edd-order-address-state-wrap,\n.order-data-address #edd-order-address-country-wrap {\n display: inline-block;\n width: 50%;\n max-width: 300px; }\n\n.edd-order-data input.small-text {\n margin: 0; }\n\n.edd-order-data input.med-text {\n margin: 0;\n width: 100px; }\n\n.edd-edit-purchase-element ul li {\n display: block;\n line-height: 1.4;\n position: relative;\n margin: 0;\n vertical-align: middle;\n font-size: 13px; }\n\n.edd-edit-purchase-element .row {\n padding: 12px; }\n\n.edd-edit-purchase-element .row:not(:last-child) {\n border-bottom: 1px solid #eee; }\n\n.edd-edit-purchase-element .row:nth-child(odd):not(.header) {\n background-color: #f9f9f9; }\n\n.edd-edit-purchase-element .row.header {\n padding: 6px 12px;\n font-weight: 600;\n vertical-align: top; }\n\n.edd-edit-purchase-element ul {\n margin: 0 0 15px; }\n\n.edd-edit-purchase-element ul:last-of-type {\n margin-bottom: 0; }\n\n#edd-order-data .data span {\n color: #666;\n font-weight: 600; }\n\n.edd-edit-purchase-element .inside {\n padding: 12px; }\n\n.edd-edit-purchase-element .edd-purchased-download-title {\n font-size: 14px;\n font-weight: 500; }\n\n.edd-edit-purchase-element .edd-purchased-download-title .deleted {\n color: #777; }\n\n.edd-edit-purchase-element .edd-purchased-download-actions {\n color: #777;\n line-height: 1.4; }\n\n.edd-edit-purchase-element .edd-purchased-download-actions .edd-purchased-download-actions-label {\n font-weight: 500; }\n\n.edd-edit-purchase-element .edd-purchased-download-actions a {\n color: #777;\n font-size: 12px; }\n\n.edd-edit-purchase-element .edd-purchased-download-actions a:hover {\n color: #444; }\n\n.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download {\n color: #a00; }\n\n.edd-edit-purchase-element .edd-purchased-download-actions .edd-order-remove-download:hover {\n color: #f00; }\n\n.edd_repeatable_upload_wrapper .pricing select,\n.edd_repeatable_product_wrapper .edd-select,\n#edd_products .edd-select {\n min-width: 100%;\n max-width: 200px; }\n\n.edd_repeatable_product_wrapper td {\n overflow: visible; }\n\n.edd-add-download-to-purchase,\n.edd-add-adjustment-to-purchase {\n padding: 15px;\n border-top: 1px solid #e5e5e5;\n background-color: #f5f5f5; }\n\n.edd-add-download-to-purchase .chosen-container,\n.edd-add-adjustment-to-purchase .chosen-container {\n width: 90% !important;\n max-width: 220px !important; }\n\n.edd-add-download-to-purchase .spinner,\n.edd-add-adjustment-to-purchase .spinner {\n margin: 0;\n float: none; }\n\n.edd-add-download-to-purchase .edd-add-order-quantity {\n width: 40px;\n height: 29px;\n vertical-align: middle; }\n\n.edd-add-download-to-purchase .edd-add-order-item-button,\n.edd-add-adjustment-to-purchase .edd-add-adjustment-button,\n.edd-add-adjustment-to-purchase input[type=\"text\"] {\n height: 29px; }\n\n@media screen and (max-width: 1284px) {\n .edd-edit-purchase-element .edd-purchased-download-title {\n font-size: 16px; }\n .edd-edit-purchase-element ul li.item_price {\n width: 22%; }\n .edd-edit-purchase-element ul li.item_price.item_quantity {\n width: 35%; }\n .edd-edit-purchase-element ul li.item_tax {\n width: 25%; }\n .edd-edit-purchase-element ul li.price {\n width: 20%; }\n .edd-edit-purchase-element .edd-purchased-download-actions {\n padding-top: 10px; } }\n\n@media screen and (max-width: 1024px) {\n .edd-edit-purchase-element ul li.item_price.item_quantity {\n width: 40%; }\n .edd-edit-purchase-element ul li.price {\n width: 24%; }\n .edd-edit-purchase-element .edd-purchased-download-actions {\n padding-top: 15px; }\n .edd-edit-purchase-element .edd-purchased-download-actions,\n .edd-edit-purchase-element .edd-purchased-download-actions a {\n font-size: 14px; } }\n\n@media screen and (max-width: 782px) {\n .edd-edit-purchase-element ul li.item_price,\n .edd-edit-purchase-element ul li.item_price.item_quantity {\n padding-bottom: 10px; }\n .edd-edit-purchase-element ul li.item_price.item_quantity {\n width: 35%; }\n .edd-edit-purchase-element ul li.item_tax,\n .edd-edit-purchase-element ul li.price {\n width: 20%;\n padding-bottom: 10px; }\n .edd-price-currency,\n .edd-payment-details-download-amount {\n font-size: 16px; }\n .order-data-column input[type=\"email\"] {\n padding: 6px 10px; }\n .edd-refund-submit-line-total td:last-of-type {\n flex: 0 0 120px; }\n #edd-item-tables-wrapper .addresses tbody tr {\n display: grid; }\n #edd-item-tables-wrapper .addresses tbody td:not(.no-items) {\n padding-left: 35%; } }\n\n@media screen and (max-width: 600px) {\n .edd-edit-purchase-element ul li.item_price,\n .edd-edit-purchase-element ul li.item_price.item_quantity,\n .edd-edit-purchase-element ul li.item_tax {\n width: 100%;\n padding-bottom: 20px; }\n .edd-edit-purchase-element ul li.price,\n .edd-edit-purchase-element .edd-add-download-to-purchase ul li.item_tax {\n width: 100%;\n padding-bottom: 0; }\n .edd-edit-purchase-element .edd-add-download-to-purchase-actions {\n padding-top: 15px; } }\n\n/** Stats */\n#edd_product_stats .label {\n display: inline-block; }\n\n#edd_product_stats .product-sales-stats:before,\n#edd_product_stats .product-earnings-stats:before {\n color: #82878c;\n font: normal 20px/1 'dashicons';\n display: inline-block;\n padding: 0 2px 0 0;\n position: relative;\n top: 0;\n left: -1px;\n speak: none;\n text-decoration: none !important;\n vertical-align: top;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale; }\n\n#edd_product_stats .product-sales-stats:before {\n content: '\\f174'; }\n\n#edd_product_stats .product-earnings-stats:before {\n content: '\\f239'; }\n\n/* Dashboard Page Styles\n-------------------------------------------------------------- */\nbody.dashboard_page_edd-upgrades.js .postbox .hndle {\n cursor: default; }\n\n/* Dashboard Widget Styles\n-------------------------------------------------------------- */\n.edd_dashboard_widget table thead td {\n border-bottom: 1px solid #ececec;\n color: #777; }\n\n.edd_dashboard_widget .table_left {\n float: left;\n width: 45%; }\n\n.edd_dashboard_widget .table_right {\n float: right;\n width: 45%; }\n\n.edd_dashboard_widget .inside {\n font-size: 12px; }\n\n.edd_dashboard_widget td {\n padding: 3px 0; }\n\n.edd_dashboard_widget .b,\n.edd_dashboard_widget .t {\n line-height: 1.5;\n vertical-align: middle; }\n\n.edd_dashboard_widget .b {\n padding-right: 6px;\n width: auto; }\n\n.edd_dashboard_widget .t {\n font-size: 12px;\n padding-right: 12px;\n color: #777;\n width: 100%; }\n\n.edd_dashboard_widget .label_heading {\n border-top: 1px solid #ececec;\n color: #8f8f8f;\n font-family: Helvetica, Arial, sans-serif;\n font-size: 12px;\n font-weight: normal;\n display: block;\n padding-top: 10px;\n margin: 0 0 8px 12px; }\n\n.edd_dashboard_widget .edd_dashboard_widget_subheading {\n border-top: 1px solid #ececec;\n color: #8f8f8f;\n font-size: 14px;\n padding-top: 10px;\n margin: 1em 0 0 0; }\n\n.edd_dashboard_widget .edd_dashboard_widget_subheading + .table {\n margin: 8px 0 0 0; }\n\n.edd_dashboard_widget .edd_price_label {\n background: #00769c;\n border-radius: 3px;\n color: white;\n font-size: 10px;\n padding: 2px 4px;\n margin-right: 2px; }\n\n.edd_dashboard_widget table {\n width: 100%;\n margin-left: 0;\n margin-bottom: 1em; }\n\ntd.edd_order_label {\n width: 80%; }\n\ntd.edd_order_price {\n text-align: right; }\n\n@media handheld, only screen and (max-width: 1000px) {\n .edd_dashboard_widget .edd-recent-email {\n display: none; } }\n\n/* Reports Styles\n-------------------------------------------------------------- */\n/* Force a scrollbar when on the reports page (https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6718) */\nbody.download_page_edd-reports {\n overflow-y: scroll; }\n\n.edd-chip {\n font-size: 10px;\n font-weight: bold;\n text-transform: uppercase;\n line-height: 1;\n padding: 3px;\n border-radius: 3px;\n color: #fff;\n background-color: #444; }\n\n.edd-vertical-sections .edd-legacy-label {\n display: inline-block;\n position: absolute;\n top: 11px;\n right: 6px; }\n\n/* Keeping this rule for Software Licensing Reports */\n.edd-reports-wrapper .postbox h2,\n.edd-reports-wrapper .postbox h3 {\n font-size: 1.3em; }\n\n#edd-dashboard-widgets-wrap .metabox-holder {\n padding-top: 0; }\n\n.edd-reports-wrapper .postbox .edd-select {\n max-width: 200px;\n vertical-align: baseline;\n margin-right: 4px;\n margin-bottom: 16px; }\n\n.download_page_edd-reports #edd-item-wrapper {\n margin: 0; }\n\n#edd-dashboard-widgets-wrap .postbox h2,\n#edd-dashboard-widgets-wrap .postbox h3 {\n cursor: default; }\n\n.edd-date-range-options .edd_datepicker {\n width: 105px; }\n\n.edd-report-wrap {\n clear: both; }\n\n.edd-report-wrap h3 {\n clear: both;\n margin: 0 0 20px; }\n\n.edd-reports-chart,\n.edd-reports-table {\n margin-bottom: 20px; }\n\n.edd-admin--has-grid {\n display: grid;\n display: -ms-grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n grid-gap: 20px; }\n\n.edd-admin--has-grid .postbox {\n margin-bottom: 0; }\n\n.edd-admin--has-grid .edd-from-to-wrapper {\n display: flex;\n margin-bottom: 16px;\n width: 100%; }\n\n.edd-admin--has-grid .edd-from-to-wrapper input {\n width: 100%; }\n\n.edd-admin--has-grid .edd-from-to-wrapper span {\n flex-grow: 1; }\n\n.edd-admin--has-grid form {\n display: flex;\n flex-direction: column;\n flex-wrap: wrap;\n position: relative; }\n\nfieldset.edd-to-and-from-container {\n display: grid;\n display: -ms-grid;\n grid-template-columns: 1fr 1fr;\n grid-gap: 8px; }\n\nspan.edd-to-and-from--separator {\n line-height: normal;\n align-self: center;\n margin-bottom: 16px; }\n\n.edd-admin--has-grid .postbox .edd-select {\n max-width: 100%;\n margin-right: 0; }\n\n.edd-admin--has-grid .button.updating-message:before,\n.edd-admin--has-grid .button.updated-message:before {\n vertical-align: text-bottom;\n margin: 0 5px 0 0; }\n\n.edd-import-export-form .edd-progress {\n background: #ddd;\n border-radius: 15px;\n height: 15px;\n flex-basis: 100%; }\n\n.edd-import-export-form .edd-progress div {\n background: #ccc;\n border-radius: 15px;\n height: 100%;\n width: 0; }\n\n.edd-import-export-form .notice-wrap {\n background-color: #f4f4f4;\n border-style: solid;\n border-width: 1px 0;\n border-color: #eae9e9;\n padding: 12px;\n overflow: auto;\n margin: 20px -12px -23px;\n position: relative;\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center; }\n\n.notice-wrap div.notice {\n margin: 0; }\n\n.admin-color-fresh .edd-import-export-form .edd-progress div {\n background: #0073aa; }\n\n.admin-color-light .edd-import-export-form .edd-progress div {\n background: #888; }\n\n.admin-color-blue .edd-import-export-form .edd-progress div {\n background: #096484; }\n\n.admin-color-coffee .edd-import-export-form .edd-progress div {\n background: #c7a589; }\n\n.admin-color-ectoplasm .edd-import-export-form .edd-progress div {\n background: #a3b745; }\n\n.admin-color-midnight .edd-import-export-form .edd-progress div {\n background: #e14d43; }\n\n.admin-color-sunrise .edd-import-export-form .edd-progress div {\n background: #dd823b; }\n\n.graph-option-section {\n float: left; }\n\n.edd-report-filters-title span {\n display: block;\n padding: 20px; }\n\n#edd-graphs-filter form {\n padding: 20px; }\n\n#edd-graphs-filter label {\n vertical-align: inherit; }\n\n#edd-graphs-filter .graph-option-section {\n display: inline-block;\n line-height: 2em;\n margin: 0 5px 0 0;\n padding: 0; }\n\n.download_page_edd-reports .section-content #post-body-content {\n float: none; }\n\n.download_page_edd-reports .section-content select[name=\"range\"] {\n display: none; }\n\n.edd-mix-totals {\n background-color: #fff;\n border: 1px solid #e5e5e5;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);\n padding: 10px; }\n\n.edd-mix-chart {\n display: inline-block;\n width: 49%;\n vertical-align: top; }\n\n.edd-graph-notes {\n color: #9c9c9c; }\n\n.edd-graph-notes span {\n display: block; }\n\n.edd-pie-graph .legend {\n display: none; }\n\n.edd-pie-legend {\n overflow: auto;\n margin-top: 10px; }\n\n.edd-legend-item-wrapper {\n color: #333;\n display: inline-block;\n font-size: 8pt;\n padding: 2px 5px 0px 5px;\n width: 48%;\n height: 20px; }\n\n.edd-legend-color {\n border: 1px solid #cfcfcf;\n display: inline-block;\n margin-right: 5px;\n width: 20px;\n height: 15px; }\n\n.edd-pie-legend-item {\n display: inline-block;\n vertical-align: top;\n width: 80%; }\n\n#edd-reports-tiles-wrap .metabox-holder {\n padding: 0; }\n\n#edd-reports-tiles-wrap #dashboard-widgets {\n overflow: auto; }\n\n#edd-reports-tiles-wrap #dashboard-widgets .postbox-container {\n width: 33.3%; }\n\n/** Hide legacy report empty navigations */\n.download_page_edd-reports .section-content .tablenav.top {\n display: none; }\n\n#edd_tax_rates {\n margin: 1em 0 0; }\n\n[id*=\"edd-sendwp-\"].button,\n[id*=\"edd-jilt-\"].button {\n font-size: 16px;\n height: auto;\n padding: 8px 14px;\n margin: 6px 0 0; }\n\n[id*=\"edd-sendwp-\"].button .dashicons,\n[id*=\"edd-jilt-\"].button .dashicons {\n line-height: 29px;\n margin-right: 8px; }\n\n[id*=\"edd-sendwp-\"].button .edd-loading,\n[id*=\"edd-sendwp-\"].button .edd-loading:after,\n[id*=\"edd-jilt-\"].button .edd-loading,\n[id*=\"edd-jilt-\"].button .edd-loading:after {\n border-radius: 50%;\n display: inline-block;\n width: 14px;\n height: 14px; }\n\n[id*=\"edd-sendwp-\"].button .edd-loading,\n[id*=\"edd-jilt-\"].button .edd-loading {\n position: relative;\n top: 3px;\n margin-left: 4px;\n box-shadow: 0 0 2px rgba(0, 0, 0, 0.2); }\n\n[id*=\"edd-sendwp-\"].button .edd-loading,\n[id*=\"edd-jilt-\"].button .edd-loading {\n -webkit-animation: edd-spinning 1.1s infinite linear;\n animation: edd-spinning 1.1s infinite linear;\n border-top: 2px solid rgba(255, 255, 255, 0.5);\n border-right: 2px solid rgba(255, 255, 255, 0.5);\n border-bottom: 2px solid rgba(255, 255, 255, 0.5);\n border-left: 2px solid #fff;\n font-size: 14px;\n filter: alpha(opacity=0);\n -ms-transform: translateZ(0);\n transform: translateZ(0); }\n\n#edd-sendwp-disconnect.button .edd-loading.dark,\n#edd-jilt-disconnect.button .edd-loading.dark {\n border-top-color: rgba(0, 0, 0, 0.2);\n border-right-color: rgba(0, 0, 0, 0.2);\n border-bottom-color: rgba(0, 0, 0, 0.2);\n border-left-color: #666;\n box-shadow: none; }\n\n.jilt-notice {\n position: relative; }\n\n@-webkit-keyframes edd-spinning {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg); } }\n\n@keyframes edd-spinning {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg); } }\n\n#edd-reports-tiles-wrap #dashboard-widgets .sortable-placeholder {\n padding: 0;\n margin: 0 0 20px 0;\n line-height: 0;\n box-sizing: border-box;\n height: 110px; }\n\n#edd-reports-tiles-wrap #dashboard-widgets #primary-sortables {\n margin-left: 0; }\n\n#edd-reports-tiles-wrap #dashboard-widgets #tertiary-sortables {\n margin-right: 0; }\n\n#edd-reports-tiles-wrap {\n display: -ms-grid;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n grid-gap: 20px; }\n\n.edd-reports-tile {\n text-align: center;\n padding: 30px 10px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n border: 1px solid #e5e5e5;\n background: #fafafa;\n position: relative;\n box-sizing: border-box; }\n\n.edd-reports-tile span.dashicons {\n display: inline-block;\n font-size: 30px;\n line-height: 20px;\n height: 20px;\n width: 20px;\n position: relative;\n top: 4px;\n left: -5px;\n margin-left: -5px;\n color: #999; }\n\n.edd-reports-tile .tile-label {\n text-align: center;\n text-transform: uppercase;\n font-size: 11px;\n font-weight: normal;\n color: #888;\n order: 2;\n margin-top: 3px; }\n\n.edd-reports-tile .tile-value {\n order: 1;\n color: #333;\n font-size: 2em;\n line-height: 1;\n transition: all .2s ease-in-out; }\n\n.edd-reports-tile:hover {\n border: 1px solid #aaa; }\n\n.edd-reports-tile:hover .tile-value:not(.tile-no-data) {\n transform: scale(1.05); }\n\n.edd-reports-tile .tile-amount {\n color: #2794da; }\n\n.edd-reports-tile .tile-number {\n color: #9966ff; }\n\n.edd-reports-tile .tile-amount,\n.edd-reports-tile .tile-number {\n color: #fff; }\n\n.edd-reports-tile .tile-value.tile-no-data {\n color: #ddd; }\n\n.edd-reports-tile .tile-value.tile-url {\n font-size: 1.5em; }\n\n.edd-reports-tile .tile-compare {\n position: absolute;\n right: 0;\n bottom: 0;\n color: #aaa;\n font-size: 11px;\n line-height: 1em;\n background-color: #fff;\n border-left: 1px solid #e5e5e5;\n border-top: 1px solid #e5e5e5;\n border-bottom: 1px solid #fff;\n border-right: 1px solid #fff;\n border-top-left-radius: 8px;\n padding: 4px 0 0 9px;\n margin: 0 -1px -1px 0; }\n\n.edd-reports-tile:hover .tile-compare {\n border-left: 1px solid #bbb;\n border-top: 1px solid #bbb;\n color: #777; }\n\n@media screen and (min-width: 600px) {\n #edd-reports-charts-wrap {\n display: -ms-grid;\n display: grid;\n grid-template-columns: repeat(2, minmax(200px, 50%));\n grid-gap: 20px; }\n .edd-reports-chart {\n margin-bottom: 0; }\n .edd-reports-chart-line {\n grid-column: 1 / span 2; } }\n\n#edd-chartjs-tooltip {\n position: absolute;\n background-color: #fff;\n -webkit-border-radius: 7px;\n -moz-border-radius: 7px;\n border-radius: 7px;\n -webkit-transition: all .1s ease;\n transition: all .1s ease;\n pointer-events: none;\n -webkit-transform: translate(-50%, 0);\n transform: translate(-50%, 0);\n font-size: 12px;\n box-shadow: 0 0 0 1px rgba(89, 94, 100, 0.1), 0 15px 35px 0 rgba(89, 94, 100, 0.1), 0 5px 15px 0 rgba(0, 0, 0, 0.12);\n min-width: 120px;\n opacity: 0; }\n\n.edd-chartjs-tooltip-key {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin-right: 5px; }\n\n/* Upgrades page styles\n-------------------------------------------------------------- */\n/* Settings page styles\n-------------------------------------------------------------- */\n.wrap-licenses .form-table,\n.wrap-licenses thead,\n.wrap-licenses tbody,\n.wrap-licenses tfoot,\n.wrap-licenses tr,\n.wrap-licenses td,\n.wrap-licenses th,\n.wrap-licenses caption {\n display: block; }\n\n.wrap-licenses .form-table tr {\n float: left;\n margin: 0 15px 15px 0;\n background: #fff;\n border: 1px solid #ccc;\n width: 30.5%;\n max-width: 350px;\n padding: 14px;\n min-height: 220px;\n position: relative;\n box-sizing: border-box; }\n\n.wrap-licenses .form-table th {\n background: #f9f9f9;\n padding: 14px;\n border-bottom: 1px solid #ccc;\n margin: -14px -14px 20px;\n width: 100%; }\n\n.wrap-licenses .form-table td {\n padding: 0; }\n\n.wrap-licenses td input.regular-text {\n margin: 0 0 8px;\n width: 100%; }\n\n.wrap-licenses .edd-license-data[class*=\"edd-license-\"] {\n position: absolute;\n background: #fafafa;\n padding: 14px;\n border-top: 1px solid #eee;\n margin: 20px -14px -14px;\n min-height: 67px;\n width: 100%;\n bottom: 14px;\n box-sizing: border-box; }\n\n.wrap-licenses .edd-license-data[class*=\"edd-license-\"] a {\n color: #444; }\n\n.wrap-licenses .edd-license-data[class*=\"edd-license-\"] a:hover {\n text-decoration: none; }\n\n.wrap-licenses .edd-license-data.license-expires-soon-notice {\n background-color: #00a0d2;\n color: #fff;\n border-color: #00a0d2; }\n\n.wrap-licenses .edd-license-data.edd-license-expired {\n background-color: #e24e4e;\n color: #fff;\n border-color: #e24e4e; }\n\n.wrap-licenses .edd-license-data.edd-license-error,\n.wrap-licenses .edd-license-data.edd-license-missing,\n.wrap-licenses .edd-license-data.edd-license-invalid,\n.wrap-licenses .edd-license-data.edd-license-site_inactive,\n.wrap-licenses .edd-license-data.edd-license-item_name_mismatch {\n background-color: #ffebcd;\n border-color: #ffebcd; }\n\n.wrap-licenses .edd-license-data p {\n font-size: 13px;\n margin-top: 0; }\n\n.wrap-licenses .edd-license-data.license-expires-soon-notice a,\n.wrap-licenses .edd-license-data.edd-license-expired a {\n color: #fff; }\n\n.wrap-licenses .edd-license-data.license-expires-soon-notice a:hover,\n.wrap-licenses .edd-license-data.edd-license-expired a:hover {\n text-decoration: none; }\n\n.wrap-licenses p.submit {\n clear: both; }\n\n/* Global Graph Styles\n-------------------------------------------------------------- */\n.edd-graph .y1Axis {\n color: #edc240 !important; }\n\n.edd-graph .y2Axis {\n color: #afd8f8 !important; }\n\n/* API Table Styles\n-------------------------------------------------------------- */\n.wp-list-table.apikeys input.code {\n width: 100%;\n font-size: 10px;\n cursor: text;\n background: #fff;\n border: 1px solid #ddd;\n box-shadow: none;\n color: #555; }\n\n/* Toggle Styles\n-------------------------------------------------------------- */\n.edd-toggle {\n position: relative;\n display: inline-block;\n overflow: visible; }\n\n.edd-toggle input[type=\"checkbox\"] {\n display: inline-block;\n vertical-align: middle;\n position: relative;\n margin: 0;\n padding: 0;\n width: 42px;\n height: 24px;\n background-color: #ccc;\n -webkit-transition: background 0.2s ease;\n transition: background 0.2s ease;\n border-radius: 34px;\n box-shadow: none;\n border: none; }\n\n.edd-toggle .label {\n display: inline-block;\n vertical-align: middle;\n white-space: nowrap; }\n\n.edd-toggle input[type=\"checkbox\"]:before {\n position: absolute;\n content: \"\";\n height: 18px;\n width: 18px;\n left: 3px;\n bottom: 3px;\n background-color: white;\n -webkit-transition: 0.1s transform ease;\n transition: 0.1s transform ease;\n border-radius: 50%; }\n\n.edd-toggle input[type=\"checkbox\"]:checked {\n background-color: #007cba; }\n\n.edd-toggle input[type=\"checkbox\"]:active,\n.edd-toggle input[type=\"checkbox\"]:focus {\n outline: 0;\n box-shadow: 0 0 0 1px #fff, 0 0 0 3px #7e8993; }\n\n.edd-toggle input[type=\"checkbox\"]:checked:active,\n.edd-toggle input[type=\"checkbox\"]:checked:focus {\n box-shadow: 0 0 0 1px #fff, 0 0 0 3px #007cba; }\n\n.edd-toggle input[type=\"checkbox\"]:checked:before {\n -webkit-transform: translateX(22px);\n -ms-transform: translateX(22px);\n transform: translateX(22px); }\n\n.edd-toggle input + .label,\n.edd-toggle .label + input {\n margin-left: 5px; }\n\n/* List Table Styles\n-------------------------------------------------------------- */\n.download_page_edd-tools .tablenav .actions {\n overflow: visible; }\n\n.edd_user_search_wrap {\n position: relative;\n overflow: visible; }\n\n.edd_user_search_wrap .spinner {\n position: absolute;\n margin: 0;\n padding: 0;\n right: 4px;\n top: -2px; }\n\n.edd_user_search_wrap.loading .spinner {\n visibility: visible; }\n\n.edd_user_search_results {\n position: absolute;\n left: 0;\n top: 20px; }\n\n.edd_user_search_results a.edd-ajax-user-cancel {\n position: absolute;\n right: 6px;\n top: 2px; }\n\n.edd_user_search_results ul {\n background: #fafafa;\n border: 1px solid #dfdfdf;\n overflow-y: scroll;\n padding: 0;\n margin: 0;\n height: 150px;\n width: 185px;\n box-shadow: 0 3px 5px rgba(0, 0, 0, 0.1); }\n\n.edd_user_search_results li {\n margin: 0; }\n\n.edd_user_search_results li a {\n display: block;\n text-decoration: none;\n padding: 6px 10px; }\n\n.edd_user_search_results li a:hover {\n background: #f5f5f5; }\n\n.edd_user_search_results li.no-users {\n text-align: center;\n vertical-align: middle;\n display: block;\n line-height: 150px;\n color: #bbb;\n text-transform: uppercase;\n font-size: 11px; }\n\n@media screen and (max-width: 1100px) {\n .edd-mix-chart {\n display: block;\n width: 100%; }\n .wrap-licenses .form-table tr {\n width: 46%;\n max-width: none;\n min-height: 230px; } }\n\n@media screen and (max-width: 782px) {\n .license-lifetime-notice,\n .license-expiration-date-notice,\n .license-null {\n padding-left: 0; }\n [class^=\"license-\"] input[type=\"text\"] {\n margin-bottom: 3px; } }\n\n@media screen and (max-width: 600px) {\n .wrap-licenses .form-table tr {\n width: 100%;\n min-height: 230px; }\n #edd-edit-order-form input.large-text {\n width: 100%; } }\n\n/* Customer Styles\n-------------------------------------------------------------- */\n#edd-item-wrapper {\n background: #fff;\n border: 1px solid #c3c4c7;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);\n position: relative;\n margin-top: 15px;\n display: flex; }\n\n#edd-item-wrapper.full-width {\n max-width: 100%; }\n\n#edd-item-wrapper:after {\n content: \"\";\n display: block;\n clear: both;\n visibility: hidden;\n font-size: 0;\n height: 0; }\n\n.edd-sections-wrap {\n clear: both;\n width: 100%; }\n\n.edd-sections-wrap .section-wrap {\n background-color: #fff;\n display: inline-block;\n z-index: 2; }\n\n.js .edd-sections-wrap .edd-vertical-sections:not(.meta-box) .section-wrap > div {\n min-height: 500px;\n height: 100%; }\n\n.edd-sections-wrap .section-wrap .customer-section:not(:last-child) {\n border-bottom: 1px solid #eee; }\n\n.edd-sections-wrap .section-wrap .customer-section table {\n margin-bottom: 20px; }\n\n.edd-sections-wrap .section-wrap {\n border-left: 1px solid #e5e5e5; }\n\n.edd-sections-wrap .section-wrap .section-content > * {\n padding: 20px; }\n\n.edd-sections-wrap .section-wrap .avatar-wrap {\n float: left;\n padding-right: 10px;\n text-align: center; }\n\n.edd-sections-wrap .section-wrap img.avatar {\n border-radius: 5px; }\n\n.edd-sections-wrap .section-wrap .customer-id {\n position: absolute;\n right: 0;\n top: 0;\n padding: 10px;\n background-color: #fafafa;\n border: 1px solid #eee;\n border-bottom-left-radius: 20%;\n border-top: none;\n border-right: none;\n font-family: monospace;\n font-size: 18px;\n font-weight: 600; }\n\n.edd-item-info.customer-info input[type=\"text\"],\n.edd-item-info.customer-info input[type=\"password\"],\n.edd-item-info.customer-info select {\n width: 200px;\n height: auto;\n box-shadow: none;\n transition: none;\n border: 1px solid #ddd;\n margin: -5px 0 4px -2px;\n font-size: 13px;\n padding: 2px 4px; }\n\n.edd-sections-wrap .section-wrap .customer-main-wrapper {\n float: left; }\n\n.edd-sections-wrap .section-wrap .customer-main-wrapper input[name=\"customerinfo[name]\"] {\n font-size: 24px; }\n\n.edd-sections-wrap .section-wrap .customer-address-wrapper {\n float: right;\n margin-top: -3px;\n margin-right: 50px;\n width: 202px; }\n\n.edd-sections-wrap .section-wrap .info-wrapper {\n min-height: 125px;\n overflow: visible; }\n\n.edd-sections-wrap .section-wrap .customer-address span[data-key=\"address\"],\n.edd-sections-wrap .section-wrap .customer-address span[data-key=\"address2\"],\n.edd-sections-wrap .section-wrap .customer-address span[data-key=\"country\"] {\n display: block; }\n\n.edd-sections-wrap .section-wrap a.delete {\n color: #ff0000;\n margin-right: 5px;\n text-decoration: none; }\n\n.customer-info {\n min-height: 185px; }\n\n.customer-info .customer-name {\n font-size: 24px;\n font-weight: 600; }\n\n.customer-info .customer-name.editable {\n margin-bottom: 6px; }\n\n.customer-edit-link a {\n font-weight: normal;\n text-decoration: none; }\n\n.disconnect-user a {\n color: #aaa;\n font-size: 20px; }\n\n#customer-edit-actions {\n padding: 3px;\n line-height: 28px;\n text-align: center; }\n\n#customer-edit-actions .button-secondary {\n margin-right: 5px; }\n\n#customer-edit-actions .cancel {\n padding: 5px; }\n\n.edd-sections-wrap .section-wrap .row-title {\n width: 30%; }\n\n.edd-sections-wrap .section-wrap .editable {\n display: block;\n padding: 3px; }\n\n.edd-sections-wrap .section-wrap div.edit-item {\n margin-left: -4px;\n margin-top: -20px; }\n\n.edd-sections-wrap .section-wrap .customer-address.edit-item {\n margin-top: 3px; }\n\n.edd-sections-wrap .section-wrap span.edit-item {\n display: none; }\n\n.edd-sections-wrap .section-wrap .edit-item input {\n font-size: 13px; }\n\n.edd-sections-wrap .section-wrap .customer-name.edit-item input {\n margin-top: -5px; }\n\n.edd-sections-wrap .section-wrap .edd_user_search_results {\n left: -2px;\n top: 18px; }\n\n.edd-sections-wrap .section-wrap .edd_user_search_results ul {\n width: 198px; }\n\n#edd-item-stats-wrapper {\n margin: 0 auto;\n text-align: center; }\n\n#edd-item-stats-wrapper ul {\n display: flex;\n margin: 0; }\n\n#edd-item-stats-wrapper li {\n font-size: 14px;\n margin-bottom: 0;\n width: 50%; }\n\n#edd-item-stats-wrapper a {\n text-decoration: none; }\n\n#edd-item-stats-wrapper .dashicons {\n color: #888;\n margin-top: -2px; }\n\n#edd-item-tables-wrapper table {\n width: 100%; }\n\n#edd-item-tables-wrapper .no-items {\n text-align: left; }\n\n#edd-item-tables-wrapper .emails .add-customer-email-row {\n background-color: #f4f4f4;\n border-top: 1px solid #e5e5e5; }\n\n#edd-item-tables-wrapper .add-customer-email-wrapper {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n margin: 12px 0; }\n\n#edd-item-tables-wrapper .edd-form-group {\n margin-bottom: 0; }\n\n#edd-item-tables-wrapper .edd-make-email-primary {\n flex-grow: 1;\n margin-left: 12px; }\n\n#edd-item-tables-wrapper .emails .spinner {\n float: none;\n margin: 0 10px;\n align-self: center; }\n\n#edd-item-tables-wrapper .notice-error {\n background-color: #fff5f5; }\n\n#edd-item-notes-wrapper {\n min-height: 50px; }\n\n.edd-item-header-small {\n height: 30px;\n padding-bottom: 20px;\n border-bottom: 1px solid #e5e5e5; }\n\n.edd-item-header-small span {\n font-weight: 600;\n line-height: 15px;\n font-size: 15px;\n display: inline-block;\n margin-bottom: 10px; }\n\n.edd-item-header-small span,\n.edd-item-header-small img {\n vertical-align: middle; }\n\n.edd-item-header-small img.avatar {\n margin-right: 5px; }\n\n.customer-note-input {\n margin-bottom: 5px;\n width: 100%; }\n\n.customer-note-wrapper {\n border-bottom: 1px solid #f9f9f9;\n min-height: 38px;\n padding: 7px 0 7px 7px; }\n\n.customer-note-wrapper span {\n display: block; }\n\n.note-content-wrap {\n padding-top: 7px; }\n\n.edd-sections-wrap .section-wrap .notice-container {\n padding-left: 20px;\n padding-right: 20px;\n margin-left: -20px;\n margin-right: -20px; }\n\n@media screen and (max-width: 810px) and (min-width: 656px) {\n .customer-info .customer-name {\n font-size: 16px; }\n .edd-sections-wrap .section-wrap .widefat td, .widefat th {\n max-width: 100% !important;\n display: table-cell; } }\n\n@media screen and (max-width: 781px) {\n #edd-item-tab-wrapper,\n .edd-sections-wrap .section-wrap {\n margin: 0;\n width: 100%; }\n #edd-item-tab-wrapper-list .dashicons {\n /** [1] */\n font-size: 18px; }\n .edd-item-has-tabs .edd-sections-wrap .section-wrap {\n border-top: 1px solid #e5e5e5;\n border-left: 0;\n margin-top: -1px; } }\n\n@media screen and (max-width: 656px) {\n .edd-item-info.customer-info {\n position: relative; }\n .edd-sections-wrap .section-wrap .customer-address-wrapper {\n float: none;\n position: absolute;\n top: 84px;\n left: 165px;\n max-width: 200px; }\n .edd-sections-wrap .section-wrap .customer-main-wrapper {\n float: none;\n position: absolute;\n left: 165px; }\n .customer-info .customer-name {\n font-size: 16px; }\n .edd-sections-wrap .section-wrap #edd-item-stats-wrapper {\n padding-left: 0;\n padding-right: 0; }\n .edd-sections-wrap .section-wrap .customer-section {\n margin-bottom: 0; }\n .edd-sections-wrap .section-wrap .widefat td.no-items,\n .edd-sections-wrap .section-wrap .widefat td.column-primary,\n .edd-sections-wrap .section-wrap .widefat th.column-primary {\n width: 100px !important;\n display: table-cell;\n overflow: hidden;\n text-align: left; }\n .edd-sections-wrap .section-wrap .customer-id {\n display: none; }\n #edd-item-tables-wrapper .emails td.column-primary {\n padding-right: 10px;\n width: 100% !important; }\n #edd-item-tables-wrapper .edd-form-group {\n margin: 0 0 16px 0; } }\n\n@media screen and (max-width: 480px) {\n #edd-item-tab-wrapper-list li {\n width: 50%; }\n #edd-item-tab-wrapper-list li:nth-child(3n+3) {\n border-width: 0 1px 1px 0; }\n #edd-item-tab-wrapper-list li:nth-child(even) {\n border-width: 0 0 1px 0; }\n .edd-repeatable-row-title,\n .edd-repeatable-row-actions {\n text-align: left;\n width: 100%; }\n .edd-repeatable-row-title {\n padding-bottom: 0; }\n .edd-repeatable-row-standard-fields,\n .edd-bundled-product-row {\n flex-wrap: wrap; }\n .edd-repeatable-row-standard-fields .edd-form-group,\n .edd-bundled-product-row .edd-form-group {\n margin-left: 0 !important;\n margin-bottom: 24px; }\n .edd-bundled-product-row .edd-bundled-product-item-reorder .edd-product-file-reorder {\n padding: 0; }\n .download_page_edd-reports .button {\n text-align: center; }\n #edd-payment-date-filters span {\n display: block; }\n #edd-payment-date-filters span > input {\n float: right; }\n #edd-add-discount select[multiple] option,\n #edd-edit-discount select[multiple] option {\n height: 20px; }\n .download_page_edd-tools .inside input[type=\"text\"],\n .download_page_edd-tools .inside select,\n .download_page_edd-tools .inside input[type=\"submit\"],\n .download_page_edd-settings .inside input[type=\"button\"],\n .download_page_edd-reports .inside input[type=\"text\"],\n .download_page_edd-reports .inside select,\n .download_page_edd-reports .inside input[type=\"submit\"],\n .download_page_edd-reports .inside .button {\n width: 100%; }\n #edd-add-discount select[multiple],\n #edd-edit-discount select[multiple],\n .download_page_edd-tools select[multiple] {\n height: 200px !important; }\n .download_page_edd-settings input[type=\"checkbox\"] {\n margin: 2px 0; }\n .post-type-download input[type=\"checkbox\"] {\n margin-left: 2px; } }\n\n/* System Info page styles\n-------------------------------------------------------------- */\n.inside .edd-tools-textarea {\n background: #32373c;\n color: rgba(240, 245, 250, 0.7);\n font-size: 12px;\n font-family: Menlo, Monaco, monospace;\n display: block;\n overflow: auto;\n white-space: pre;\n width: 100%;\n height: 450px;\n padding: 10px;\n outline: none; }\n\n#system-info-textarea::selection {\n background: #555;\n color: #fff; }\n\n#edd-system-info .edd-inline-button {\n margin-left: 5px; }\n\n/* Tools Styles\n-------------------------------------------------------------- */\n.recount-stats-controls form {\n display: inline; }\n\n.edd-recount-stats-descriptions span {\n display: none;\n line-height: 24px; }\n\n#edd-debug-log .edd-inline-button {\n margin-left: 5px; }\n\n/* Tools Styles\n-------------------------------------------------------------- */\n.edd-vertical-sections {\n overflow: visible;\n display: block;\n display: flex; }\n\n#edd-item-tab-wrapper,\n.edd-vertical-sections .section-nav {\n position: relative;\n width: 20%;\n line-height: 1em;\n margin: 0 -1px 0 0;\n padding: 0;\n background-color: #f5f5f5;\n border-right: 1px solid #e5e5e5;\n box-sizing: border-box;\n max-width: 200px; }\n\n#edd-item-tab-wrapper-list {\n /** [1] */\n margin: 0; }\n\n#edd-item-tab-wrapper li,\n.edd-vertical-sections .section-nav li {\n display: block;\n position: relative;\n margin: 0;\n padding: 0;\n background-color: #fcfcfc; }\n\n.edd-vertical-sections .section-title:last-of-type {\n margin-bottom: 24px; }\n\n#edd-item-tab-wrapper li a,\n#edd-item-tab-wrapper li > .edd-item-tab-label-wrap,\n.edd-vertical-sections .section-nav li a {\n display: flex;\n margin: 0;\n padding: 9px;\n text-decoration: none;\n border-bottom: 1px solid #e5e5e5;\n box-shadow: none;\n position: relative;\n align-items: center; }\n\n#edd-item-tab-wrapper li a:focus,\n#edd-item-tab-wrapper li a:hover,\n.edd-vertical-sections .section-nav li a:hover,\n.edd-vertical-sections .section-nav li a:focus {\n box-shadow: inset 5px 0;\n outline: 0;\n transition: all .25s; }\n\n.edd-vertical-sections .section-nav .section-title--is-active a:after {\n content: '';\n width: 1px;\n height: 100%;\n background: #fff;\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n z-index: 3; }\n\n#edd-item-tab-wrapper li > .edd-item-tab-label-wrap {\n /** [1] */\n background-color: #fff; }\n\n.edd-vertical-sections .section-nav li a > .dashicons,\n.edd-vertical-sections .section-nav li a > span {\n display: inline-block; }\n\n.edd-vertical-sections .section-nav li a > span {\n max-width: 76%; }\n\n.edd-vertical-sections .section-nav li a .dashicons {\n line-height: 20px;\n margin-right: 3px;\n color: #888; }\n\n.edd-vertical-sections .section-nav .section-title--is-active a {\n font-weight: bold;\n color: #555;\n background-color: #fff;\n border-right: none;\n margin-right: -1px; }\n\n.edd-vertical-sections.use-js .section-content,\n.no-js .edd-vertical-sections.use-js .section-nav,\n.no-js .edd-vertical-sections.use-js.edd-item-header-small {\n display: none; }\n\n.no-js .edd-vertical-sections.use-js .section-content {\n display: block; }\n\n/* Fresh */\n.admin-color-fresh .edd-vertical-sections .section-nav li a:hover,\n.admin-color-fresh .edd-vertical-sections .section-nav li a:focus,\n.admin-color-fresh .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #0073aa; }\n\n/* Blue */\n.admin-color-blue .edd-vertical-sections .section-nav li a:hover,\n.admin-color-blue .edd-vertical-sections .section-nav li a:focus,\n.admin-color-blue .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #096484; }\n\n/* Coffee */\n.admin-color-coffee .edd-vertical-sections .section-nav li a:hover,\n.admin-color-coffee .edd-vertical-sections .section-nav li a:focus,\n.admin-color-coffee .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #c7a589; }\n\n/* Ectoplasm */\n.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:hover,\n.admin-color-ectoplasm .edd-vertical-sections .section-nav li a:focus,\n.admin-color-ectoplasm .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #a3b745; }\n\n/* Midnight */\n.admin-color-midnight .edd-vertical-sections .section-nav li a:hover,\n.admin-color-midnight .edd-vertical-sections .section-nav li a:focus,\n.admin-color-midnight .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #e14d43; }\n\n/* Ocean */\n.admin-color-ocean .edd-vertical-sections .section-nav li a:hover,\n.admin-color-ocean .edd-vertical-sections .section-nav li a:focus,\n.admin-color-ocean .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #627c83; }\n\n/* Sunrise */\n.admin-color-sunrise .edd-vertical-sections .section-nav li a:hover,\n.admin-color-sunrise .edd-vertical-sections .section-nav li a:focus,\n.admin-color-sunrise .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #be3631; }\n\n/* Light */\n.admin-color-light .edd-vertical-sections .section-nav li a:hover,\n.admin-color-light .edd-vertical-sections .section-nav li a:focus,\n.admin-color-light .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #888; }\n\n/* bbPress Color Schemes */\n/* Evergreen */\n.admin-color-evergreen .edd-vertical-sections .section-nav li a:hover,\n.admin-color-evergreen .edd-vertical-sections .section-nav li a:focus,\n.admin-color-evergreen .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #36533f; }\n\n/* Mint */\n.admin-color-mint .edd-vertical-sections .section-nav li a:hover,\n.admin-color-mint .edd-vertical-sections .section-nav li a:focus,\n.admin-color-mint .edd-vertical-sections .section-nav .section-title--is-active a {\n box-shadow: inset 5px 0 #4f6d59; }\n\n.edd-vertical-sections .section-nav .section-title--is-active .dashicons {\n color: #555; }\n\n@media only screen and (max-width: 782px) {\n #edd-item-tab-wrapper,\n .edd-vertical-sections .section-nav {\n width: 48px; }\n .edd-vertical-sections .section-nav li a {\n justify-content: center; }\n .edd-vertical-sections .section-nav li a .dashicons {\n width: 24px;\n height: 24px;\n font-size: 24px;\n line-height: 24px;\n margin: 0; }\n .section-nav li .dashicons::before {\n width: 24px;\n height: 24px; }\n #edd-item-tab-wrapper .edd-item-tab-label,\n .section-nav li .label {\n overflow: hidden;\n position: absolute;\n top: -1000em;\n left: -1000em;\n width: 1px;\n height: 1px; } }\n\n/* Content wrapper */\n#edd-item-card-wrapper,\n.edd-vertical-sections .section-wrap {\n width: 80%; }\n\n#edd-item-card-wrapper .item-section {\n /** [1] */\n background: #fff;\n overflow: hidden;\n box-sizing: border-box; }\n\n*:not(#edd-item-tab-wrapper) + #edd-item-card-wrapper .item-section {\n /** [1] */\n margin: 25px 0;\n padding: 20px;\n border: 1px solid #e5e5e5;\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); }\n\n#edd-item-tab-wrapper + #edd-item-card-wrapper {\n /** [1] */\n padding: 20px;\n border-left: 1px solid #e5e5e5;\n box-sizing: border-box; }\n\n@media only screen and (min-width: 1200px) {\n #edd-item-card-wrapper,\n #edd-graphs-filter,\n .edd-vertical-sections:not(.meta-box) .section-wrap {\n width: calc( 100% - 200px); } }\n\n@media only screen and (max-width: 782px) {\n #edd-item-card-wrapper,\n #edd-graphs-filter,\n .edd-vertical-sections .section-wrap {\n width: calc( 100% - 48px); } }\n\n#edd-debug-log .edd-inline-button {\n margin-left: 5px; }\n\n/* Promotional element styles\n-------------------------------------------------------------- */\n/* Settings sidebar */\n.edd-settings-sidebar {\n padding-top: 27px; }\n\n.edd-settings-sidebar-content {\n background-color: #fff;\n text-align: center;\n border: 1px solid #ddd;\n box-sizing: border-box;\n max-width: 300px; }\n\n.edd-settings-sidebar-content p {\n font-size: 14px;\n line-height: 1.5;\n margin-top: 0; }\n\n/* Settings sidebar header section */\n.edd-sidebar-header-section {\n background-color: #35495c;\n line-height: 1;\n padding: 26px 20px 24px;\n border-bottom: 3px dashed #fafafa; }\n\n/* Settings sidebar description section */\n.edd-sidebar-description-section {\n background-color: #fafafa;\n padding: 16px 20px;\n border-bottom: 1px solid #ddd; }\n\n.edd-sidebar-description-section .edd-sidebar-description {\n margin: 0; }\n\n/* Settings sidebar coupon section */\n.edd-sidebar-coupon-section {\n font-size: 14px;\n padding: 16px 20px; }\n\n.edd-sidebar-coupon-section label {\n display: block;\n line-height: 1.4;\n margin-bottom: 6px; }\n\n.edd-sidebar-coupon-section label strong {\n color: #253b51;\n font-weight: 700; }\n\n.edd-sidebar-coupon-section input {\n background: #f4f7fa;\n font-size: 22px;\n font-weight: 600;\n text-align: center;\n padding: 10px;\n border: 2px dashed #2794da;\n border-radius: 4px;\n margin-bottom: 16px;\n box-shadow: none;\n width: 100%; }\n\n.edd-sidebar-coupon-section input:focus {\n border: 2px dashed #2794da;\n box-shadow: none; }\n\n.edd-settings-sidebar-content .edd-coupon-note {\n color: #6c7883;\n font-size: 13px;\n font-style: italic;\n margin: 0; }\n\n.edd-settings-sidebar-content .edd-coupon-note a {\n color: #253b51; }\n\n.edd-settings-sidebar-content .edd-coupon-note a:hover {\n text-decoration: none; }\n\n/* Settings sidebar footer section */\n.edd-sidebar-footer-section {\n background-color: #fafafa;\n padding: 16px 20px;\n border-top: 1px solid #ddd; }\n\n.edd-sidebar-footer-section .edd-cta-button {\n display: block;\n background-color: #2794da;\n color: #fff;\n text-decoration: none;\n font-size: 20px;\n font-weight: 700;\n text-transform: uppercase;\n padding: 17px 10px;\n border: none;\n border-radius: 4px;\n width: 100%;\n box-sizing: border-box;\n box-shadow: none;\n transition: background-color .2s; }\n\n.edd-sidebar-footer-section .edd-cta-button:hover {\n background-color: #2386c5; }\n\n/* Settings sidebar responsive behavior */\n@media all and (min-width: 1080px) {\n .edd-has-sidebar .edd-settings-content {\n float: left;\n width: 67%; }\n .edd-has-sidebar .edd-settings-sidebar {\n float: right;\n width: 31%; } }\n\n@media all and (min-width: 1240px) {\n .edd-has-sidebar .edd-settings-content {\n width: 74%; }\n .edd-has-sidebar .edd-settings-sidebar {\n width: 23%; } }\n\n/* Settings - Move sidebar below content only on Taxes tab */\n.taxes-tab .edd-has-sidebar .edd-settings-content,\n.taxes-tab .edd-has-sidebar .edd-settings-sidebar {\n float: none;\n width: 100%; }\n\n/* Extensions (add-ons) page promotional element */\n.bfcm-promo-img-container {\n background-color: #35495c;\n width: 100%;\n height: 160px; }\n\n.bfcm-code {\n color: #2794da;\n font-weight: 700; }\n\n.sale-ends {\n position: absolute;\n bottom: 9px;\n right: 14px;\n display: inline-block;\n color: #6c7883;\n font-size: 12px;\n text-align: right;\n font-style: italic;\n width: 150px; }\n\n/**\n * Forms\n * ---------------------------------------------------------- */\n/**\n * Form Group\n *\n *
\n * \n *
\n * \t\n *
\n *

Help

\n *
\n *\n *\n *
\n * Label\n *\n *
\n * \t\n * \t\n *
\n *\n *
\n * \t\n * \t\n *
\n *
\n *\n */\n.edd-form-group {\n margin-bottom: 16px; }\n\n.edd-form-group:last-of-type {\n margin-bottom: 0; }\n\n.edd-form-group__label {\n display: block;\n margin-bottom: 8px;\n padding: 0; }\n\n.edd-form-group__control {\n margin-bottom: 8px; }\n\n.edd-form-group__control.is-radio,\n.edd-form-group__control.is-check {\n margin-top: 4px; }\n\n.edd-form-group__control:last-of-type {\n margin-bottom: 0; }\n\n.edd-form-group__input {\n max-width: 100%; }\n\n.edd-form-group__input[type=\"checkbox\"],\n.edd-form-group__input[type=\"radio\"] {\n margin-top: 0; }\n\n.edd-form-group__input[type=\"checkbox\"] + label,\n.edd-form-group__input[type=\"radio\"] + label {\n display: unset; }\n\nselect.edd-form-group__input {\n max-width: 100%; }\n\n.edd-form-group__help {\n color: #666;\n font-size: 13px;\n font-style: italic;\n line-height: initial;\n margin: 8px 0 0; }\n"],"sourceRoot":""} \ No newline at end of file diff --git a/assets/css/edd-rtl.min.css b/assets/css/edd-rtl.min.css new file mode 100644 index 00000000000..ffe05d39534 --- /dev/null +++ b/assets/css/edd-rtl.min.css @@ -0,0 +1 @@ +.edd-icon{display:inline-block;fill:currentColor;position:relative;vertical-align:middle}.edd-icon-spin{display:inline-block;animation:edd-icon-spin 2s linear infinite}@keyframes edd-icon-spin{0%{transform:rotate(0deg)}to{transform:rotate(-359deg)}}.edd_clearfix:after{display:block;visibility:hidden;float:none;clear:both;text-indent:-9999px;content:"."}#edd_checkout_cart{text-align:right;width:100%;border:none;margin:0 0 21px;table-layout:auto}#edd_checkout_cart td,#edd_checkout_cart th{text-align:right;border:1px solid #eee;color:#666;padding:.5em 1.387em}#edd_checkout_cart .edd_cart_header_row th{background:#fafafa;padding:1.387em}#edd_checkout_cart .edd_cart_discount_row th,#edd_checkout_cart .edd_cart_tax_row th{background:none}#edd_checkout_cart th{font-weight:700}#edd_checkout_cart td{line-height:25px;vertical-align:middle;background:#fff}#edd_checkout_cart td.edd_cart_actions,#edd_checkout_cart td:last-child,#edd_checkout_cart th.edd_cart_actions,#edd_checkout_cart th.edd_cart_total,#edd_checkout_cart th:last-child{text-align:left}#edd_checkout_cart td img{float:right;margin:0 0 0 8px;background:none;padding:0;border:none}#edd_checkout_cart input.edd-item-quantity{width:3em;padding:2px}#edd_checkout_cart .edd_discount{display:inline-block;margin-right:5px}.edd_discount_remove{display:inline-block;width:14px;height:14px;background:url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22currentColor%22%3E%0A%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M9.75%209.75l4.5%204.5m0-4.5l-4.5%204.5M21%2012a9%209%200%2011-18%200%209%209%200%200118%200z%22%20%2F%3E%0A%3C%2Fsvg%3E%0A) 100% 0 no-repeat;position:relative;opacity:.6}.edd_discount_remove:hover{opacity:1}#edd_checkout_cart br{display:none}#edd_checkout_cart a.edd-cart-saving-button{font-weight:400;text-decoration:none}#edd_checkout_form_wrap legend{display:block;font-size:120%;line-height:1;font-weight:700;width:100%;margin:0 0 1rem;padding:0}#edd_checkout_form_wrap label{font-weight:700;display:block;position:relative;line-height:100%;font-size:95%;margin:0 0 5px}#edd_checkout_form_wrap span.edd-description{color:#666;font-size:80%;display:block;margin:0 0 5px}#edd_checkout_form_wrap input.edd-input,#edd_checkout_form_wrap textarea.edd-input{display:inline-block;width:70%}#edd_checkout_form_wrap select.edd-select{display:block;width:60%}#edd_checkout_form_wrap select.edd-select.edd-select-small{display:inline;width:auto}#edd_checkout_form_wrap input.edd-input.error,#edd_checkout_form_wrap textarea.edd-input.error{border-color:#c4554e}#edd_checkout_form_wrap>p{margin:0 0 21px}#edd_checkout_form_wrap span.edd-required-indicator{color:#b94a48;display:inline}#edd_checkout_form_wrap input[type=email],#edd_checkout_form_wrap input[type=password],#edd_checkout_form_wrap input[type=tel],#edd_checkout_form_wrap input[type=text],#edd_checkout_form_wrap textarea{padding:4px 6px}#edd_checkout_form_wrap input[type=radio]{border:none;margin-left:5px}#edd_checkout_form_wrap input[type=checkbox]{display:inline-block;margin:0 0 0 5px}#edd_checkout_form_wrap input[type=checkbox]+label,#edd_checkout_form_wrap input[type=checkbox]+label:after{display:inline}#edd_checkout_form_wrap .edd-payment-icons{display:flex;margin:0 0 8px}#edd_checkout_form_wrap .edd-payment-icons img.payment-icon{max-height:32px}#edd_checkout_form_wrap .edd-payment-icons .payment-icon{margin:0 0 0 10px}#edd_checkout_form_wrap #edd-payment-mode-wrap label{display:inline-block;margin:0 0 0 20px}#edd_checkout_form_wrap #edd-payment-mode-wrap .edd-payment-mode-label{font-weight:700;display:inline-block;position:relative;margin-bottom:5px}#edd_checkout_form_wrap fieldset{border:1px solid #eee;padding:1.387em;margin:0 0 21px}#edd_checkout_form_wrap #edd_discount_code,#edd_checkout_form_wrap #edd_purchase_submit,#edd_checkout_form_wrap #edd_register_account_fields{padding:0;border:none}#edd_checkout_form_wrap fieldset fieldset{margin:0;border:none;padding:0}#edd_checkout_form_wrap #edd-login-account-wrap,#edd_checkout_form_wrap #edd-new-account-wrap,#edd_checkout_form_wrap #edd_final_total_wrap,#edd_checkout_form_wrap #edd_show_discount,#edd_checkout_form_wrap .edd-cart-adjustment{background:#fafafa;color:#666;padding:.5em 1.387em}#edd_checkout_form_wrap #edd-discount-code-wrap,#edd_checkout_form_wrap #edd_final_total_wrap,#edd_checkout_form_wrap #edd_show_discount{border:1px solid #eee}#edd_checkout_form_wrap .edd-cart-adjustment{padding:1.387em}#edd_checkout_form_wrap .edd-cart-adjustment input.edd-input,#edd_checkout_form_wrap .edd-cart-adjustment input.edd-submit{display:inline-block}#edd_checkout_form_wrap .edd-cart-adjustment input.edd-submit{padding:3px 12px;margin-bottom:2px}#edd_checkout_form_wrap #edd-discount-error-wrap{width:100%;display:inline-block;margin:1em 0 0}#edd_checkout_form_wrap #edd-login-account-wrap,#edd_checkout_form_wrap #edd-new-account-wrap{margin:-1.387em -1.387em 21px;border-right:none;border-left:none;border-top:none}#edd_checkout_form_wrap #edd_payment_mode_select,#edd_checkout_form_wrap fieldset#edd_register_fields #edd_checkout_user_info{margin-bottom:21px}#edd_checkout_form_wrap fieldset#edd_register_account_fields legend{padding-top:11px}#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_login_password,#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_register_password{margin:0}#edd_checkout_form_wrap fieldset#edd_cc_fields legend{border:none;padding:0}#edd_checkout_form_wrap fieldset p:last-child{margin-bottom:0}#edd_checkout_form_wrap fieldset#edd_cc_fields #edd-card-number-wrap{margin-top:5px}#edd_checkout_form_wrap #edd_purchase_final_total{margin:21px 0}#edd_checkout_form_wrap #edd_purchase_final_total p{margin:0}#edd_secure_site_wrapper{padding:4px 0 4px 4px;font-weight:700}#edd_secure_site_wrapper span{vertical-align:middle}#edd_checkout_form_wrap input.edd-input.card-number.valid{background-image:url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22green%22%3E%0A%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M4.5%2012.75l6%206%209-13.5%22%20%2F%3E%0A%3C%2Fsvg%3E%0A);background-repeat:no-repeat;background-position:2% 50%}#edd_checkout_form_wrap span.exp-divider{display:inline}#edd_checkout_form_wrap span.card-type{position:absolute;top:0;left:0}#edd_checkout_form_wrap span.card-type.off{display:none}#edd_checkout_form_wrap .edd-cart-ajax{box-shadow:none}.edd-amazon-profile-wrapper{font-size:12px}.edd-amazon-profile-name{font-weight:600}.edd-amazon-logout{font-size:10px;line-height:12px}.edd-amazon-logout a{cursor:pointer}#edd-amazon-address-box,#edd-amazon-wallet-box{height:228px;width:350px}#edd-amazon-address-box{margin-bottom:15px}.edd_cart_tax .edd-loading-ajax.edd-loading{margin:0 auto 0 0;display:inline-block}@media only screen and (min-width:768px){#edd-amazon-address-box,#edd-amazon-wallet-box{width:100%;height:228px}}.edd_purchase_submit_wrapper{position:relative}.edd_purchase_submit_wrapper a.edd-add-to-cart{text-decoration:none;display:none;position:relative;overflow:hidden}.edd_purchase_submit_wrapper .edd-cart-ajax{display:none;position:relative;right:-35px}.edd-submit.button.edd-ajax-loading{padding-left:30px}.edd-add-to-cart .edd-add-to-cart-label{opacity:1;filter:alpha(opacity=100)}.edd-loading,.edd-loading:after{border-radius:50%;display:block;width:1.5em;height:1.5em}.edd-loading{animation:edd-spinning 1.1s linear infinite;border:.2em solid hsla(0,0%,100%,.2);border-right-color:#fff;font-size:.75em;position:absolute;right:calc(50% - .75em);top:calc(50% - .75em);opacity:0;filter:alpha(opacity=0);transform:translateZ(0)}.edd-discount-loader.edd-loading,.edd-loading-ajax.edd-loading,a.edd-add-to-cart.white .edd-loading{border-color:rgba(0,0,0,.2) #000 rgba(0,0,0,.2) rgba(0,0,0,.2)}.edd-loading-ajax.edd-loading{display:inline-block;position:relative;top:0;right:.25em;vertical-align:middle}#edd_checkout_form_wrap .edd-cart-adjustment .edd-apply-discount.edd-submit{display:inline-block}.edd-discount-loader.edd-loading{display:inline-block;position:relative;right:auto;vertical-align:middle;width:1.25em;height:1.25em}.edd-loading-ajax.edd-loading{opacity:1}@keyframes edd-spinning{0%{transform:rotate(0deg)}to{transform:rotate(-1turn)}}.edd-loading,a.edd-add-to-cart .edd-add-to-cart-label{transition:opacity .1s!important}.edd-add-to-cart[data-edd-loading] .edd-add-to-cart-label{opacity:0;filter:alpha(opacity=0)}.edd-add-to-cart[data-edd-loading] .edd-loading,.edd-discount-loader.edd-loading{opacity:1;filter:alpha(opacity=100)}.edd-cart-added-alert{color:#567622;display:block;position:absolute}.edd_form input.edd-input.required,.edd_form select.edd-select.required{color:#000}body.edd_receipt_page{background-color:#fff;color:#141412;margin:0;font-family:Helvetica,sans-serif;font-size:12px}body.edd_receipt_page:before{position:relative}body.edd_receipt_page #edd_receipt_wrapper{width:660px;margin:0 auto;padding:50px 0}body.edd_receipt_page table{display:table;width:100%;border-bottom:1px solid #ededed;border-collapse:collapse;border-spacing:0;font-size:14px;line-height:2;margin:0 0 20px}body.edd_receipt_page td,body.edd_receipt_page th{display:table-cell;text-align:right;border-top:1px solid #ededed;padding:6px 10px;font-weight:400}body.edd_receipt_page th{font-weight:700;text-transform:uppercase}body.edd_receipt_page h3{font-size:22px;margin:40px 0 5px;clear:both;display:block;font-weight:700}body.edd_receipt_page li{list-style:none}table#edd_purchase_receipt,table#edd_purchase_receipt_products{width:100%}table#edd_purchase_receipt_products td,table#edd_purchase_receipt_products th,table#edd_purchase_receipt td,table#edd_purchase_receipt th{text-align:right}table#edd_purchase_receipt .edd_receipt_payment_status.cancelled,table#edd_purchase_receipt .edd_receipt_payment_status.failed,table#edd_purchase_receipt .edd_receipt_payment_status.pending,table#edd_purchase_receipt .edd_receipt_payment_status.revoked{color:#f73f2e}table#edd_purchase_receipt_products li{list-style:none;margin:0 10px 8px 0}table#edd_purchase_receipt_products ul.edd_purchase_receipt_files,table#edd_purchase_receipt ul{margin:0;padding:0}table#edd_purchase_receipt li.edd_download_file{list-style:none;margin:0 0 8px}table#edd_purchase_receipt_products .edd_purchase_receipt_product_notes{font-style:italic}table#edd_purchase_receipt_products .edd_purchase_receipt_product_name{font-weight:700}table#edd_purchase_receipt_products .edd_bundled_product_name{font-style:italic;font-weight:700}#edd_user_history{text-align:right;width:100%;border-top:1px solid #f0f0f0;border-bottom:none}#edd_user_history td,#edd_user_history th{text-align:right;padding:3px 5px;border-bottom:1px solid #f0f0f0;border-top:none}#edd_user_history th{font-weight:700;background:#f5f5f5}#edd_user_history td{line-height:25px;vertical-align:middle}#edd_user_history .edd_purchase_status.cancelled,#edd_user_history .edd_purchase_status.failed,#edd_user_history .edd_purchase_status.pending,#edd_user_history .edd_purchase_status.revoked{color:#f73f2e}#edd_login_form legend,#edd_register_form legend{font-size:120%;margin-bottom:1em}#edd_login_form fieldset,#edd_register_form fieldset{border:none}#edd_login_form .edd-input,#edd_register_form .edd-input{box-sizing:border-box}#edd_login_form label,#edd_register_form label{cursor:pointer}#edd_profile_editor_form p{margin-bottom:8px}#edd_profile_editor_form label{display:inline-block}#edd_profile_editor_form .edd-profile-emails{list-style-type:none;display:inline-table;margin-right:0;margin-bottom:0}#edd_profile_editor_form .edd-profile-email{width:auto}#edd_profile_editor_form .edd-profile-email .actions{display:none}#edd_profile_editor_form .edd-profile-email:hover>span{display:inline-block}.edd_added_to_cart_alert{padding:5px;font-size:14px;border:1px solid #046a9e;background:#9ecce2;color:#333;margin:8px 0}.edd_added_to_cart_alert a.edd_alert_checkout_link{color:#000!important}input.edd_submit_plain{background:none!important;border:none!important;padding:0!important;display:inline;cursor:pointer}.single-download .edd_download_purchase_form{margin-bottom:1.387em}.edd_download_purchase_form .edd_download_quantity_wrapper{margin:0 0 .5em}.edd_download_purchase_form .edd_download_quantity_wrapper .edd-item-quantity{width:75px}.edd_download_purchase_form .edd_price_options{margin:0 0 15px}.edd_download_purchase_form .edd_price_options ul{margin:0;padding:0;list-style:none}.edd_download_purchase_form .edd_price_options li{display:block;padding:0;margin:0}.edd_download_purchase_form .edd_price_options span{display:inline;padding:0;margin:0}.edd_download_purchase_form .edd_price_options .edd_download_quantity_wrapper{padding-right:18px}.edd_download_purchase_form .edd_price_options .edd_download_quantity_wrapper *{font-size:80%}.edd_download_purchase_form .edd_price_options input.edd-item-quantity{display:inline;width:50px;max-width:90%}#edd-purchase-button,.edd-submit,[type=submit].edd-submit{display:inline-block;padding:6px 12px;margin:0;font-size:14px;font-weight:400;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid #ccc;border-radius:4px;box-shadow:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.edd-submit.button:focus,[type=submit].edd-submit:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.edd-submit.button:active{background-image:none;outline:0;box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.edd-submit.plain{padding:0;border:none;border-radius:0}.edd-submit.button,.edd-submit.button.gray,.edd-submit.button:visited{color:#333;background:#f0f0f0;border-color:#ccc}.edd-submit.button.gray:active,.edd-submit.button.gray:focus,.edd-submit.button.gray:hover,.edd-submit.button:active,.edd-submit.button:focus,.edd-submit.button:hover{color:#333;background:#ebebeb;border-color:#adadad}.edd-submit.button.gray:active{background-image:none}.edd-submit.button.white{color:#333;background:#fff;border-color:#ccc}.edd-submit.button.white:active,.edd-submit.button.white:focus,.edd-submit.button.white:hover{color:#333;background:#ebebeb;border-color:#adadad}.edd-submit.button.white:active{background-image:none}.edd-submit.button.blue{color:#fff;background:#428bca;border-color:#357ebd}.edd-submit.button.blue.active,.edd-submit.button.blue:focus,.edd-submit.button.blue:hover{color:#fff;background:#3276b1;border-color:#285e8e}.edd-submit.button.blue.active{background-image:none}.edd-submit.button.red{color:#fff;background:#d9534f;border-color:#d43f3a}.edd-submit.button.red:active,.edd-submit.button.red:focus,.edd-submit.button.red:hover{color:#fff;background:#d2322d;border-color:#ac2925}.edd-submit.button.red:active{background-image:none}.edd-submit.button.green{color:#fff;background:#5cb85c;border-color:#4cae4c}.edd-submit.button.green:active,.edd-submit.button.green:focus,.edd-submit.button.green:hover{color:#fff;background:#47a447;border-color:#398439}.edd-submit.button.green:active{background-image:none}.edd-submit.button.yellow{color:#fff;background:#f0ad4e;border-color:#eea236}.edd-submit.button.yellow:active,.edd-submit.button.yellow:focus,.edd-submit.button.yellow:hover{color:#fff;background:#ed9c28;border-color:#d58512}.edd-submit.button.yellow:active{background-image:none}.edd-submit.button.orange{color:#fff;background:#ed9c28;border-color:#e3921e}.edd-submit.button.orange:active,.edd-submit.button.orange:focus,.edd-submit.button.orange:hover{color:#fff;background:#e59016;border-color:#d58512}.edd-submit.button.orange:active{background-image:none}.edd-submit.button.dark-gray{color:#fff;background:#363636;border-color:#222}.edd-submit.button.dark-gray:active,.edd-submit.button.dark-gray:focus,.edd-submit.button.dark-gray:hover{color:#fff;background:#333;border-color:#adadad}.edd-submit.button.dark-gray:active{background-image:none}.edd_downloads_list{display:-ms-grid;display:grid;grid-column-gap:20px;grid-row-gap:40px}.edd_downloads_list:after{content:"";display:table;clear:both}.edd_download{float:right}.edd_download_columns_1 .edd_download{width:100%}.edd_download_columns_2 .edd_download{width:50%}.edd_download_columns_0 .edd_download,.edd_download_columns_3 .edd_download{width:33%}.edd_download_columns_4 .edd_download{width:25%}.edd_download_columns_5 .edd_download{width:20%}.edd_download_columns_6 .edd_download{width:16.6%}.edd_download_inner{padding:0 8px 8px;margin:0 0 10px}.edd_download_columns_2 .edd_download:nth-child(odd),.edd_download_columns_3 .edd_download:nth-child(3n+1),.edd_download_columns_4 .edd_download:nth-child(4n+1),.edd_download_columns_5 .edd_download:nth-child(5n+1),.edd_download_columns_6 .edd_download:nth-child(6n+1){clear:right}.edd_download_image{max-width:100%}.edd_download .edd_price{margin-bottom:10px}@media(min-width:768px){.edd_downloads_list:not(.edd_download_columns_1){-ms-grid-columns:(1fr)[2];grid-template-columns:repeat(2,1fr)}}@media(min-width:1200px){.edd_downloads_list.edd_download_columns_2{-ms-grid-columns:(1fr)[2];grid-template-columns:repeat(2,1fr)}.edd_downloads_list.edd_download_columns_3{-ms-grid-columns:(1fr)[3];grid-template-columns:repeat(3,1fr)}.edd_downloads_list.edd_download_columns_4{-ms-grid-columns:(1fr)[4];grid-template-columns:repeat(4,1fr)}.edd_downloads_list.edd_download_columns_5{-ms-grid-columns:(1fr)[5];grid-template-columns:repeat(5,1fr)}.edd_downloads_list.edd_download_columns_6{-ms-grid-columns:(1fr)[6];grid-template-columns:repeat(6,1fr)}}@supports(display:grid){.edd_downloads_list .edd_download{width:auto}.edd_download_inner{padding:0;margin:0}}.edd-hide-on-empty.cart-empty{display:none}.edd-cart-ajax{margin:0 4px 0 8px;position:relative;top:2px;background:none;border:none;padding:0}.edd-cart-number-of-items{font-style:italic;color:grey}.edd-cart-meta.edd_subtotal{font-weight:700;font-style:italic}.edd-cart-meta.edd_cart_tax{font-size:1em;font-style:italic}.edd-cart-meta.edd_cart_tax:before{font-style:normal}.edd-cart-meta.edd_total{font-weight:700}.edd-cart-meta{padding:2px 5px}.edd-cart-meta.edd_subtotal,.edd-cart-meta.edd_total{background-color:#f9f9f9}.edd_errors:not(.edd-alert){border-radius:2px;border:1px solid #e6db55;margin:0 0 21px;background:#ffffe0;color:#333}.edd_error{padding:10px}p.edd_error{margin:0!important}.edd_success:not(.edd-alert){border-radius:2px;border:1px solid #b3ce89;margin:20px 0;background:#d5eab3;color:#567622;padding:6px 8px;box-shadow:inset 0 1px 0 hsla(0,0%,100%,.7)}.edd-alert{border-radius:2px;margin-bottom:20px;padding:10px;border:1px solid transparent;vertical-align:middle}.edd-alert p{padding:0}.edd-alert p:not(:last-child){margin-bottom:5px}.edd-alert p:last-child{margin-bottom:0}.edd-alert-error{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.edd-alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.edd-alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.edd-alert-warn{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} \ No newline at end of file diff --git a/assets/css/edd.min.css b/assets/css/edd.min.css new file mode 100644 index 00000000000..637125945df --- /dev/null +++ b/assets/css/edd.min.css @@ -0,0 +1 @@ +.edd-icon{display:inline-block;fill:currentColor;position:relative;vertical-align:middle}.edd-icon-spin{display:inline-block;animation:edd-icon-spin 2s linear infinite}@keyframes edd-icon-spin{0%{transform:rotate(0deg)}to{transform:rotate(359deg)}}.edd_clearfix:after{display:block;visibility:hidden;float:none;clear:both;text-indent:-9999px;content:"."}#edd_checkout_cart{text-align:left;width:100%;border:none;margin:0 0 21px;table-layout:auto}#edd_checkout_cart td,#edd_checkout_cart th{text-align:left;border:1px solid #eee;color:#666;padding:.5em 1.387em}#edd_checkout_cart .edd_cart_header_row th{background:#fafafa;padding:1.387em}#edd_checkout_cart .edd_cart_discount_row th,#edd_checkout_cart .edd_cart_tax_row th{background:none}#edd_checkout_cart th{font-weight:700}#edd_checkout_cart td{line-height:25px;vertical-align:middle;background:#fff}#edd_checkout_cart td.edd_cart_actions,#edd_checkout_cart td:last-child,#edd_checkout_cart th.edd_cart_actions,#edd_checkout_cart th.edd_cart_total,#edd_checkout_cart th:last-child{text-align:right}#edd_checkout_cart td img{float:left;margin:0 8px 0 0;background:none;padding:0;border:none}#edd_checkout_cart input.edd-item-quantity{width:3em;padding:2px}#edd_checkout_cart .edd_discount{display:inline-block;margin-left:5px}.edd_discount_remove{display:inline-block;width:14px;height:14px;background:url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22currentColor%22%3E%0A%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M9.75%209.75l4.5%204.5m0-4.5l-4.5%204.5M21%2012a9%209%200%2011-18%200%209%209%200%200118%200z%22%20%2F%3E%0A%3C%2Fsvg%3E%0A) 0 0 no-repeat;position:relative;opacity:.6}.edd_discount_remove:hover{opacity:1}#edd_checkout_cart br{display:none}#edd_checkout_cart a.edd-cart-saving-button{font-weight:400;text-decoration:none}#edd_checkout_form_wrap legend{display:block;font-size:120%;line-height:1;font-weight:700;width:100%;margin:0 0 1rem;padding:0}#edd_checkout_form_wrap label{font-weight:700;display:block;position:relative;line-height:100%;font-size:95%;margin:0 0 5px}#edd_checkout_form_wrap span.edd-description{color:#666;font-size:80%;display:block;margin:0 0 5px}#edd_checkout_form_wrap input.edd-input,#edd_checkout_form_wrap textarea.edd-input{display:inline-block;width:70%}#edd_checkout_form_wrap select.edd-select{display:block;width:60%}#edd_checkout_form_wrap select.edd-select.edd-select-small{display:inline;width:auto}#edd_checkout_form_wrap input.edd-input.error,#edd_checkout_form_wrap textarea.edd-input.error{border-color:#c4554e}#edd_checkout_form_wrap>p{margin:0 0 21px}#edd_checkout_form_wrap span.edd-required-indicator{color:#b94a48;display:inline}#edd_checkout_form_wrap input[type=email],#edd_checkout_form_wrap input[type=password],#edd_checkout_form_wrap input[type=tel],#edd_checkout_form_wrap input[type=text],#edd_checkout_form_wrap textarea{padding:4px 6px}#edd_checkout_form_wrap input[type=radio]{border:none;margin-right:5px}#edd_checkout_form_wrap input[type=checkbox]{display:inline-block;margin:0 5px 0 0}#edd_checkout_form_wrap input[type=checkbox]+label,#edd_checkout_form_wrap input[type=checkbox]+label:after{display:inline}#edd_checkout_form_wrap .edd-payment-icons{display:flex;margin:0 0 8px}#edd_checkout_form_wrap .edd-payment-icons img.payment-icon{max-height:32px}#edd_checkout_form_wrap .edd-payment-icons .payment-icon{margin:0 10px 0 0}#edd_checkout_form_wrap #edd-payment-mode-wrap label{display:inline-block;margin:0 20px 0 0}#edd_checkout_form_wrap #edd-payment-mode-wrap .edd-payment-mode-label{font-weight:700;display:inline-block;position:relative;margin-bottom:5px}#edd_checkout_form_wrap fieldset{border:1px solid #eee;padding:1.387em;margin:0 0 21px}#edd_checkout_form_wrap #edd_discount_code,#edd_checkout_form_wrap #edd_purchase_submit,#edd_checkout_form_wrap #edd_register_account_fields{padding:0;border:none}#edd_checkout_form_wrap fieldset fieldset{margin:0;border:none;padding:0}#edd_checkout_form_wrap #edd-login-account-wrap,#edd_checkout_form_wrap #edd-new-account-wrap,#edd_checkout_form_wrap #edd_final_total_wrap,#edd_checkout_form_wrap #edd_show_discount,#edd_checkout_form_wrap .edd-cart-adjustment{background:#fafafa;color:#666;padding:.5em 1.387em}#edd_checkout_form_wrap #edd-discount-code-wrap,#edd_checkout_form_wrap #edd_final_total_wrap,#edd_checkout_form_wrap #edd_show_discount{border:1px solid #eee}#edd_checkout_form_wrap .edd-cart-adjustment{padding:1.387em}#edd_checkout_form_wrap .edd-cart-adjustment input.edd-input,#edd_checkout_form_wrap .edd-cart-adjustment input.edd-submit{display:inline-block}#edd_checkout_form_wrap .edd-cart-adjustment input.edd-submit{padding:3px 12px;margin-bottom:2px}#edd_checkout_form_wrap #edd-discount-error-wrap{width:100%;display:inline-block;margin:1em 0 0}#edd_checkout_form_wrap #edd-login-account-wrap,#edd_checkout_form_wrap #edd-new-account-wrap{margin:-1.387em -1.387em 21px;border-left:none;border-right:none;border-top:none}#edd_checkout_form_wrap #edd_payment_mode_select,#edd_checkout_form_wrap fieldset#edd_register_fields #edd_checkout_user_info{margin-bottom:21px}#edd_checkout_form_wrap fieldset#edd_register_account_fields legend{padding-top:11px}#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_login_password,#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_register_password{margin:0}#edd_checkout_form_wrap fieldset#edd_cc_fields legend{border:none;padding:0}#edd_checkout_form_wrap fieldset p:last-child{margin-bottom:0}#edd_checkout_form_wrap fieldset#edd_cc_fields #edd-card-number-wrap{margin-top:5px}#edd_checkout_form_wrap #edd_purchase_final_total{margin:21px 0}#edd_checkout_form_wrap #edd_purchase_final_total p{margin:0}#edd_secure_site_wrapper{padding:4px 4px 4px 0;font-weight:700}#edd_secure_site_wrapper span{vertical-align:middle}#edd_checkout_form_wrap input.edd-input.card-number.valid{background-image:url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22green%22%3E%0A%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M4.5%2012.75l6%206%209-13.5%22%20%2F%3E%0A%3C%2Fsvg%3E%0A);background-repeat:no-repeat;background-position:98% 50%}#edd_checkout_form_wrap span.exp-divider{display:inline}#edd_checkout_form_wrap span.card-type{position:absolute;top:0;right:0}#edd_checkout_form_wrap span.card-type.off{display:none}#edd_checkout_form_wrap .edd-cart-ajax{box-shadow:none}.edd-amazon-profile-wrapper{font-size:12px}.edd-amazon-profile-name{font-weight:600}.edd-amazon-logout{font-size:10px;line-height:12px}.edd-amazon-logout a{cursor:pointer}#edd-amazon-address-box,#edd-amazon-wallet-box{height:228px;width:350px}#edd-amazon-address-box{margin-bottom:15px}.edd_cart_tax .edd-loading-ajax.edd-loading{margin:0 0 0 auto;display:inline-block}@media only screen and (min-width:768px){#edd-amazon-address-box,#edd-amazon-wallet-box{width:100%;height:228px}}.edd_purchase_submit_wrapper{position:relative}.edd_purchase_submit_wrapper a.edd-add-to-cart{text-decoration:none;display:none;position:relative;overflow:hidden}.edd_purchase_submit_wrapper .edd-cart-ajax{display:none;position:relative;left:-35px}.edd-submit.button.edd-ajax-loading{padding-right:30px}.edd-add-to-cart .edd-add-to-cart-label{opacity:1;filter:alpha(opacity=100)}.edd-loading,.edd-loading:after{border-radius:50%;display:block;width:1.5em;height:1.5em}.edd-loading{animation:edd-spinning 1.1s linear infinite;border:.2em solid hsla(0,0%,100%,.2);border-left-color:#fff;font-size:.75em;position:absolute;left:calc(50% - .75em);top:calc(50% - .75em);opacity:0;filter:alpha(opacity=0);transform:translateZ(0)}.edd-discount-loader.edd-loading,.edd-loading-ajax.edd-loading,a.edd-add-to-cart.white .edd-loading{border-color:rgba(0,0,0,.2) rgba(0,0,0,.2) rgba(0,0,0,.2) #000}.edd-loading-ajax.edd-loading{display:inline-block;position:relative;top:0;left:.25em;vertical-align:middle}#edd_checkout_form_wrap .edd-cart-adjustment .edd-apply-discount.edd-submit{display:inline-block}.edd-discount-loader.edd-loading{display:inline-block;position:relative;left:auto;vertical-align:middle;width:1.25em;height:1.25em}.edd-loading-ajax.edd-loading{opacity:1}@keyframes edd-spinning{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.edd-loading,a.edd-add-to-cart .edd-add-to-cart-label{transition:opacity .1s!important}.edd-add-to-cart[data-edd-loading] .edd-add-to-cart-label{opacity:0;filter:alpha(opacity=0)}.edd-add-to-cart[data-edd-loading] .edd-loading,.edd-discount-loader.edd-loading{opacity:1;filter:alpha(opacity=100)}.edd-cart-added-alert{color:#567622;display:block;position:absolute}.edd_form input.edd-input.required,.edd_form select.edd-select.required{color:#000}body.edd_receipt_page{background-color:#fff;color:#141412;margin:0;font-family:Helvetica,sans-serif;font-size:12px}body.edd_receipt_page:before{position:relative}body.edd_receipt_page #edd_receipt_wrapper{width:660px;margin:0 auto;padding:50px 0}body.edd_receipt_page table{display:table;width:100%;border-bottom:1px solid #ededed;border-collapse:collapse;border-spacing:0;font-size:14px;line-height:2;margin:0 0 20px}body.edd_receipt_page td,body.edd_receipt_page th{display:table-cell;text-align:left;border-top:1px solid #ededed;padding:6px 10px;font-weight:400}body.edd_receipt_page th{font-weight:700;text-transform:uppercase}body.edd_receipt_page h3{font-size:22px;margin:40px 0 5px;clear:both;display:block;font-weight:700}body.edd_receipt_page li{list-style:none}table#edd_purchase_receipt,table#edd_purchase_receipt_products{width:100%}table#edd_purchase_receipt_products td,table#edd_purchase_receipt_products th,table#edd_purchase_receipt td,table#edd_purchase_receipt th{text-align:left}table#edd_purchase_receipt .edd_receipt_payment_status.cancelled,table#edd_purchase_receipt .edd_receipt_payment_status.failed,table#edd_purchase_receipt .edd_receipt_payment_status.pending,table#edd_purchase_receipt .edd_receipt_payment_status.revoked{color:#f73f2e}table#edd_purchase_receipt_products li{list-style:none;margin:0 0 8px 10px}table#edd_purchase_receipt_products ul.edd_purchase_receipt_files,table#edd_purchase_receipt ul{margin:0;padding:0}table#edd_purchase_receipt li.edd_download_file{list-style:none;margin:0 0 8px}table#edd_purchase_receipt_products .edd_purchase_receipt_product_notes{font-style:italic}table#edd_purchase_receipt_products .edd_purchase_receipt_product_name{font-weight:700}table#edd_purchase_receipt_products .edd_bundled_product_name{font-style:italic;font-weight:700}#edd_user_history{text-align:left;width:100%;border-top:1px solid #f0f0f0;border-bottom:none}#edd_user_history td,#edd_user_history th{text-align:left;padding:3px 5px;border-bottom:1px solid #f0f0f0;border-top:none}#edd_user_history th{font-weight:700;background:#f5f5f5}#edd_user_history td{line-height:25px;vertical-align:middle}#edd_user_history .edd_purchase_status.cancelled,#edd_user_history .edd_purchase_status.failed,#edd_user_history .edd_purchase_status.pending,#edd_user_history .edd_purchase_status.revoked{color:#f73f2e}#edd_login_form legend,#edd_register_form legend{font-size:120%;margin-bottom:1em}#edd_login_form fieldset,#edd_register_form fieldset{border:none}#edd_login_form .edd-input,#edd_register_form .edd-input{box-sizing:border-box}#edd_login_form label,#edd_register_form label{cursor:pointer}#edd_profile_editor_form p{margin-bottom:8px}#edd_profile_editor_form label{display:inline-block}#edd_profile_editor_form .edd-profile-emails{list-style-type:none;display:inline-table;margin-left:0;margin-bottom:0}#edd_profile_editor_form .edd-profile-email{width:auto}#edd_profile_editor_form .edd-profile-email .actions{display:none}#edd_profile_editor_form .edd-profile-email:hover>span{display:inline-block}.edd_added_to_cart_alert{padding:5px;font-size:14px;border:1px solid #046a9e;background:#9ecce2;color:#333;margin:8px 0}.edd_added_to_cart_alert a.edd_alert_checkout_link{color:#000!important}input.edd_submit_plain{background:none!important;border:none!important;padding:0!important;display:inline;cursor:pointer}.single-download .edd_download_purchase_form{margin-bottom:1.387em}.edd_download_purchase_form .edd_download_quantity_wrapper{margin:0 0 .5em}.edd_download_purchase_form .edd_download_quantity_wrapper .edd-item-quantity{width:75px}.edd_download_purchase_form .edd_price_options{margin:0 0 15px}.edd_download_purchase_form .edd_price_options ul{margin:0;padding:0;list-style:none}.edd_download_purchase_form .edd_price_options li{display:block;padding:0;margin:0}.edd_download_purchase_form .edd_price_options span{display:inline;padding:0;margin:0}.edd_download_purchase_form .edd_price_options .edd_download_quantity_wrapper{padding-left:18px}.edd_download_purchase_form .edd_price_options .edd_download_quantity_wrapper *{font-size:80%}.edd_download_purchase_form .edd_price_options input.edd-item-quantity{display:inline;width:50px;max-width:90%}#edd-purchase-button,.edd-submit,[type=submit].edd-submit{display:inline-block;padding:6px 12px;margin:0;font-size:14px;font-weight:400;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid #ccc;border-radius:4px;box-shadow:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.edd-submit.button:focus,[type=submit].edd-submit:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.edd-submit.button:active{background-image:none;outline:0;box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.edd-submit.plain{padding:0;border:none;border-radius:0}.edd-submit.button,.edd-submit.button.gray,.edd-submit.button:visited{color:#333;background:#f0f0f0;border-color:#ccc}.edd-submit.button.gray:active,.edd-submit.button.gray:focus,.edd-submit.button.gray:hover,.edd-submit.button:active,.edd-submit.button:focus,.edd-submit.button:hover{color:#333;background:#ebebeb;border-color:#adadad}.edd-submit.button.gray:active{background-image:none}.edd-submit.button.white{color:#333;background:#fff;border-color:#ccc}.edd-submit.button.white:active,.edd-submit.button.white:focus,.edd-submit.button.white:hover{color:#333;background:#ebebeb;border-color:#adadad}.edd-submit.button.white:active{background-image:none}.edd-submit.button.blue{color:#fff;background:#428bca;border-color:#357ebd}.edd-submit.button.blue.active,.edd-submit.button.blue:focus,.edd-submit.button.blue:hover{color:#fff;background:#3276b1;border-color:#285e8e}.edd-submit.button.blue.active{background-image:none}.edd-submit.button.red{color:#fff;background:#d9534f;border-color:#d43f3a}.edd-submit.button.red:active,.edd-submit.button.red:focus,.edd-submit.button.red:hover{color:#fff;background:#d2322d;border-color:#ac2925}.edd-submit.button.red:active{background-image:none}.edd-submit.button.green{color:#fff;background:#5cb85c;border-color:#4cae4c}.edd-submit.button.green:active,.edd-submit.button.green:focus,.edd-submit.button.green:hover{color:#fff;background:#47a447;border-color:#398439}.edd-submit.button.green:active{background-image:none}.edd-submit.button.yellow{color:#fff;background:#f0ad4e;border-color:#eea236}.edd-submit.button.yellow:active,.edd-submit.button.yellow:focus,.edd-submit.button.yellow:hover{color:#fff;background:#ed9c28;border-color:#d58512}.edd-submit.button.yellow:active{background-image:none}.edd-submit.button.orange{color:#fff;background:#ed9c28;border-color:#e3921e}.edd-submit.button.orange:active,.edd-submit.button.orange:focus,.edd-submit.button.orange:hover{color:#fff;background:#e59016;border-color:#d58512}.edd-submit.button.orange:active{background-image:none}.edd-submit.button.dark-gray{color:#fff;background:#363636;border-color:#222}.edd-submit.button.dark-gray:active,.edd-submit.button.dark-gray:focus,.edd-submit.button.dark-gray:hover{color:#fff;background:#333;border-color:#adadad}.edd-submit.button.dark-gray:active{background-image:none}.edd_downloads_list{display:-ms-grid;display:grid;grid-column-gap:20px;grid-row-gap:40px}.edd_downloads_list:after{content:"";display:table;clear:both}.edd_download{float:left}.edd_download_columns_1 .edd_download{width:100%}.edd_download_columns_2 .edd_download{width:50%}.edd_download_columns_0 .edd_download,.edd_download_columns_3 .edd_download{width:33%}.edd_download_columns_4 .edd_download{width:25%}.edd_download_columns_5 .edd_download{width:20%}.edd_download_columns_6 .edd_download{width:16.6%}.edd_download_inner{padding:0 8px 8px;margin:0 0 10px}.edd_download_columns_2 .edd_download:nth-child(odd),.edd_download_columns_3 .edd_download:nth-child(3n+1),.edd_download_columns_4 .edd_download:nth-child(4n+1),.edd_download_columns_5 .edd_download:nth-child(5n+1),.edd_download_columns_6 .edd_download:nth-child(6n+1){clear:left}.edd_download_image{max-width:100%}.edd_download .edd_price{margin-bottom:10px}@media(min-width:768px){.edd_downloads_list:not(.edd_download_columns_1){-ms-grid-columns:(1fr)[2];grid-template-columns:repeat(2,1fr)}}@media(min-width:1200px){.edd_downloads_list.edd_download_columns_2{-ms-grid-columns:(1fr)[2];grid-template-columns:repeat(2,1fr)}.edd_downloads_list.edd_download_columns_3{-ms-grid-columns:(1fr)[3];grid-template-columns:repeat(3,1fr)}.edd_downloads_list.edd_download_columns_4{-ms-grid-columns:(1fr)[4];grid-template-columns:repeat(4,1fr)}.edd_downloads_list.edd_download_columns_5{-ms-grid-columns:(1fr)[5];grid-template-columns:repeat(5,1fr)}.edd_downloads_list.edd_download_columns_6{-ms-grid-columns:(1fr)[6];grid-template-columns:repeat(6,1fr)}}@supports(display:grid){.edd_downloads_list .edd_download{width:auto}.edd_download_inner{padding:0;margin:0}}.edd-hide-on-empty.cart-empty{display:none}.edd-cart-ajax{margin:0 8px 0 4px;position:relative;top:2px;background:none;border:none;padding:0}.edd-cart-number-of-items{font-style:italic;color:grey}.edd-cart-meta.edd_subtotal{font-weight:700;font-style:italic}.edd-cart-meta.edd_cart_tax{font-size:1em;font-style:italic}.edd-cart-meta.edd_cart_tax:before{font-style:normal}.edd-cart-meta.edd_total{font-weight:700}.edd-cart-meta{padding:2px 5px}.edd-cart-meta.edd_subtotal,.edd-cart-meta.edd_total{background-color:#f9f9f9}.edd_errors:not(.edd-alert){border-radius:2px;border:1px solid #e6db55;margin:0 0 21px;background:#ffffe0;color:#333}.edd_error{padding:10px}p.edd_error{margin:0!important}.edd_success:not(.edd-alert){border-radius:2px;border:1px solid #b3ce89;margin:20px 0;background:#d5eab3;color:#567622;padding:6px 8px;box-shadow:inset 0 1px 0 hsla(0,0%,100%,.7)}.edd-alert{border-radius:2px;margin-bottom:20px;padding:10px;border:1px solid transparent;vertical-align:middle}.edd-alert p{padding:0}.edd-alert p:not(:last-child){margin-bottom:5px}.edd-alert p:last-child{margin-bottom:0}.edd-alert-error{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.edd-alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.edd-alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.edd-alert-warn{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} \ No newline at end of file diff --git a/assets/css/frontend/style.scss b/assets/css/frontend/style.scss new file mode 100644 index 00000000000..37d9d6c88eb --- /dev/null +++ b/assets/css/frontend/style.scss @@ -0,0 +1,1059 @@ +/** + * Easy Digital Downloads Styles + * + * @package EDD + * @subpackage CSS + * @copyright Copyright (c) 2015, Pippin Williamson + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License +*/ +@charset "UTF-8"; + +/* Base styles for SVG icons. */ + +.edd-icon { + display: inline-block; + fill: currentColor; + position: relative; /* Align more nicely with capital letters */ + vertical-align: middle; +} + +.edd-icon-spin { + display: inline-block; + animation: edd-icon-spin 2s infinite linear; +} + +@keyframes edd-icon-spin { + 0% { + transform: rotate(0deg) + } + 100% { + transform: rotate(359deg) + } +} + + +/* =Checkout Form +-------------------------------------------------------------- */ +.edd_clearfix:after { + display: block; + visibility: hidden; + float: none; + clear: both; + text-indent: -9999px; + content: "."; +} + +/* Cart Contents */ +#edd_checkout_cart { + text-align: left; + width: 100%; + border: none; + margin: 0 0 21px; + table-layout: auto; +} +#edd_checkout_cart th, +#edd_checkout_cart td { + text-align: left; + border: 1px solid #eee; + color: #666; + padding: 0.5em 1.387em; +} +#edd_checkout_cart .edd_cart_header_row th { + background: #fafafa; + padding: 1.387em; +} +#edd_checkout_cart .edd_cart_tax_row th, +#edd_checkout_cart .edd_cart_discount_row th { + background: none; +} +#edd_checkout_cart th { + font-weight: bold; +} +#edd_checkout_cart td { + line-height: 25px; + vertical-align: middle; + background: #fff; +} +#edd_checkout_cart th.edd_cart_actions, +#edd_checkout_cart td.edd_cart_actions, +#edd_checkout_cart th:last-child, +#edd_checkout_cart td:last-child, +#edd_checkout_cart th.edd_cart_total { + text-align: right; +} +#edd_checkout_cart td img { + float: left; + margin: 0 8px 0 0; + background: none; + padding: 0; + border: none; +} +#edd_checkout_cart input.edd-item-quantity { + width: 3em; + padding: 2px; +} +#edd_checkout_cart .edd_discount { + display: inline-block; + margin-left: 5px; +} +.edd_discount_remove { + display: inline-block; + width: 14px; + height: 14px; + background: url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22currentColor%22%3E%0A%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M9.75%209.75l4.5%204.5m0-4.5l-4.5%204.5M21%2012a9%209%200%2011-18%200%209%209%200%200118%200z%22%20%2F%3E%0A%3C%2Fsvg%3E%0A) 0 0 no-repeat; + position: relative; + opacity: .6; + + &:hover { + opacity: 1; + } +} + +#edd_checkout_cart br { + display: none; +} +#edd_checkout_cart a.edd-cart-saving-button { + font-weight: normal; + text-decoration: none; +} + +/* Checkout Fields */ +#edd_checkout_form_wrap legend { + display: block; + font-size: 120%; + line-height: 1; + font-weight: bold; + width: 100%; + margin: 0 0 1rem; + padding: 0; +} + +#edd_checkout_form_wrap label { + font-weight: bold; + display: block; + position: relative; + line-height: 100%; + font-size: 95%; + margin: 0 0 5px; +} +#edd_checkout_form_wrap span.edd-description { + color: #666; + font-size: 80%; + display: block; + margin: 0 0 5px; +} +#edd_checkout_form_wrap input.edd-input, +#edd_checkout_form_wrap textarea.edd-input { + display: inline-block; + width: 70%; +} +#edd_checkout_form_wrap select.edd-select { + display: block; + width: 60%; +} +#edd_checkout_form_wrap select.edd-select.edd-select-small { + display: inline; + width: auto; +} +#edd_checkout_form_wrap input.edd-input.error, +#edd_checkout_form_wrap textarea.edd-input.error { + border-color: #c4554e; +} +#edd_checkout_form_wrap > p { + margin: 0 0 21px; +} +#edd_checkout_form_wrap span.edd-required-indicator { + color: #b94a48; + display: inline; +} +#edd_checkout_form_wrap textarea, +#edd_checkout_form_wrap input[type="text"], +#edd_checkout_form_wrap input[type="email"], +#edd_checkout_form_wrap input[type="password"], +#edd_checkout_form_wrap input[type="tel"] { + padding: 4px 6px; +} +#edd_checkout_form_wrap input[type="radio"] { + border: none; + margin-right: 5px; +} +#edd_checkout_form_wrap input[type="checkbox"] { + display: inline-block; + margin: 0 5px 0 0; +} +#edd_checkout_form_wrap input[type="checkbox"] + label, +#edd_checkout_form_wrap input[type="checkbox"] + label:after { + display: inline; +} +#edd_checkout_form_wrap .edd-payment-icons { + display: -ms-flexbox; + display: flex; + margin: 0 0 8px; +} +#edd_checkout_form_wrap .edd-payment-icons img.payment-icon { + max-height: 32px; +} +#edd_checkout_form_wrap .edd-payment-icons .payment-icon { + margin: 0 10px 0 0; +} +#edd_checkout_form_wrap #edd-payment-mode-wrap label { + display: inline-block; + margin: 0 20px 0 0; +} +#edd_checkout_form_wrap #edd-payment-mode-wrap .edd-payment-mode-label { + font-weight: bold; + display: inline-block; + position: relative; + margin-bottom: 5px; +} +#edd_checkout_form_wrap fieldset { + border: 1px solid #eee; + padding: 1.387em; + margin: 0 0 21px; +} +#edd_checkout_form_wrap #edd_purchase_submit, +#edd_checkout_form_wrap #edd_discount_code, +#edd_checkout_form_wrap #edd_register_account_fields { + padding: 0; + border: none; +} +#edd_checkout_form_wrap fieldset fieldset { + margin: 0; + border: none; + padding: 0; +} +#edd_checkout_form_wrap #edd-login-account-wrap, +#edd_checkout_form_wrap #edd-new-account-wrap, +#edd_checkout_form_wrap #edd_show_discount, +#edd_checkout_form_wrap .edd-cart-adjustment, +#edd_checkout_form_wrap #edd_final_total_wrap { + background: #fafafa; + color: #666; + padding: 0.5em 1.387em; +} +#edd_checkout_form_wrap #edd-discount-code-wrap, +#edd_checkout_form_wrap #edd_final_total_wrap, +#edd_checkout_form_wrap #edd_show_discount { + border: 1px solid #eee; +} +#edd_checkout_form_wrap .edd-cart-adjustment { + padding: 1.387em; +} +#edd_checkout_form_wrap .edd-cart-adjustment input.edd-input, +#edd_checkout_form_wrap .edd-cart-adjustment input.edd-submit { + display: inline-block; +} +#edd_checkout_form_wrap .edd-cart-adjustment input.edd-submit { + padding: 3px 12px; + margin-bottom: 2px; +} +#edd_checkout_form_wrap #edd-discount-error-wrap { + width: 100%; + display: inline-block; + margin: 1em 0 0; +} +#edd_checkout_form_wrap #edd-new-account-wrap, +#edd_checkout_form_wrap #edd-login-account-wrap { + margin: -1.387em -1.387em 21px; + border-left: none; + border-right: none; + border-top: none; +} +#edd_checkout_form_wrap #edd_payment_mode_select { + margin-bottom: 21px; +} +#edd_checkout_form_wrap fieldset#edd_register_fields #edd_checkout_user_info { + margin-bottom: 21px; +} +#edd_checkout_form_wrap fieldset#edd_register_account_fields legend { + padding-top: 11px; +} +#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_register_password, +#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_login_password { + margin: 0; +} +#edd_checkout_form_wrap fieldset#edd_cc_fields legend { + border: none; + padding: 0; +} +#edd_checkout_form_wrap fieldset p:last-child { + margin-bottom: 0; +} +#edd_checkout_form_wrap fieldset#edd_cc_fields #edd-card-number-wrap { + margin-top: 5px; +} +#edd_checkout_form_wrap #edd_purchase_final_total { + margin: 21px 0; +} +#edd_checkout_form_wrap #edd_purchase_final_total p { + margin: 0; +} +#edd_secure_site_wrapper { + padding: 4px 4px 4px 0; + font-weight: bold; +} +#edd_secure_site_wrapper span { + vertical-align: middle; +} +#edd_checkout_form_wrap input.edd-input.card-number.valid { + background-image: url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22green%22%3E%0A%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M4.5%2012.75l6%206%209-13.5%22%20%2F%3E%0A%3C%2Fsvg%3E%0A); + background-repeat: no-repeat; + background-position: 98% 50%; +} +#edd_checkout_form_wrap span.exp-divider { + display: inline; +} +#edd_checkout_form_wrap span.card-type { + position: absolute; + top: 0; + right: 0; +} +#edd_checkout_form_wrap span.card-type.off { + display: none; +} +#edd_checkout_form_wrap .edd-cart-ajax { + box-shadow: none; +} +.edd-amazon-profile-wrapper { + font-size: 12px; +} +.edd-amazon-profile-name { + font-weight: 600; +} +.edd-amazon-logout { + font-size: 10px; + line-height: 12px; +} +.edd-amazon-logout a { + cursor: pointer; +} +#edd-amazon-wallet-box, +#edd-amazon-address-box { + height: 228px; + width: 350px; +} +#edd-amazon-address-box { + margin-bottom: 15px; +} + +/* Left align the loading on taxes */ +.edd_cart_tax .edd-loading-ajax.edd-loading { + margin: 0px 0px 0px auto; + display: inline-block; +} + +/* Desktop and tablet */ +@media only screen and (min-width: 768px) { + #edd-amazon-address-box, + #edd-amazon-wallet-box { + width: 100%; + height: 228px; + } +} + +/* =Ajax Add To Cart Button +-------------------------------------------------------------- */ +.edd_purchase_submit_wrapper { + position: relative; +} +.edd_purchase_submit_wrapper a.edd-add-to-cart { + text-decoration: none; + display: none; + position: relative; + overflow: hidden; +} +.edd_purchase_submit_wrapper .edd-cart-ajax { + display: none; + position: relative; + left: -35px; +} +.edd-submit.button.edd-ajax-loading { + padding-right: 30px; +} +.edd-add-to-cart .edd-add-to-cart-label { + opacity: 1; + filter: alpha(opacity=100); +} +.edd-loading, +.edd-loading:after { + border-radius: 50%; + display: block; + width: 1.5em; + height: 1.5em; +} +.edd-loading { + animation: edd-spinning 1.1s infinite linear; + border-top: 0.2em solid rgba(255, 255, 255, 0.2); + border-right: 0.2em solid rgba(255, 255, 255, 0.2); + border-bottom: 0.2em solid rgba(255, 255, 255, 0.2); + border-left: 0.2em solid #fff; + font-size: 0.75em; + position: absolute; + left: calc(50% - 0.75em); + top: calc(50% - 0.75em); + opacity: 0; + filter: alpha(opacity=0); + transform: translateZ(0); +} +a.edd-add-to-cart.white .edd-loading, +.edd-discount-loader.edd-loading, +.edd-loading-ajax.edd-loading { + border-top-color: rgba(0, 0, 0, 0.2); + border-right-color: rgba(0, 0, 0, 0.2); + border-bottom-color: rgba(0, 0, 0, 0.2); + border-left-color: #000; +} +.edd-loading-ajax.edd-loading { + display: inline-block; + position: relative; + top: 0; + left: 0.25em; + vertical-align: middle; +} + +#edd_checkout_form_wrap .edd-cart-adjustment .edd-apply-discount.edd-submit { + display: inline-block; +} +.edd-discount-loader.edd-loading { + display: inline-block; + position: relative; + left: auto; + vertical-align: middle; + width: 1.25em; + height: 1.25em; +} + +.edd-loading-ajax.edd-loading { + opacity: 1; +} + +@keyframes edd-spinning { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +a.edd-add-to-cart .edd-add-to-cart-label, +.edd-loading { + transition: .1s opacity !important; +} +.edd-add-to-cart[data-edd-loading] .edd-add-to-cart-label { + opacity: 0; + filter: alpha(opacity=0); +} +.edd-add-to-cart[data-edd-loading] .edd-loading, +.edd-discount-loader.edd-loading { + opacity: 1; + filter: alpha(opacity=100); +} +.edd-cart-added-alert { + color: #567622; + display: block; + position: absolute; +} + + +/* =Theme Specific styling +-------------------------------------------------------------- */ + +/* Twenty Twelve */ +.edd_form input.edd-input.required, +.edd_form select.edd-select.required { + color: #000; +} + + +/* =Receipt Page +-------------------------------------------------------------- */ +body.edd_receipt_page { + background-color: #fff; + color: #141412; + margin: 0; + font-family: Helvetica, sans-serif; + font-size: 12px; +} +body.edd_receipt_page:before { + position: relative; +} +body.edd_receipt_page #edd_receipt_wrapper { + width: 660px; + margin: 0 auto; + padding: 50px 0; +} +body.edd_receipt_page table { + display: table; + width: 100%; + border-bottom: 1px solid #ededed; + border-collapse: collapse; + border-spacing: 0; + font-size: 14px; + line-height: 2; + margin: 0 0 20px; +} +body.edd_receipt_page td, +body.edd_receipt_page th { + display: table-cell; + text-align: left; + border-top: 1px solid #ededed; + padding: 6px 10px; + font-weight: normal; +} +body.edd_receipt_page th { + font-weight: bold; + text-transform: uppercase; +} +body.edd_receipt_page h3 { + font-size: 22px; + margin: 40px 0 5px; + clear: both; + display: block; + font-weight: bold; +} +body.edd_receipt_page li { + list-style: none; +} + + +/* =Purchase Summary Tables +-------------------------------------------------------------- */ +table#edd_purchase_receipt_products, +table#edd_purchase_receipt { + width: 100%; +} +table#edd_purchase_receipt_products td, +table#edd_purchase_receipt_products th, +table#edd_purchase_receipt td, +table#edd_purchase_receipt th { + text-align: left; +} +table#edd_purchase_receipt .edd_receipt_payment_status.pending, +table#edd_purchase_receipt .edd_receipt_payment_status.cancelled, +table#edd_purchase_receipt .edd_receipt_payment_status.revoked, +table#edd_purchase_receipt .edd_receipt_payment_status.failed { + color: #f73f2e; +} +table#edd_purchase_receipt_products li { + list-style: none; + margin: 0 0 8px 10px; +} +table#edd_purchase_receipt ul, +table#edd_purchase_receipt_products ul.edd_purchase_receipt_files { + margin: 0; + padding: 0; +} +table#edd_purchase_receipt li.edd_download_file { + list-style: none; + margin: 0 0 8px 0; +} +table#edd_purchase_receipt_products .edd_purchase_receipt_product_notes { + font-style: italic; +} +table#edd_purchase_receipt_products .edd_purchase_receipt_product_name { + font-weight: bold; +} +table#edd_purchase_receipt_products .edd_bundled_product_name { + font-style: italic; + font-weight: bold; +} + + +/* =Purchase History +-------------------------------------------------------------- */ +#edd_user_history { + text-align: left; + width: 100%; + border-top: 1px solid #f0f0f0; + border-bottom: none; +} +#edd_user_history th, +#edd_user_history td { + text-align: left; + padding: 3px 5px; + border-bottom: 1px solid #f0f0f0; + border-top: none; +} +#edd_user_history th { + font-weight: bold; + background: #f5f5f5; +} +#edd_user_history td { + line-height: 25px; + vertical-align: middle; +} +#edd_user_history .edd_purchase_status.revoked, +#edd_user_history .edd_purchase_status.failed, +#edd_user_history .edd_purchase_status.cancelled, +#edd_user_history .edd_purchase_status.pending { + color: #f73f2e; +} + + +/* =Registration / login Form +-------------------------------------------------------------- */ +#edd_register_form legend, +#edd_login_form legend { + font-size: 120%; + margin-bottom: 1em; +} + +#edd_register_form fieldset, +#edd_login_form fieldset { + border: none; +} + +#edd_register_form .edd-input, +#edd_login_form .edd-input { + box-sizing: border-box; +} + +#edd_register_form label, +#edd_login_form label { + cursor: pointer; +} + +/* =Profile Form +-------------------------------------------------------------- */ +#edd_profile_editor_form p { + margin-bottom: 8px; +} +#edd_profile_editor_form label { + display: inline-block; +} +#edd_profile_editor_form .edd-profile-emails { + list-style-type: none; + display: inline-table; + margin-left: 0; + margin-bottom: 0; +} +#edd_profile_editor_form .edd-profile-email { + width: auto; +} +#edd_profile_editor_form .edd-profile-email .actions { + display: none; +} +#edd_profile_editor_form .edd-profile-email:hover > span { + display: inline-block; +} + + + +/* =Alerts +-------------------------------------------------------------- */ +.edd_added_to_cart_alert { + padding: 5px; + font-size: 14px; + border: 1px solid #046a9e; + background: #9ecce2; + color: #333; + margin: 8px 0; +} +.edd_added_to_cart_alert a.edd_alert_checkout_link { + color: #000 !important; +} + + +/* =Purchase buttons +-------------------------------------------------------------- */ +input.edd_submit_plain { + background: none !important; + border: none !important; + padding: 0 !important; + display: inline; + cursor: pointer; +} +.single-download .edd_download_purchase_form { + margin-bottom: 1.387em; +} +.edd_download_purchase_form .edd_download_quantity_wrapper { + margin: 0 0 0.5em; +} +.edd_download_purchase_form .edd_download_quantity_wrapper .edd-item-quantity { + width: 75px; +} +.edd_download_purchase_form .edd_price_options { + margin: 0 0 15px; +} +.edd_download_purchase_form .edd_price_options ul { + margin: 0; + padding: 0; + list-style: none; +} +.edd_download_purchase_form .edd_price_options li { + display: block; + padding: 0; + margin: 0; +} +.edd_download_purchase_form .edd_price_options span { + display: inline; + padding: 0; + margin: 0; +} +.edd_download_purchase_form .edd_price_options .edd_download_quantity_wrapper { + padding-left: 18px +} +.edd_download_purchase_form .edd_price_options .edd_download_quantity_wrapper * { + font-size: 80% +} +.edd_download_purchase_form .edd_price_options input.edd-item-quantity { + display: inline; + width: 50px; + max-width: 90%; +} +.edd-submit, +#edd-purchase-button, +[type="submit"].edd-submit { + display: inline-block; + padding: 6px 12px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.428571429; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + border: 1px solid #cccccc; + border-radius: 4px; + box-shadow: none; + user-select: none; +} +.edd-submit.button:focus, +[type="submit"].edd-submit:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.edd-submit.button:active { + background-image: none; + outline: 0; + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.edd-submit.plain { + padding: 0; + border: none; + border-radius: 0; +} + +/** Gray (Default) */ +.edd-submit.button, +.edd-submit.button, +.edd-submit.button:visited, +.edd-submit.button, +.edd-submit.button.gray { + color: #333333; + background: #f0f0f0; + border-color: #cccccc; +} +.edd-submit.button:hover, +.edd-submit.button:focus, +.edd-submit.button:active, +.edd-submit.button.gray:hover, +.edd-submit.button.gray:focus, +.edd-submit.button.gray:active { + color: #333333; + background: #ebebeb; + border-color: #adadad; +} +.edd-submit.button.gray:active { + background-image: none; +} + +/** White */ +.edd-submit.button.white { + color: #333333; + background: #ffffff; + border-color: #cccccc; +} +.edd-submit.button.white:hover, +.edd-submit.button.white:focus, +.edd-submit.button.white:active { + color: #333333; + background: #ebebeb; + border-color: #adadad; +} +.edd-submit.button.white:active { + background-image: none; +} + +/** Blue */ +.edd-submit.button.blue { + color: #ffffff; + background: #428bca; + border-color: #357ebd; +} +.edd-submit.button.blue:hover, +.edd-submit.button.blue:focus, +.edd-submit.button.blue.active { + color: #ffffff; + background: #3276b1; + border-color: #285e8e; +} +.edd-submit.button.blue.active { + background-image: none; +} + +/** Red */ +.edd-submit.button.red { + color: #ffffff; + background: #d9534f; + border-color: #d43f3a; +} +.edd-submit.button.red:hover, +.edd-submit.button.red:focus, +.edd-submit.button.red:active { + color: #ffffff; + background: #d2322d; + border-color: #ac2925; +} +.edd-submit.button.red:active { + background-image: none; +} + +/** Green */ +.edd-submit.button.green { + color: #ffffff; + background: #5cb85c; + border-color: #4cae4c; +} +.edd-submit.button.green:hover, +.edd-submit.button.green:focus, +.edd-submit.button.green:active { + color: #ffffff; + background: #47a447; + border-color: #398439; +} +.edd-submit.button.green:active { + background-image: none; +} + +/** Yellow */ +.edd-submit.button.yellow { + color: #ffffff; + background: #f0ad4e; + border-color: #eea236; +} +.edd-submit.button.yellow:hover, +.edd-submit.button.yellow:focus, +.edd-submit.button.yellow:active { + color: #ffffff; + background: #ed9c28; + border-color: #d58512; +} +.edd-submit.button.yellow:active { + background-image: none; +} + +/** Orange */ +.edd-submit.button.orange { + color: #ffffff; + background: #ed9c28; + border-color: #e3921e; +} +.edd-submit.button.orange:hover, +.edd-submit.button.orange:focus, +.edd-submit.button.orange:active { + color: #ffffff; + background: #e59016; + border-color: #d58512; +} +.edd-submit.button.orange:active { + background-image: none; +} + +/** Dark Gray */ +.edd-submit.button.dark-gray { + color: #fff; + background: #363636; + border-color: #222; +} +.edd-submit.button.dark-gray:hover, +.edd-submit.button.dark-gray:focus, +.edd-submit.button.dark-gray:active { + color: #fff; + background: #333; + border-color: #adadad; +} +.edd-submit.button.dark-gray:active { + background-image: none; +} + + +/* =Downloads Shortcode +-------------------------------------------------------------- */ +.edd_downloads_list { + display: grid; + grid-column-gap: 20px; + grid-row-gap: 40px; +} +.edd_downloads_list:after { + content: ""; + display: table; + clear: both; +} +.edd_download { + float: left; +} +.edd_download_columns_1 .edd_download { width: 100%; } +.edd_download_columns_2 .edd_download { width: 50%; } +.edd_download_columns_0 .edd_download, +.edd_download_columns_3 .edd_download { width: 33%; } +.edd_download_columns_4 .edd_download { width: 25%; } +.edd_download_columns_5 .edd_download { width: 20%; } +.edd_download_columns_6 .edd_download { width: 16.6%; } +.edd_download_inner { + padding: 0 8px 8px; + margin: 0 0 10px; +} +.edd_download_columns_2 .edd_download:nth-child(2n+1), +.edd_download_columns_3 .edd_download:nth-child(3n+1), +.edd_download_columns_4 .edd_download:nth-child(4n+1), +.edd_download_columns_5 .edd_download:nth-child(5n+1), +.edd_download_columns_6 .edd_download:nth-child(6n+1) { + clear: left; +} +.edd_download_image { + max-width: 100%; +} +.edd_download .edd_price { + margin-bottom: 10px; +} +@media (min-width: 768px) { + .edd_downloads_list:not(.edd_download_columns_1) { + grid-template-columns: repeat(2, 1fr); + } +} +@media (min-width: 1200px) { + .edd_downloads_list.edd_download_columns_2 { grid-template-columns: repeat(2, 1fr); } + .edd_downloads_list.edd_download_columns_3 { grid-template-columns: repeat(3, 1fr); } + .edd_downloads_list.edd_download_columns_4 { grid-template-columns: repeat(4, 1fr); } + .edd_downloads_list.edd_download_columns_5 { grid-template-columns: repeat(5, 1fr); } + .edd_downloads_list.edd_download_columns_6 { grid-template-columns: repeat(6, 1fr); } +} +@supports (display: grid) { + .edd_downloads_list .edd_download { + width: auto; + } + .edd_download_inner { + padding: 0; + margin: 0; + } +} + +/* =Misc styles +-------------------------------------------------------------- */ +.edd-hide-on-empty.cart-empty { + display: none; +} +.edd-cart-ajax { + margin: 0 8px 0 4px; + position: relative; + top: 2px; + background: none; + border: none; + padding: 0; +} +.edd-cart-number-of-items { + font-style: italic; + color: grey; +} +.edd-cart-meta.edd_subtotal { + font-weight: bold; + font-style: italic; +} +.edd-cart-meta.edd_cart_tax { + font-size: 1em; + font-style: italic; +} + +/** Since this is a LI, make sure to not italicize any list item images */ +.edd-cart-meta.edd_cart_tax::before { + font-style: normal; +} + +.edd-cart-meta.edd_total { + font-weight: bold; +} +.edd-cart-meta { + padding: 2px 5px; +} +.edd-cart-meta.edd_subtotal, +.edd-cart-meta.edd_total { + background-color: #f9f9f9; +} + + +/** Old Error Styles */ +/* =Error styles +-------------------------------------------------------------- */ +.edd_errors:not(.edd-alert) { + border-radius: 2px; + border: 1px solid #E6DB55; + margin: 0 0 21px; + background: #FFFFE0; + color: #333; +} +.edd_error { + padding: 10px; +} +p.edd_error { + margin: 0 !important; +} + + +/* =Success Message styles +-------------------------------------------------------------- */ +.edd_success:not(.edd-alert) { + border-radius: 2px; + border: 1px solid #b3ce89; + margin: 20px 0; + background: #d5eab3; + color: #567622; + padding: 6px 8px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7); +} + +/** End old Error */ + +/** Usage Is as Follows */ +/*
This is your error message
*/ +/* Replace edd-error with the class of your choice */ + +/* Alert Styles */ +.edd-alert { + border-radius: 2px; + margin-bottom: 20px; + padding: 10px; + border: 1px solid transparent; + vertical-align: middle; +} +.edd-alert p { + padding: 0; +} +.edd-alert p:not(:last-child) { + margin-bottom: 5px; +} +.edd-alert p:last-child { + margin-bottom: 0; +} +.edd-alert-error { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.edd-alert-success { + background-color: #dff0d8; + border-color: #d6e9c6; + color:#3c763d; +} +.edd-alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.edd-alert-warn { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} diff --git a/assets/css/jquery-ui-fresh-rtl.min.css b/assets/css/jquery-ui-fresh-rtl.min.css new file mode 100644 index 00000000000..87836669ad3 --- /dev/null +++ b/assets/css/jquery-ui-fresh-rtl.min.css @@ -0,0 +1 @@ +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,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{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}/*\*/* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}/**/.ui-helper-zfix{width:100%;height:100%;top:0;right:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;right:0;width:100%;height:100%}.ui-widget{font-family:sans-serif;font-size:12px}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:sans-serif;font-size:1em}.ui-widget-content{border:1px solid #dfdfdf;background:#fff;color:#333}.ui-widget-header{border:1px solid #dfdfdf;color:#333;font-weight:bold;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,right top,right bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec)}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #dfdfdf;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,right top,right bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec);font-weight:normal;color:#333}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#333;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{border:1px solid #ccc;background-color:#ececec;background-image:-ms-linear-gradient(top,#ececec,#f9f9f9);background-image:-moz-linear-gradient(top,#ececec,#f9f9f9);background-image:-o-linear-gradient(top,#ececec,#f9f9f9);background-image:-webkit-gradient(linear,right top,right bottom,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#ececec,#f9f9f9);background-image:linear-gradient(top,#ececec,#f9f9f9);font-weight:normal;color:#000}.ui-state-hover a,.ui-state-hover a:hover{color:#000;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #dfdfdf;background:#fff;font-weight:normal;color:#333}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#333;text-decoration:none}.ui-widget :active{outline:0}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #e6db55;background:#ffffe0;color:#333}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#333}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #c00;background:#ffebe8;color:#c00}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#c00}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#c00}.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);font-weight:normal}.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-icon{width:16px;height:16px;background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-content .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-header .ui-icon{background-image:url(../images/ui-icons_999999_256x240.png)}.ui-state-default .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-active .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(../images/ui-icons_21759b_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(../images/ui-icons_cc0000_256x240.png)}.ui-icon-carat-1-n{background-position:100% 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:100% -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:-64px -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:100% -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:-64px -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:100% -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:100% -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:100% -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:100% -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:100% -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:100% -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:100% -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-off{background-position:-96px -144px}.ui-icon-radio-on{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:100% -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{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:100% -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:100% -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:100% -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:100% -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-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topright:3px;-webkit-border-top-right-radius:3px;border-top-right-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topleft:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomright:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomleft:3px;-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px}.ui-widget-overlay{background:#000;opacity:.6;filter:Alpha(Opacity=60)}.ui-widget-shadow{box-shadow:0 0 16px rgba(0,0,0,0.3)}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;z-index:99999;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;right:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;right:0}.ui-resizable-e{cursor:e-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-se{cursor:sw-resize;width:12px;height:12px;left:1px;bottom:1px}.ui-resizable-sw{cursor:se-resize;width:9px;height:9px;right:-5px;bottom:-5px}.ui-resizable-nw{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-resizable-ne{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion{width:100%}.ui-accordion .ui-accordion-header{cursor:pointer;position:relative;margin-top:1px;zoom:1}.ui-accordion .ui-accordion-li-fix{display:inline}.ui-accordion .ui-accordion-header-active{border-bottom:0!important}.ui-accordion .ui-accordion-header a{display:block;font-size:1em;padding:.5em .7em .5em .5em}.ui-accordion-icons .ui-accordion-header a{padding-right:2.2em}.ui-accordion .ui-accordion-header .ui-icon{position:absolute;right:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none;zoom:1}.ui-accordion .ui-accordion-content-active{display:block}.ui-autocomplete{position:absolute;cursor:default}* html .ui-autocomplete{width:1px}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:right}.ui-menu .ui-menu{margin-top:-3px}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:right;clear:right;width:100%}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-button{display:inline-block;position:relative;padding:0;margin-left:.1em;text-decoration:none!important;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icons .ui-button-text{padding-right:2.1em;padding-left:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{right:50%;margin-right:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{right:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{left:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{left:.5em}.ui-buttonset{margin-left:7px}.ui-buttonset .ui-button{margin-right:0;margin-left:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-dialog{position:fixed;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:right;margin:.1em 0 .1em 16px}.ui-dialog .ui-dialog-titlebar-close{position:absolute;left:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:100%;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:right;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em .4em .5em 1em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:left}.ui-dialog .ui-dialog-buttonpane button{margin:.5em 0 .5em .4em;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;left:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-slider{position:relative;text-align:right}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:100% 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-right:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{right:0}.ui-slider-horizontal .ui-slider-range-max{left:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{right:-.3em;margin-right:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{right:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:right;position:relative;top:1px;margin:0 0 1px .2em;border-bottom:0!important;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:right;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-selected{margin-bottom:0;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:100%}.ui-tabs .ui-tabs-hide{display:none!important}.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{right:2px}.ui-datepicker .ui-datepicker-next{left:2px}.ui-datepicker .ui-datepicker-prev-hover{right:1px}.ui-datepicker .ui-datepicker-next-hover{left:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;right:50%;margin-right:-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-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.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:left;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-right:0;border-left:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:left;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:right}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:right}.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{border-right-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:right}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:ltr}.ui-datepicker-rtl .ui-datepicker-prev{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-next{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker-rtl .ui-datepicker-group{float:left}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0;border-right-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0;border-right-width:1px}.ui-datepicker-cover{display:none;display:block;position:absolute;z-index:-1;filter:mask();top:-4px;right:-4px;width:200px;height:200px}.ui-progressbar{height:2em;text-align:right}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-widget-header{background-color:#83b4d8;background-image:linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-o-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-moz-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-webkit-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-ms-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%)} diff --git a/assets/css/jquery-ui-fresh.min.css b/assets/css/jquery-ui-fresh.min.css new file mode 100644 index 00000000000..632e100373e --- /dev/null +++ b/assets/css/jquery-ui-fresh.min.css @@ -0,0 +1 @@ +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,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{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}/*\*/* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}/**/.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-widget{font-family:sans-serif;font-size:12px}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:sans-serif;font-size:1em}.ui-widget-content{border:1px solid #dfdfdf;background:#fff;color:#333}.ui-widget-header{border:1px solid #dfdfdf;color:#333;font-weight:bold;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec)}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #dfdfdf;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec);font-weight:normal;color:#333}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#333;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{border:1px solid #ccc;background-color:#ececec;background-image:-ms-linear-gradient(top,#ececec,#f9f9f9);background-image:-moz-linear-gradient(top,#ececec,#f9f9f9);background-image:-o-linear-gradient(top,#ececec,#f9f9f9);background-image:-webkit-gradient(linear,left top,left bottom,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#ececec,#f9f9f9);background-image:linear-gradient(top,#ececec,#f9f9f9);font-weight:normal;color:#000}.ui-state-hover a,.ui-state-hover a:hover{color:#000;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #dfdfdf;background:#fff;font-weight:normal;color:#333}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#333;text-decoration:none}.ui-widget :active{outline:0}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #e6db55;background:#ffffe0;color:#333}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#333}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #c00;background:#ffebe8;color:#c00}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#c00}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#c00}.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);font-weight:normal}.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-icon{width:16px;height:16px;background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-content .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-header .ui-icon{background-image:url(../images/ui-icons_999999_256x240.png)}.ui-state-default .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-active .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(../images/ui-icons_21759b_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(../images/ui-icons_cc0000_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-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:-64px -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:-64px -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:0 -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-off{background-position:-96px -144px}.ui-icon-radio-on{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{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-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:3px;-webkit-border-top-right-radius:3px;border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:3px;-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.ui-widget-overlay{background:#000;opacity:.6;filter:Alpha(Opacity=60)}.ui-widget-shadow{box-shadow:0 0 16px rgba(0,0,0,0.3)}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;z-index:99999;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion{width:100%}.ui-accordion .ui-accordion-header{cursor:pointer;position:relative;margin-top:1px;zoom:1}.ui-accordion .ui-accordion-li-fix{display:inline}.ui-accordion .ui-accordion-header-active{border-bottom:0!important}.ui-accordion .ui-accordion-header a{display:block;font-size:1em;padding:.5em .5em .5em .7em}.ui-accordion-icons .ui-accordion-header a{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none;zoom:1}.ui-accordion .ui-accordion-content-active{display:block}.ui-autocomplete{position:absolute;cursor:default}* html .ui-autocomplete{width:1px}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:left}.ui-menu .ui-menu{margin-top:-3px}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:left;clear:left;width:100%}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;text-decoration:none!important;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-dialog{position:fixed;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:0;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:1px;margin:0 .2em 1px 0;border-bottom:0!important;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-selected{margin-bottom:0;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:0}.ui-tabs .ui-tabs-hide{display:none!important}.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-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.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}.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{border-left-width:0}.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-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{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{display:none;display:block;position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}.ui-progressbar{height:2em;text-align:left}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-widget-header{background-color:#83b4d8;background-image:linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-o-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-moz-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-webkit-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-ms-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%)} \ No newline at end of file diff --git a/assets/css/variables/_colors.scss b/assets/css/variables/_colors.scss new file mode 100644 index 00000000000..95a02b335ff --- /dev/null +++ b/assets/css/variables/_colors.scss @@ -0,0 +1,31 @@ +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/colors.native"; +/** + * WordPress Core colors current as of 5.5.1. + */ +$wp-text: $gray-text-min; +$wp-border: #c3c4c7; + +$wp-input-text: #32373c; +$wp-input-border: #7e8993; + +$wp-alternate: #f9f9f9; +$wp-inactive: #999; + +$wp-red-50: #d63638; +$wp-green-30: #00ba37; +$wp-green-50: #008a20; +$wp-yellow-50: #996800; + +$wp-gray-0: $gray-0; +$wp-gray-2: $gray-100; +$wp-gray-5: #dcdcde; +$wp-gray-10: #c3c4c7; +$wp-gray-20: $gray-20; +$wp-gray-40: $gray-40; +$wp-gray-50: $gray-50; + +$buddypress-colors: ( + 'evergreen': #36533f, + 'mint': #4f6d59, +); diff --git a/assets/css/variables/_icons.scss b/assets/css/variables/_icons.scss new file mode 100644 index 00000000000..6bf609004f2 --- /dev/null +++ b/assets/css/variables/_icons.scss @@ -0,0 +1,5 @@ + +$icon-search: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBjbGFzcz0idy02IGgtNiI+CiAgPHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMjEgMjFsLTUuMTk3LTUuMTk3bTAgMEE3LjUgNy41IDAgMTA1LjE5NiA1LjE5NmE3LjUgNy41IDAgMDAxMC42MDcgMTAuNjA3eiIgLz4KPC9zdmc+Cg==); +$icon-close: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBjbGFzcz0idy02IGgtNiI+CiAgPHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNNiAxOEwxOCA2TTYgNmwxMiAxMiIgLz4KPC9zdmc+Cg==); +$icon-install: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTkuMTExNTggMVY2LjA3NTkxSDEwLjk2NDFWNi44MTY5Mkw4LjAwMDA3IDkuNzgwOTVMNS4wMzYwNCA2LjgxNjkyVjYuMDc1OTFINi44ODg1NlYxSDkuMTExNThaTTMuMTgzNTIgMTAuMTUxNVYxMi4wMDRIMTIuODE2NlYxMC4xNTE1SDE1Ljc4MDdWMTMuNDg2VjE0Ljk2OEgxNC4yOTg2SDEuNzAxNUgwLjIxOTQ4MlYxMy40ODZWMTAuMTUxNUgzLjE4MzUyWiIgZmlsbD0iIzk5OSIvPgo8L3N2Zz4K); +$icon-settings: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yOTAyIDYuMjkzMjJMMTMuNzc0NyA3LjE2ODJDMTMuODEzNiA3LjQ0MDE0IDEzLjgzMzYgNy43MTc5NCAxMy44MzM2IDguMDAwMTRDMTMuODMzNiA4LjI4MjQgMTMuODEzNiA4LjU2MDIgMTMuNzc0NyA4LjgzMjE0TDE1LjI5MDIgOS43MDcxNEwxMy4xMjM1IDEzLjQ1OTlMMTEuNjA3NiAxMi41ODQ3QzExLjE3MzIgMTIuOTI2OSAxMC42ODg4IDEzLjIwODkgMTAuMTY3IDEzLjQxNzdWMTUuMTY2OEg1LjgzMzY1VjEzLjQxNzdDNS4zMTE4IDEzLjIwODkgNC44MjczOCAxMi45MjY5IDQuMzkzMDIgMTIuNTg0N0wyLjg3NzEyIDEzLjQ1OTlMMC43MTA0NTYgOS43MDcxNEwyLjIyNTkzIDguODMyMTRDMi4xODcwNiA4LjU2MDIgMi4xNjY5OCA4LjI4MjQgMi4xNjY5OCA4LjAwMDE0QzIuMTY2OTggNy43MTc5NCAyLjE4NzA2IDcuNDQwMTQgMi4yMjU5MiA3LjE2ODJMMC43MTA0NDkgNi4yOTMyMkwyLjg3NzEyIDIuNTQwNDRMNC4zOTMwMiAzLjQxNTY1QzQuODI3MzggMy4wNzM0NCA1LjMxMTc5IDIuNzkxNDkgNS44MzM2NSAyLjU4MjYxVjAuODMzNDk2SDEwLjE2N1YyLjU4MjYxQzEwLjY4ODggMi43OTE0OSAxMS4xNzMyIDMuMDczNDQgMTEuNjA3NiAzLjQxNTY1TDEzLjEyMzUgMi41NDA0NEwxNS4yOTAyIDYuMjkzMjJaTTguMDAwMTYgMTAuMzMzM0M5LjI4ODgzIDEwLjMzMzMgMTAuMzMzNSA5LjI4ODY3IDEwLjMzMzUgOEMxMC4zMzM1IDYuNzExMzQgOS4yODg4MyA1LjY2NjY3IDguMDAwMTYgNS42NjY2N0M2LjcxMTUgNS42NjY2NyA1LjY2NjgzIDYuNzExMzQgNS42NjY4MyA4QzUuNjY2ODMgOS4yODg2NyA2LjcxMTUgMTAuMzMzMyA4LjAwMDE2IDEwLjMzMzNaIiBmaWxsPSIjNkI3MjgwIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTUuMjkwMiA2LjI5MzIyTDEzLjc3NDcgNy4xNjgyQzEzLjgxMzYgNy40NDAxNCAxMy44MzM2IDcuNzE3OTQgMTMuODMzNiA4LjAwMDE0QzEzLjgzMzYgOC4yODI0IDEzLjgxMzYgOC41NjAyIDEzLjc3NDcgOC44MzIxNEwxNS4yOTAyIDkuNzA3MTRMMTMuMTIzNSAxMy40NTk5TDExLjYwNzYgMTIuNTg0N0MxMS4xNzMyIDEyLjkyNjkgMTAuNjg4OCAxMy4yMDg5IDEwLjE2NyAxMy40MTc3VjE1LjE2NjhINS44MzM2NVYxMy40MTc3QzUuMzExOCAxMy4yMDg5IDQuODI3MzggMTIuOTI2OSA0LjM5MzAyIDEyLjU4NDdMMi44NzcxMiAxMy40NTk5TDAuNzEwNDU2IDkuNzA3MTRMMi4yMjU5MyA4LjgzMjE0QzIuMTg3MDYgOC41NjAyIDIuMTY2OTggOC4yODI0IDIuMTY2OTggOC4wMDAxNEMyLjE2Njk4IDcuNzE3OTQgMi4xODcwNiA3LjQ0MDE0IDIuMjI1OTIgNy4xNjgyTDAuNzEwNDQ5IDYuMjkzMjJMMi44NzcxMiAyLjU0MDQ0TDQuMzkzMDIgMy40MTU2NUM0LjgyNzM4IDMuMDczNDQgNS4zMTE3OSAyLjc5MTQ5IDUuODMzNjUgMi41ODI2MVYwLjgzMzQ5NkgxMC4xNjdWMi41ODI2MUMxMC42ODg4IDIuNzkxNDkgMTEuMTczMiAzLjA3MzQ0IDExLjYwNzYgMy40MTU2NUwxMy4xMjM1IDIuNTQwNDRMMTUuMjkwMiA2LjI5MzIyWk04LjAwMDE2IDEwLjMzMzNDOS4yODg4MyAxMC4zMzMzIDEwLjMzMzUgOS4yODg2NyAxMC4zMzM1IDhDMTAuMzMzNSA2LjcxMTM0IDkuMjg4ODMgNS42NjY2NyA4LjAwMDE2IDUuNjY2NjdDNi43MTE1IDUuNjY2NjcgNS42NjY4MyA2LjcxMTM0IDUuNjY2ODMgOEM1LjY2NjgzIDkuMjg4NjcgNi43MTE1IDEwLjMzMzMgOC4wMDAxNiAxMC4zMzMzWiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+Cjwvc3ZnPgo=); diff --git a/assets/css/variables/_mixins.scss b/assets/css/variables/_mixins.scss new file mode 100644 index 00000000000..259935ffc28 --- /dev/null +++ b/assets/css/variables/_mixins.scss @@ -0,0 +1,23 @@ +@mixin edd-admin-colors() { + @include admin-scheme(#007cba); + @include wordpress-admin-schemes(); + + @each $slug, $color in $buddypress-colors { + body.admin-color-#{$slug} { + @include admin-scheme($color); + } + } +} + +@mixin edd-spinner( $size: 1em, $border-background: $white, $border-color: $black, $border-width: 2px ) { + animation: 1.5s linear infinite edd-spinning; + animation-play-state: inherit; + border: $border-width solid $border-background; + border-bottom-color: $border-color; + border-radius: 100%; + content: ""; + width: $size; + height: $size; + transform: translate3d(-50%, -50%, 0); + will-change: transform; +} diff --git a/assets/css/variables/_variables.scss b/assets/css/variables/_variables.scss new file mode 100644 index 00000000000..0d446ac5918 --- /dev/null +++ b/assets/css/variables/_variables.scss @@ -0,0 +1,9 @@ +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/variables"; +@import "~@wordpress/base-styles/mixins"; +@import "~@wordpress/base-styles/breakpoints"; +@import "~@wordpress/base-styles/animations"; + +@import "colors"; +@import "icons"; +@import "mixins"; diff --git a/assets/css/vendor/chosen-rtl.min.css b/assets/css/vendor/chosen-rtl.min.css new file mode 100755 index 00000000000..506a4636f61 --- /dev/null +++ b/assets/css/vendor/chosen-rtl.min.css @@ -0,0 +1,11 @@ +/*! +Chosen, a Select Box Enhancer for jQuery and Prototype +by Patrick Filler for Harvest, http://getharvest.com + +Version 1.8.5 +Full source at https://github.com/harvesthq/chosen +Copyright (c) 2011-2018 Harvest http://getharvest.com + +MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md +This file is generated by `grunt build`, do not edit it by hand. +*/.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:13px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chosen-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1010;width:100%;border:1px solid #aaa;border-top:0;background:#fff;-webkit-box-shadow:0 4px 5px rgba(0,0,0,.15);box-shadow:0 4px 5px rgba(0,0,0,.15);display:none}.chosen-container.chosen-with-drop .chosen-drop{display:block}.chosen-container a{cursor:pointer}.chosen-container .chosen-single .group-name,.chosen-container .search-choice .group-name{margin-left:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-weight:400;color:#999}.chosen-container .chosen-single .group-name:after,.chosen-container .search-choice .group-name:after{content:":";padding-right:2px;vertical-align:top}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:0 8px 0 0;height:25px;border:1px solid #aaa;border-radius:5px;background-color:#fff;background:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#fff),color-stop(50%,#f6f6f6),color-stop(52%,#eee),to(#f4f4f4));background:linear-gradient(#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background-clip:padding-box;-webkit-box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-single input[type=text]{cursor:pointer;opacity:0;position:absolute;width:0}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span{display:block;overflow:hidden;margin-left:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-left:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:6px;left:26px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-single .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single.chosen-disabled .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single .chosen-single div{position:absolute;top:0;left:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url(chosen-sprite.png) no-repeat 0 2px}.chosen-container-single .chosen-search{position:relative;z-index:1010;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{margin:1px 0;padding:4px 5px 4px 20px;width:100%;height:auto;outline:0;border:1px solid #aaa;background:url(chosen-sprite.png) no-repeat 0 -20px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-single .chosen-drop{margin-top:-1px;border-radius:0 0 4px 4px;background-clip:padding-box}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;opacity:0;pointer-events:none}.chosen-container .chosen-results{color:#444;position:relative;overflow-x:hidden;overflow-y:auto;margin:0 0 4px 4px;padding:0 4px 0 0;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;word-wrap:break-word;-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#3875d7),color-stop(90%,#2a62bc));background-image:linear-gradient(#3875d7 20%,#2a62bc 90%);color:#fff}.chosen-container .chosen-results li.no-results{color:#777;display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-right:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;margin:0;padding:0 5px;width:100%;height:auto;border:1px solid #aaa;background-color:#fff;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(1%,#eee),color-stop(15%,#fff));background-image:linear-gradient(#eee 1%,#fff 15%);cursor:text}.chosen-container-multi .chosen-choices li{float:right;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:1px 0;padding:0;height:25px;outline:0;border:0!important;background:0 0!important;-webkit-box-shadow:none;box-shadow:none;color:#999;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0;width:25px}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 0 3px 5px;padding:3px 5px 3px 20px;border:1px solid #aaa;max-width:100%;border-radius:3px;background-color:#eee;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),to(#eee));background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-size:100% 19px;background-repeat:repeat-x;background-clip:padding-box;-webkit-box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);color:#333;line-height:13px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice span{word-wrap:break-word}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;left:3px;display:block;width:12px;height:12px;background:url(chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{background-position:-42px -10px}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-left:5px;border:1px solid #ccc;background-color:#e4e4e4;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),to(#eee));background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{margin:0;padding:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border:1px solid #5897fb;-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-gradient(linear,right top,right bottom,color-stop(20%,#eee),color-stop(80%,#fff));background-image:linear-gradient(#eee 20%,#fff 80%);-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset}.chosen-container-active.chosen-with-drop .chosen-single div{border-right:none;background:0 0}.chosen-container-active.chosen-with-drop .chosen-single div b{background-position:-18px 2px}.chosen-container-active .chosen-choices{border:1px solid #5897fb;-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#222!important}.chosen-disabled{opacity:.5!important;cursor:default}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close{cursor:default}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-resolution:144dpi),only screen and (min-resolution:1.5dppx){.chosen-container .chosen-results-scroll-down span,.chosen-container .chosen-results-scroll-up span,.chosen-container-multi .chosen-choices .search-choice .search-choice-close,.chosen-container-single .chosen-search input[type=text],.chosen-container-single .chosen-single abbr,.chosen-container-single .chosen-single div b{background-image:url(chosen-sprite@2x.png)!important;background-size:52px 37px!important;background-repeat:no-repeat!important}} \ No newline at end of file diff --git a/assets/css/vendor/chosen.min.css b/assets/css/vendor/chosen.min.css new file mode 100755 index 00000000000..fb044bf2d7f --- /dev/null +++ b/assets/css/vendor/chosen.min.css @@ -0,0 +1,8 @@ +/*! +Chosen, a Select Box Enhancer for jQuery and Prototype +Version 2.1.0 +Full source at https://github.com/jjj/chosen +Copyright (c) 2011-2020 JJJ +MIT License, https://github.com/jjj/chosen/blob/master/LICENSE.md +This file is generated by `grunt build`, do not edit it by hand. +*/.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:14px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chosen-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.chosen-container .chosen-drop{position:absolute;top:100%;z-index:1011;width:100%;border:1px solid #aaa;border-top:0;border-radius:0 0 4px 4px;background:#fff;clip:rect(0,0,0,0);-webkit-clip-path:inset(100% 100%);clip-path:inset(100% 100%);margin-top:-1px;background-clip:padding-box}.chosen-container.chosen-with-drop .chosen-drop{clip:auto;-webkit-clip-path:none;clip-path:none}.chosen-container.chosen-dropup .chosen-choices,.chosen-container.chosen-dropup .chosen-single{z-index:1010}.chosen-container.chosen-dropup .chosen-drop{top:auto;bottom:100%;border:solid #aaa;border-width:1px 1px 0 1px;border-radius:4px 4px 0 0}.chosen-container a{cursor:pointer}.chosen-container .chosen-single .group-name,.chosen-container .search-choice .group-name{margin-right:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-weight:400;color:#999}.chosen-container .chosen-single .group-name:after,.chosen-container .search-choice .group-name:after{content:":";padding-left:2px;vertical-align:top}.chosen-container .search-choice-close{position:absolute;right:3px;top:0;bottom:0;margin:auto;border:none;cursor:pointer;display:block;width:15px;height:15px;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 0 top 50%;background-size:15px 15px}.chosen-container .search-choice-close:active,.chosen-container .search-choice-close:hover{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 0 top 50%;background-size:15px 15px}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:2.5px 0 2.5px 7px;border:1px solid #aaa;border-radius:4px;background-color:#fff;background-clip:padding-box;color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span,.chosen-container-single .chosen-single-with-deselect.chosen-default span{display:block;overflow:hidden;margin-right:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:42px}.chosen-container-single .chosen-single .search-choice-close{right:26px}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 5px top 52%;background-size:15px 15px}.chosen-container-single .chosen-search{position:relative;z-index:1011;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{margin:0;padding:5px 20px 5px 5px;width:100%;height:auto;outline:0;border:1px solid #ccc;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 5px top 50%;background-size:15px 15px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:4px}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;clip:rect(0,0,0,0);-webkit-clip-path:inset(100% 100%);clip-path:inset(100% 100%)}.chosen-container-single.chosen-container-single-nosearch.chosen-dropup .chosen-results{padding-top:4px}.chosen-container-single.chosen-container-single-nosearch.chosen-dropup .chosen-single{z-index:1010}.chosen-container-single .chosen-drop .result-selected{background-color:#eee}.chosen-container .chosen-results{color:#444;position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;word-wrap:break-word;-webkit-touch-callout:none;border-radius:4px}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;color:#fff}.chosen-container .chosen-results li.no-results{color:#777;display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;margin:0;padding:0 3px;width:100%;height:auto;border-radius:4px;border:1px solid #aaa;background-color:#fff;cursor:text}.chosen-container-multi .chosen-choices li{float:left;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:0 3px;padding:6.5px 0;outline:0;border:none;background:0 0;-webkit-box-shadow:none;box-shadow:none;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0;width:25px}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 5px 3px 0;padding:4px 20px 4px 5px;border:1px solid #aaa;border-radius:3px;max-width:100%;background-color:#eee;color:#333;line-height:12px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice span{font-size:95%;word-wrap:break-word}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;border:1px solid #ccc;background-color:#e4e4e4;color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-multi.chosen-dropup .chosen-results{padding-top:4px}.chosen-container-multi.chosen-dropup .chosen-single{z-index:1010}.chosen-container-active .chosen-single{outline:#00f}.chosen-container-active.chosen-with-drop .chosen-choices,.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;border-bottom-right-radius:0;border-bottom-left-radius:0}.chosen-container-active.chosen-with-drop .chosen-single div b{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat right 5px top 52%;background-size:15px 15px}.chosen-container-active.chosen-with-drop.chosen-dropup .chosen-choices,.chosen-container-active.chosen-with-drop.chosen-dropup .chosen-single{border-radius:0 0 4px 4px}.chosen-container-active .chosen-choices{border:1px solid #5897fb}.chosen-disabled{opacity:.5;cursor:default}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .search-choice-close{cursor:default}.chosen-rtl{text-align:right}.chosen-rtl .chosen-single{padding:2px 7px 2px 0}.chosen-rtl .chosen-single span{margin-right:0;margin-left:26px;direction:rtl}.chosen-rtl .chosen-single-with-deselect span{margin-left:42px}.chosen-rtl .chosen-single div{right:auto;left:0}.chosen-rtl .chosen-single .search-choice-close{right:auto;left:26px}.chosen-rtl .chosen-choices li{float:right}.chosen-rtl .chosen-choices li.search-field input[type=text]{direction:rtl}.chosen-rtl .chosen-choices li.search-choice{margin:3px 0 3px 5px;padding:3px 5px 3px 20px}.chosen-rtl .chosen-choices li.search-choice .search-choice-close{right:auto;left:3px}.chosen-rtl.chosen-container-single .chosen-results{margin:0 0 4px 4px;padding:0 4px 0 0}.chosen-rtl .chosen-results li.group-option{padding-right:15px;padding-left:0}.chosen-rtl .chosen-search input[type=text]{padding:5px 5px 5px 20px;background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat left 5px top 55%;background-size:15px 15px;direction:rtl}.chosen-rtl.chosen-container-single .chosen-single div b{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat left 5px top 52%;background-size:15px 15px}.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b{background:url('data:image/svg+xml;charset=US-ASCII,') no-repeat left 5px top 52%;background-size:15px 15px} \ No newline at end of file diff --git a/assets/css/vendor/jquery-ui-fresh-rtl.min.css b/assets/css/vendor/jquery-ui-fresh-rtl.min.css new file mode 100644 index 00000000000..87836669ad3 --- /dev/null +++ b/assets/css/vendor/jquery-ui-fresh-rtl.min.css @@ -0,0 +1 @@ +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,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{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}/*\*/* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}/**/.ui-helper-zfix{width:100%;height:100%;top:0;right:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;right:0;width:100%;height:100%}.ui-widget{font-family:sans-serif;font-size:12px}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:sans-serif;font-size:1em}.ui-widget-content{border:1px solid #dfdfdf;background:#fff;color:#333}.ui-widget-header{border:1px solid #dfdfdf;color:#333;font-weight:bold;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,right top,right bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec)}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #dfdfdf;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,right top,right bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec);font-weight:normal;color:#333}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#333;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{border:1px solid #ccc;background-color:#ececec;background-image:-ms-linear-gradient(top,#ececec,#f9f9f9);background-image:-moz-linear-gradient(top,#ececec,#f9f9f9);background-image:-o-linear-gradient(top,#ececec,#f9f9f9);background-image:-webkit-gradient(linear,right top,right bottom,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#ececec,#f9f9f9);background-image:linear-gradient(top,#ececec,#f9f9f9);font-weight:normal;color:#000}.ui-state-hover a,.ui-state-hover a:hover{color:#000;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #dfdfdf;background:#fff;font-weight:normal;color:#333}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#333;text-decoration:none}.ui-widget :active{outline:0}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #e6db55;background:#ffffe0;color:#333}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#333}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #c00;background:#ffebe8;color:#c00}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#c00}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#c00}.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);font-weight:normal}.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-icon{width:16px;height:16px;background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-content .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-header .ui-icon{background-image:url(../images/ui-icons_999999_256x240.png)}.ui-state-default .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-active .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(../images/ui-icons_21759b_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(../images/ui-icons_cc0000_256x240.png)}.ui-icon-carat-1-n{background-position:100% 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:100% -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:-64px -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:100% -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:-64px -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:100% -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:100% -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:100% -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:100% -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:100% -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:100% -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:100% -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-off{background-position:-96px -144px}.ui-icon-radio-on{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:100% -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{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:100% -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:100% -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:100% -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:100% -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-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topright:3px;-webkit-border-top-right-radius:3px;border-top-right-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topleft:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomright:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomleft:3px;-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px}.ui-widget-overlay{background:#000;opacity:.6;filter:Alpha(Opacity=60)}.ui-widget-shadow{box-shadow:0 0 16px rgba(0,0,0,0.3)}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;z-index:99999;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;right:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;right:0}.ui-resizable-e{cursor:e-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-se{cursor:sw-resize;width:12px;height:12px;left:1px;bottom:1px}.ui-resizable-sw{cursor:se-resize;width:9px;height:9px;right:-5px;bottom:-5px}.ui-resizable-nw{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-resizable-ne{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion{width:100%}.ui-accordion .ui-accordion-header{cursor:pointer;position:relative;margin-top:1px;zoom:1}.ui-accordion .ui-accordion-li-fix{display:inline}.ui-accordion .ui-accordion-header-active{border-bottom:0!important}.ui-accordion .ui-accordion-header a{display:block;font-size:1em;padding:.5em .7em .5em .5em}.ui-accordion-icons .ui-accordion-header a{padding-right:2.2em}.ui-accordion .ui-accordion-header .ui-icon{position:absolute;right:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none;zoom:1}.ui-accordion .ui-accordion-content-active{display:block}.ui-autocomplete{position:absolute;cursor:default}* html .ui-autocomplete{width:1px}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:right}.ui-menu .ui-menu{margin-top:-3px}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:right;clear:right;width:100%}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-button{display:inline-block;position:relative;padding:0;margin-left:.1em;text-decoration:none!important;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icons .ui-button-text{padding-right:2.1em;padding-left:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{right:50%;margin-right:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{right:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{left:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{left:.5em}.ui-buttonset{margin-left:7px}.ui-buttonset .ui-button{margin-right:0;margin-left:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-dialog{position:fixed;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:right;margin:.1em 0 .1em 16px}.ui-dialog .ui-dialog-titlebar-close{position:absolute;left:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:100%;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:right;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em .4em .5em 1em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:left}.ui-dialog .ui-dialog-buttonpane button{margin:.5em 0 .5em .4em;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;left:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-slider{position:relative;text-align:right}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:100% 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-right:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{right:0}.ui-slider-horizontal .ui-slider-range-max{left:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{right:-.3em;margin-right:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{right:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:right;position:relative;top:1px;margin:0 0 1px .2em;border-bottom:0!important;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:right;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-selected{margin-bottom:0;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:100%}.ui-tabs .ui-tabs-hide{display:none!important}.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{right:2px}.ui-datepicker .ui-datepicker-next{left:2px}.ui-datepicker .ui-datepicker-prev-hover{right:1px}.ui-datepicker .ui-datepicker-next-hover{left:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;right:50%;margin-right:-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-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.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:left;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-right:0;border-left:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:left;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:right}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:right}.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{border-right-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:right}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:ltr}.ui-datepicker-rtl .ui-datepicker-prev{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-next{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker-rtl .ui-datepicker-group{float:left}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0;border-right-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0;border-right-width:1px}.ui-datepicker-cover{display:none;display:block;position:absolute;z-index:-1;filter:mask();top:-4px;right:-4px;width:200px;height:200px}.ui-progressbar{height:2em;text-align:right}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-widget-header{background-color:#83b4d8;background-image:linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-o-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-moz-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-webkit-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-ms-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%)} diff --git a/assets/css/vendor/jquery-ui-fresh.min.css b/assets/css/vendor/jquery-ui-fresh.min.css new file mode 100755 index 00000000000..632e100373e --- /dev/null +++ b/assets/css/vendor/jquery-ui-fresh.min.css @@ -0,0 +1 @@ +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,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{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}/*\*/* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}/**/.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-widget{font-family:sans-serif;font-size:12px}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:sans-serif;font-size:1em}.ui-widget-content{border:1px solid #dfdfdf;background:#fff;color:#333}.ui-widget-header{border:1px solid #dfdfdf;color:#333;font-weight:bold;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec)}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #dfdfdf;background-color:#f1f1f1;background-image:-ms-linear-gradient(top,#f9f9f9,#ececec);background-image:-moz-linear-gradient(top,#f9f9f9,#ececec);background-image:-o-linear-gradient(top,#f9f9f9,#ececec);background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#ececec));background-image:-webkit-linear-gradient(top,#f9f9f9,#ececec);background-image:linear-gradient(top,#f9f9f9,#ececec);font-weight:normal;color:#333}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#333;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{border:1px solid #ccc;background-color:#ececec;background-image:-ms-linear-gradient(top,#ececec,#f9f9f9);background-image:-moz-linear-gradient(top,#ececec,#f9f9f9);background-image:-o-linear-gradient(top,#ececec,#f9f9f9);background-image:-webkit-gradient(linear,left top,left bottom,from(#ececec),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#ececec,#f9f9f9);background-image:linear-gradient(top,#ececec,#f9f9f9);font-weight:normal;color:#000}.ui-state-hover a,.ui-state-hover a:hover{color:#000;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #dfdfdf;background:#fff;font-weight:normal;color:#333}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#333;text-decoration:none}.ui-widget :active{outline:0}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #e6db55;background:#ffffe0;color:#333}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#333}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #c00;background:#ffebe8;color:#c00}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#c00}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#c00}.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);font-weight:normal}.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-icon{width:16px;height:16px;background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-content .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-widget-header .ui-icon{background-image:url(../images/ui-icons_999999_256x240.png)}.ui-state-default .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-active .ui-icon{background-image:url(../images/ui-icons_333333_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(../images/ui-icons_21759b_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(../images/ui-icons_cc0000_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-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:-64px -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:-64px -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:0 -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-off{background-position:-96px -144px}.ui-icon-radio-on{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{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-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:3px;-webkit-border-top-right-radius:3px;border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:3px;-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.ui-widget-overlay{background:#000;opacity:.6;filter:Alpha(Opacity=60)}.ui-widget-shadow{box-shadow:0 0 16px rgba(0,0,0,0.3)}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;z-index:99999;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion{width:100%}.ui-accordion .ui-accordion-header{cursor:pointer;position:relative;margin-top:1px;zoom:1}.ui-accordion .ui-accordion-li-fix{display:inline}.ui-accordion .ui-accordion-header-active{border-bottom:0!important}.ui-accordion .ui-accordion-header a{display:block;font-size:1em;padding:.5em .5em .5em .7em}.ui-accordion-icons .ui-accordion-header a{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none;zoom:1}.ui-accordion .ui-accordion-content-active{display:block}.ui-autocomplete{position:absolute;cursor:default}* html .ui-autocomplete{width:1px}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:left}.ui-menu .ui-menu{margin-top:-3px}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:left;clear:left;width:100%}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;text-decoration:none!important;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-dialog{position:fixed;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:0;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:1px;margin:0 .2em 1px 0;border-bottom:0!important;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-selected{margin-bottom:0;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:0}.ui-tabs .ui-tabs-hide{display:none!important}.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-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.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}.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{border-left-width:0}.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-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{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{display:none;display:block;position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}.ui-progressbar{height:2em;text-align:left}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-widget-header{background-color:#83b4d8;background-image:linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-o-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-moz-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-webkit-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%);background-image:-ms-linear-gradient(bottom,#72a7cf 0,#90c5ee 100%)} \ No newline at end of file diff --git a/assets/images/edd-cpt-2x.png b/assets/images/edd-cpt-2x.png new file mode 100644 index 00000000000..89e8f5b0e0b Binary files /dev/null and b/assets/images/edd-cpt-2x.png differ diff --git a/assets/images/edd-cpt.png b/assets/images/edd-cpt.png new file mode 100644 index 00000000000..a59acb5c195 Binary files /dev/null and b/assets/images/edd-cpt.png differ diff --git a/assets/images/edd-cross-hair.png b/assets/images/edd-cross-hair.png new file mode 100755 index 00000000000..7cb88bb96a4 Binary files /dev/null and b/assets/images/edd-cross-hair.png differ diff --git a/assets/images/edd-icon-2x.png b/assets/images/edd-icon-2x.png new file mode 100644 index 00000000000..32572a702f7 Binary files /dev/null and b/assets/images/edd-icon-2x.png differ diff --git a/assets/images/edd-icon.png b/assets/images/edd-icon.png new file mode 100644 index 00000000000..be8b1e47bc4 Binary files /dev/null and b/assets/images/edd-icon.png differ diff --git a/assets/images/edd-logo-pdf.png b/assets/images/edd-logo-pdf.png new file mode 100644 index 00000000000..deb1c625496 Binary files /dev/null and b/assets/images/edd-logo-pdf.png differ diff --git a/assets/images/edd-logo-white-2x.png b/assets/images/edd-logo-white-2x.png new file mode 100644 index 00000000000..c6f0029d22b Binary files /dev/null and b/assets/images/edd-logo-white-2x.png differ diff --git a/assets/images/edd-logo.png b/assets/images/edd-logo.png new file mode 100644 index 00000000000..de288d045e6 Binary files /dev/null and b/assets/images/edd-logo.png differ diff --git a/assets/images/edd-logo.svg b/assets/images/edd-logo.svg new file mode 100644 index 00000000000..5b295c42929 --- /dev/null +++ b/assets/images/edd-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/edd-media.png b/assets/images/edd-media.png new file mode 100755 index 00000000000..34cbac61479 Binary files /dev/null and b/assets/images/edd-media.png differ diff --git a/assets/images/edd-peeking.png b/assets/images/edd-peeking.png new file mode 100644 index 00000000000..f6f6be09731 Binary files /dev/null and b/assets/images/edd-peeking.png differ diff --git a/assets/images/icons/icon-arrow-down.png b/assets/images/icons/icon-arrow-down.png new file mode 100644 index 00000000000..2ef013c4ea3 Binary files /dev/null and b/assets/images/icons/icon-arrow-down.png differ diff --git a/assets/images/icons/icon-arrow-up.png b/assets/images/icons/icon-arrow-up.png new file mode 100644 index 00000000000..ac1f534c04e Binary files /dev/null and b/assets/images/icons/icon-arrow-up.png differ diff --git a/assets/images/icons/icon-automate.svg b/assets/images/icons/icon-automate.svg new file mode 100644 index 00000000000..74faa4376da --- /dev/null +++ b/assets/images/icons/icon-automate.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/images/icons/icon-average.png b/assets/images/icons/icon-average.png new file mode 100644 index 00000000000..082f7a68a37 Binary files /dev/null and b/assets/images/icons/icon-average.png differ diff --git a/assets/images/icons/icon-bundle.svg b/assets/images/icons/icon-bundle.svg new file mode 100644 index 00000000000..bc9612df653 --- /dev/null +++ b/assets/images/icons/icon-bundle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/icons/icon-chevron-down.svg b/assets/images/icons/icon-chevron-down.svg new file mode 100644 index 00000000000..ba51258c758 --- /dev/null +++ b/assets/images/icons/icon-chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/icons/icon-edd-heart.svg b/assets/images/icons/icon-edd-heart.svg new file mode 100644 index 00000000000..6ac1679d213 --- /dev/null +++ b/assets/images/icons/icon-edd-heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/icons/icon-email-marketing.svg b/assets/images/icons/icon-email-marketing.svg new file mode 100644 index 00000000000..9d36f6a3610 --- /dev/null +++ b/assets/images/icons/icon-email-marketing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/icons/icon-gateways.svg b/assets/images/icons/icon-gateways.svg new file mode 100644 index 00000000000..092aae04213 --- /dev/null +++ b/assets/images/icons/icon-gateways.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/icons/icon-gross.png b/assets/images/icons/icon-gross.png new file mode 100644 index 00000000000..0de9bee8940 Binary files /dev/null and b/assets/images/icons/icon-gross.png differ diff --git a/assets/images/icons/icon-install.svg b/assets/images/icons/icon-install.svg new file mode 100644 index 00000000000..da0cdc5ae7b --- /dev/null +++ b/assets/images/icons/icon-install.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/icons/icon-lead-magnets.svg b/assets/images/icons/icon-lead-magnets.svg new file mode 100644 index 00000000000..2a853d19784 --- /dev/null +++ b/assets/images/icons/icon-lead-magnets.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/icons/icon-megaphone.png b/assets/images/icons/icon-megaphone.png new file mode 100644 index 00000000000..152004e5111 Binary files /dev/null and b/assets/images/icons/icon-megaphone.png differ diff --git a/assets/images/icons/icon-net.png b/assets/images/icons/icon-net.png new file mode 100644 index 00000000000..f8bd82c7f80 Binary files /dev/null and b/assets/images/icons/icon-net.png differ diff --git a/assets/images/icons/icon-new-customers.png b/assets/images/icons/icon-new-customers.png new file mode 100644 index 00000000000..d4ec5e56f76 Binary files /dev/null and b/assets/images/icons/icon-new-customers.png differ diff --git a/assets/images/icons/icon-settings.svg b/assets/images/icons/icon-settings.svg new file mode 100644 index 00000000000..3ffd68b14b5 --- /dev/null +++ b/assets/images/icons/icon-settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/icons/icon-smiley.svg b/assets/images/icons/icon-smiley.svg new file mode 100644 index 00000000000..5d4495a2ea6 --- /dev/null +++ b/assets/images/icons/icon-smiley.svg @@ -0,0 +1 @@ + diff --git a/assets/images/icons/icon-subscriptions.svg b/assets/images/icons/icon-subscriptions.svg new file mode 100644 index 00000000000..0716178e104 --- /dev/null +++ b/assets/images/icons/icon-subscriptions.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/icons/icon-top-products.png b/assets/images/icons/icon-top-products.png new file mode 100644 index 00000000000..422c6337ba1 Binary files /dev/null and b/assets/images/icons/icon-top-products.png differ diff --git a/assets/images/icons/iphone.png b/assets/images/icons/iphone.png new file mode 100644 index 00000000000..265b7880bfd Binary files /dev/null and b/assets/images/icons/iphone.png differ diff --git a/assets/images/loading.gif b/assets/images/loading.gif new file mode 100755 index 00000000000..5e21ec18676 Binary files /dev/null and b/assets/images/loading.gif differ diff --git a/assets/images/logo-edd-dark.svg b/assets/images/logo-edd-dark.svg new file mode 100644 index 00000000000..6d831e773f3 --- /dev/null +++ b/assets/images/logo-edd-dark.svg @@ -0,0 +1,216 @@ + + + + diff --git a/assets/images/media-button.png b/assets/images/media-button.png new file mode 100755 index 00000000000..55ff62655d7 Binary files /dev/null and b/assets/images/media-button.png differ diff --git a/assets/images/onboarding/bob.jpg b/assets/images/onboarding/bob.jpg new file mode 100644 index 00000000000..db9c41d301f Binary files /dev/null and b/assets/images/onboarding/bob.jpg differ diff --git a/assets/images/onboarding/joe.jpg b/assets/images/onboarding/joe.jpg new file mode 100644 index 00000000000..1193ae6bcb4 Binary files /dev/null and b/assets/images/onboarding/joe.jpg differ diff --git a/assets/images/onboarding/nicolas.jpg b/assets/images/onboarding/nicolas.jpg new file mode 100644 index 00000000000..c81f3c71138 Binary files /dev/null and b/assets/images/onboarding/nicolas.jpg differ diff --git a/assets/images/onboarding/stripe-logo.svg b/assets/images/onboarding/stripe-logo.svg new file mode 100644 index 00000000000..37b894f9e83 --- /dev/null +++ b/assets/images/onboarding/stripe-logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/assets/images/promo/bfcm-header.svg b/assets/images/promo/bfcm-header.svg new file mode 100644 index 00000000000..c25fb608a27 --- /dev/null +++ b/assets/images/promo/bfcm-header.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/promo/edd-25-percent-off.png b/assets/images/promo/edd-25-percent-off.png new file mode 100644 index 00000000000..6e131c81fa6 Binary files /dev/null and b/assets/images/promo/edd-25-percent-off.png differ diff --git a/assets/images/screenshots/17checkout.png b/assets/images/screenshots/17checkout.png new file mode 100644 index 00000000000..69edd6bed07 Binary files /dev/null and b/assets/images/screenshots/17checkout.png differ diff --git a/assets/images/screenshots/17direct.png b/assets/images/screenshots/17direct.png new file mode 100644 index 00000000000..e7fdfe76c7b Binary files /dev/null and b/assets/images/screenshots/17direct.png differ diff --git a/assets/images/screenshots/17quantities.png b/assets/images/screenshots/17quantities.png new file mode 100644 index 00000000000..2ffdca52509 Binary files /dev/null and b/assets/images/screenshots/17quantities.png differ diff --git a/assets/images/screenshots/18-button-colors.png b/assets/images/screenshots/18-button-colors.png new file mode 100644 index 00000000000..7626a8ce6bf Binary files /dev/null and b/assets/images/screenshots/18-button-colors.png differ diff --git a/assets/images/screenshots/18cart-saving.png b/assets/images/screenshots/18cart-saving.png new file mode 100644 index 00000000000..762820ef2d9 Binary files /dev/null and b/assets/images/screenshots/18cart-saving.png differ diff --git a/assets/images/screenshots/20-discount.png b/assets/images/screenshots/20-discount.png new file mode 100644 index 00000000000..04e49cedcf1 Binary files /dev/null and b/assets/images/screenshots/20-discount.png differ diff --git a/assets/images/screenshots/20-register-login.png b/assets/images/screenshots/20-register-login.png new file mode 100644 index 00000000000..0aa9f408ae6 Binary files /dev/null and b/assets/images/screenshots/20-register-login.png differ diff --git a/assets/images/screenshots/20-sequential.png b/assets/images/screenshots/20-sequential.png new file mode 100644 index 00000000000..5541c71de2b Binary files /dev/null and b/assets/images/screenshots/20-sequential.png differ diff --git a/assets/images/screenshots/20-unlimited-downloads.png b/assets/images/screenshots/20-unlimited-downloads.png new file mode 100644 index 00000000000..798d707a5ab Binary files /dev/null and b/assets/images/screenshots/20-unlimited-downloads.png differ diff --git a/assets/images/screenshots/22-logs.png b/assets/images/screenshots/22-logs.png new file mode 100644 index 00000000000..00171254bad Binary files /dev/null and b/assets/images/screenshots/22-logs.png differ diff --git a/assets/images/screenshots/22-purchased-downloads.png b/assets/images/screenshots/22-purchased-downloads.png new file mode 100644 index 00000000000..13c3c7e2c0a Binary files /dev/null and b/assets/images/screenshots/22-purchased-downloads.png differ diff --git a/assets/images/screenshots/22-purchased-downloads2.png b/assets/images/screenshots/22-purchased-downloads2.png new file mode 100644 index 00000000000..e2d5f02366a Binary files /dev/null and b/assets/images/screenshots/22-purchased-downloads2.png differ diff --git a/assets/images/screenshots/22-quantity.png b/assets/images/screenshots/22-quantity.png new file mode 100644 index 00000000000..cf9d6a2fb4f Binary files /dev/null and b/assets/images/screenshots/22-quantity.png differ diff --git a/assets/images/screenshots/24-category-earnings.png b/assets/images/screenshots/24-category-earnings.png new file mode 100644 index 00000000000..7690501ffbe Binary files /dev/null and b/assets/images/screenshots/24-category-earnings.png differ diff --git a/assets/images/screenshots/24-checkout.png b/assets/images/screenshots/24-checkout.png new file mode 100644 index 00000000000..1d24e226c45 Binary files /dev/null and b/assets/images/screenshots/24-checkout.png differ diff --git a/assets/images/screenshots/24-export.png b/assets/images/screenshots/24-export.png new file mode 100644 index 00000000000..44639130fa8 Binary files /dev/null and b/assets/images/screenshots/24-export.png differ diff --git a/assets/images/screenshots/26-customer.png b/assets/images/screenshots/26-customer.png new file mode 100644 index 00000000000..821e83b6eb6 Binary files /dev/null and b/assets/images/screenshots/26-customer.png differ diff --git a/assets/images/screenshots/26-import.png b/assets/images/screenshots/26-import.png new file mode 100644 index 00000000000..007bcae6092 Binary files /dev/null and b/assets/images/screenshots/26-import.png differ diff --git a/assets/images/screenshots/26-refund.png b/assets/images/screenshots/26-refund.png new file mode 100644 index 00000000000..6a838dda58f Binary files /dev/null and b/assets/images/screenshots/26-refund.png differ diff --git a/assets/images/screenshots/bundles.png b/assets/images/screenshots/bundles.png new file mode 100644 index 00000000000..61e0f1339db Binary files /dev/null and b/assets/images/screenshots/bundles.png differ diff --git a/assets/images/screenshots/customer-ui.png b/assets/images/screenshots/customer-ui.png new file mode 100644 index 00000000000..b3b0c99f859 Binary files /dev/null and b/assets/images/screenshots/customer-ui.png differ diff --git a/assets/images/screenshots/edit-download.png b/assets/images/screenshots/edit-download.png new file mode 100644 index 00000000000..df8aba4db76 Binary files /dev/null and b/assets/images/screenshots/edit-download.png differ diff --git a/assets/images/screenshots/email-template-21.png b/assets/images/screenshots/email-template-21.png new file mode 100644 index 00000000000..f3d548dbfe6 Binary files /dev/null and b/assets/images/screenshots/email-template-21.png differ diff --git a/assets/images/screenshots/grid.png b/assets/images/screenshots/grid.png new file mode 100644 index 00000000000..f9440bc03fe Binary files /dev/null and b/assets/images/screenshots/grid.png differ diff --git a/assets/images/screenshots/order-details.png b/assets/images/screenshots/order-details.png new file mode 100644 index 00000000000..89719c23219 Binary files /dev/null and b/assets/images/screenshots/order-details.png differ diff --git a/assets/images/screenshots/product-earnings.png b/assets/images/screenshots/product-earnings.png new file mode 100644 index 00000000000..ed22aac3194 Binary files /dev/null and b/assets/images/screenshots/product-earnings.png differ diff --git a/assets/images/screenshots/product-tax.png b/assets/images/screenshots/product-tax.png new file mode 100644 index 00000000000..46fc7f4d73c Binary files /dev/null and b/assets/images/screenshots/product-tax.png differ diff --git a/assets/images/screenshots/purchase-link.png b/assets/images/screenshots/purchase-link.png new file mode 100644 index 00000000000..30e1bbf32d5 Binary files /dev/null and b/assets/images/screenshots/purchase-link.png differ diff --git a/assets/images/screenshots/tax-rates.png b/assets/images/screenshots/tax-rates.png new file mode 100644 index 00000000000..5761d30f4b0 Binary files /dev/null and b/assets/images/screenshots/tax-rates.png differ diff --git a/assets/images/ui-icons_21759b_256x240.png b/assets/images/ui-icons_21759b_256x240.png new file mode 100644 index 00000000000..83bb123e006 Binary files /dev/null and b/assets/images/ui-icons_21759b_256x240.png differ diff --git a/assets/images/ui-icons_333333_256x240.png b/assets/images/ui-icons_333333_256x240.png new file mode 100755 index 00000000000..f4bf52bb950 Binary files /dev/null and b/assets/images/ui-icons_333333_256x240.png differ diff --git a/assets/images/ui-icons_999999_256x240.png b/assets/images/ui-icons_999999_256x240.png new file mode 100644 index 00000000000..14b5901b381 Binary files /dev/null and b/assets/images/ui-icons_999999_256x240.png differ diff --git a/assets/images/ui-icons_cc0000_256x240.png b/assets/images/ui-icons_cc0000_256x240.png new file mode 100755 index 00000000000..1b51f76d087 Binary files /dev/null and b/assets/images/ui-icons_cc0000_256x240.png differ diff --git a/assets/images/xit.gif b/assets/images/xit.gif new file mode 100755 index 00000000000..b11c5d43e9b Binary files /dev/null and b/assets/images/xit.gif differ diff --git a/assets/js/admin/components/advanced-filters/index.js b/assets/js/admin/components/advanced-filters/index.js new file mode 100644 index 00000000000..8d5eef6283c --- /dev/null +++ b/assets/js/admin/components/advanced-filters/index.js @@ -0,0 +1,52 @@ +/* global jQuery */ + +jQuery( document ).ready( function( $ ) { + + // when the 'More' button is clicked. + $( '.edd-advanced-filters-button' ).on( 'click', function( e ) { + e.preventDefault(); + + edd_toggle_advanced_order_filters(); + } ); + + // If a click event is triggered. + $( document ).on( 'click', function( e ) { + edd_maybe_toggle_advanced_order_filters( e.target ); + }); + + // If the Escape key is pressed. + $( document ).on( 'keydown', function( event ) { + const key = event.key; + if ( key === "Escape" ) { + edd_maybe_toggle_advanced_order_filters(); + } + }); +} ); + +/** + * Given a target, determine if we should toggle the advanced orders filter overlay. + * + * Determines if the target is the advanced order filters wrappr or an element within it, + * or in the event of a keypress (target = false), toggles the 'open' class. + * + * @param {event|boolean} target The target requested the possible toggle. + * @returns void + */ +function edd_maybe_toggle_advanced_order_filters( target = false) { + var advancedFiltersWrapper = $( '#edd-advanced-filters' ); + + if ( ! advancedFiltersWrapper.hasClass( 'open' ) ) { + return false; + } + + if ( false === target || ( ! advancedFiltersWrapper.is( target ) && ! advancedFiltersWrapper.has( target ).length ) ) { + edd_toggle_advanced_order_filters(); + } +} + +/** + * Toggles the 'open' class on the advanced order filters overlay. + */ +function edd_toggle_advanced_order_filters() { + $( '#edd-advanced-filters' ).toggleClass( 'open' ); +} diff --git a/assets/js/admin/components/chosen/index.js b/assets/js/admin/components/chosen/index.js new file mode 100644 index 00000000000..7a6a41987c5 --- /dev/null +++ b/assets/js/admin/components/chosen/index.js @@ -0,0 +1,147 @@ +/* global _ */ + +/** + * Internal dependencies. + */ +import { getChosenVars } from 'utils/chosen.js'; + +jQuery( document ).ready( function( $ ) { + + // Globally apply to elements on the page. + $( '.edd-select-chosen' ).each( function() { + const el = $( this ); + el.chosen( getChosenVars( el ) ); + } ); + + $( '.edd-select-chosen .chosen-search input' ).each( function() { + // Bail if placeholder already set + if ( $( this ).attr( 'placeholder' ) ) { + return; + } + + const selectElem = $( this ).parent().parent().parent().prev( 'select.edd-select-chosen' ), + placeholder = selectElem.data( 'search-placeholder' ); + + if ( placeholder ) { + $( this ).attr( 'placeholder', placeholder ); + } + } ); + + // Add placeholders for Chosen input fields + $( '.chosen-choices' ).on( 'click', function() { + let placeholder = $( this ).parent().prev().data( 'search-placeholder' ); + if ( typeof placeholder === 'undefined' ) { + placeholder = edd_vars.type_to_search; + } + $( this ).children( 'li' ).children( 'input' ).attr( 'placeholder', placeholder ); + } ); + + // This fixes the Chosen box being 0px wide when the thickbox is opened + $( '#post' ).on( 'click', '.edd-thickbox', function() { + $( '.edd-select-chosen', '#choose-download' ).css( 'width', '100%' ); + } ); + + // Variables for setting up the typing timer + // Time in ms, Slow - 521ms, Moderate - 342ms, Fast - 300ms + let userInteractionInterval = 342, + typingTimerElements = '.edd-select-chosen .chosen-search input, .edd-select-chosen .search-field input', + typingTimer; + + // Replace options with search results + $( document.body ).on( 'keyup', typingTimerElements, _.debounce( function( e ) { + let element = $( this ), + val = element.val(), + container = element.closest( '.edd-select-chosen' ), + + select = container.prev(), + select_type = select.data( 'search-type' ), + no_bundles = container.hasClass( 'no-bundles' ), + variations = container.hasClass( 'variations' ), + variations_only = container.hasClass( 'variations-only' ), + + lastKey = e.which, + search_type = 'edd_download_search'; + + // String replace the chosen container IDs + container.attr( 'id' ).replace( '_chosen', '' ); + + // Detect if we have a defined search type, otherwise default to downloads + if ( typeof select_type !== 'undefined' ) { + // Don't trigger AJAX if this select has all options loaded + if ( 'no_ajax' === select_type ) { + return; + } + + search_type = 'edd_' + select_type + '_search'; + } else { + return; + } + + // Don't fire if short or is a modifier key (shift, ctrl, apple command key, or arrow keys) + if ( + ( val.length <= 3 && 'edd_download_search' === search_type ) || + ( + lastKey === 16 || + lastKey === 13 || + lastKey === 91 || + lastKey === 17 || + lastKey === 37 || + lastKey === 38 || + lastKey === 39 || + lastKey === 40 + ) + ) { + container.children( '.spinner' ).remove(); + return; + } + + // Maybe append a spinner + if ( ! container.children( '.spinner' ).length ) { + container.append( '' ); + } + + $.ajax( { + type: 'GET', + dataType: 'json', + url: ajaxurl, + data: { + s: val, + action: search_type, + no_bundles: no_bundles, + variations: variations, + variations_only: variations_only, + }, + + beforeSend: function() { + select.closest( 'ul.chosen-results' ).empty(); + }, + + success: function( data ) { + // Remove all options but those that are selected + $( 'option:not(:selected)', select ).remove(); + + // Add any option that doesn't already exist + $.each( data, function( key, item ) { + if ( ! $( 'option[value="' + item.id + '"]', select ).length ) { + select.append( '' ); + } + } ); + + // Get the text immediately before triggering an update. + // Any sooner will cause the text to jump around. + const val = element.val(); + + // Update the options + select.trigger( 'chosen:updated' ); + + element.val( val ); + }, + } ).fail( function( response ) { + if ( window.console && window.console.log ) { + console.log( response ); + } + } ).done( function( response ) { + container.children( '.spinner' ).remove(); + } ); + }, userInteractionInterval ) ); +} ); diff --git a/assets/js/admin/components/date-picker/index.js b/assets/js/admin/components/date-picker/index.js new file mode 100644 index 00000000000..840cb5f756f --- /dev/null +++ b/assets/js/admin/components/date-picker/index.js @@ -0,0 +1,26 @@ +/** + * Date picker + * + * This juggles a few CSS classes to avoid styling collisions with other + * third-party plugins. + */ +jQuery( document ).ready( function( $ ) { + const edd_datepicker = $( 'input.edd_datepicker' ); + + if ( edd_datepicker.length > 0 ) { + edd_datepicker + + // Disable autocomplete to avoid it covering the calendar + .attr( 'autocomplete', 'off' ) + + // Invoke the datepickers + .datepicker( { + dateFormat: edd_vars.date_picker_format, + beforeShow: function() { + $( '#ui-datepicker-div' ) + .removeClass( 'ui-datepicker' ) + .addClass( 'edd-datepicker' ); + }, + } ); + } +} ); diff --git a/assets/js/admin/components/dialog/index.js b/assets/js/admin/components/dialog/index.js new file mode 100644 index 00000000000..cd77c05b489 --- /dev/null +++ b/assets/js/admin/components/dialog/index.js @@ -0,0 +1,12 @@ +jQuery( document ).ready( function ( $ ) { + /** + * If any jQueryUI Dialog instances exist with edd-dialog, + * instantiate them. Each instance will add their own buttons and handlers later. + */ + $('.edd-dialog').dialog({ + autoOpen: false, + modal: true, + draggable: false, + closeOnEscape: true, + }); +} ); diff --git a/assets/js/admin/components/location/index.js b/assets/js/admin/components/location/index.js new file mode 100644 index 00000000000..ad53a255483 --- /dev/null +++ b/assets/js/admin/components/location/index.js @@ -0,0 +1,23 @@ +jQuery( document ).ready( function ( $ ) { + $( '.edd_countries_filter' ).on( 'change', function () { + const select = $( this ), + data = { + action: 'edd_get_shop_states', + country: select.val(), + nonce: select.data( 'nonce' ), + field_name: 'edd_regions_filter', + }; + + $.post( ajaxurl, data, function ( response ) { + $( 'select.edd_regions_filter' ).find( 'option:gt(0)' ).remove(); + + if ( 'nostates' !== response ) { + $( response ).find( 'option:gt(0)' ).appendTo( 'select.edd_regions_filter' ); + } + + $( 'select.edd_regions_filter' ).trigger( 'chosen:updated' ); + } ); + + return false; + } ); +} ); diff --git a/assets/js/admin/components/notifications/index.js b/assets/js/admin/components/notifications/index.js new file mode 100644 index 00000000000..88c44f0e962 --- /dev/null +++ b/assets/js/admin/components/notifications/index.js @@ -0,0 +1,125 @@ +/* global edd_vars */ + +document.addEventListener( 'alpine:init', () => { + Alpine.store( 'eddNotifications', { + isPanelOpen: false, + notificationsLoaded: false, + numberActiveNotifications: 0, + activeNotifications: [], + inactiveNotifications: [], + + init: function() { + const eddNotifications = this; + + /* + * The bubble starts out hidden until AlpineJS is initialized. Once it is, we remove + * the hidden class. This prevents a flash of the bubble's visibility in the event that there + * are no notifications. + */ + const notificationCountBubble = document.querySelector( '#edd-notification-button .edd-number' ); + if ( notificationCountBubble ) { + notificationCountBubble.classList.remove( 'edd-hidden' ); + } + + document.addEventListener( 'keydown', function( e ) { + if ( e.key === 'Escape' ) { + eddNotifications.closePanel(); + } + } ); + + const params = new URLSearchParams( window.location.search ); + + const triggerNotifications = params.has( 'notifications' ); + if ( triggerNotifications && 'true' === params.get( 'notifications' ) ) { + eddNotifications.openPanel(); + } + }, + + openPanel: function() { + const panelHeader = document.getElementById( 'edd-notifications-header' ); + + if ( this.notificationsLoaded ) { + this.isPanelOpen = true; + if ( panelHeader ) { + setTimeout( function() { + panelHeader.focus(); + } ); + } + + return; + } + + this.isPanelOpen = true; + + this.apiRequest( '/notifications', 'GET' ) + .then( data => { + this.activeNotifications = data.active; + this.inactiveNotifications = data.dismissed; + this.notificationsLoaded = true; + + if ( panelHeader ) { + panelHeader.focus(); + } + } ) + .catch( error => { + console.log( 'Notification error', error ); + } ); + }, + + closePanel: function() { + if ( ! this.isPanelOpen ) { + return; + } + + this.isPanelOpen = false; + + const notificationButton = document.getElementById( 'edd-notification-button' ); + if ( notificationButton ) { + notificationButton.focus(); + } + }, + + apiRequest: function( endpoint, method ) { + return fetch( edd_vars.restBase + endpoint, { + method: method, + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': edd_vars.restNonce + } + } ).then( response => { + if ( ! response.ok ) { + return Promise.reject( response ); + } + + /* + * Returning response.text() instead of response.json() because dismissing + * a notification doesn't return a JSON response, so response.json() will break. + */ + return response.text(); + //return response.json(); + } ).then( data => { + return data ? JSON.parse( data ) : null; + } ); + } , + + dismiss: function( event, index ) { + if ( 'undefined' === typeof this.activeNotifications[ index ] ) { + return; + } + + event.target.disabled = true; + + const notification = this.activeNotifications[ index ]; + + this.apiRequest( '/notifications/' + notification.id, 'DELETE' ) + .then( response => { + this.activeNotifications.splice( index, 1 ); + this.numberActiveNotifications = this.activeNotifications.length; + } ) + .catch( error => { + console.log( 'Dismiss error', error ); + } ); + } + } ); +} ); diff --git a/assets/js/admin/components/promos/index.js b/assets/js/admin/components/promos/index.js new file mode 100644 index 00000000000..ec331d1cbd0 --- /dev/null +++ b/assets/js/admin/components/promos/index.js @@ -0,0 +1,59 @@ +/* global ajaxurl */ + +jQuery( document ).ready( function( $ ) { + + /** + * Show overlay notices on a delay. + */ + const overlayNotice = $( '.edd-admin-notice-overlay' ); + const overlayNoticeClass = 'edd-promo-notice__overlay'; + if ( overlayNotice ) { + overlayNotice.wrap( '
' ); + $( document ).on( 'click', '.edd-promo-notice__trigger', function () { + $( '.' + overlayNoticeClass ).css( 'display', 'flex' ).hide().fadeIn(); + } ); + } + + /** + * Dismiss notices + */ + $( '.edd-promo-notice' ).each( function() { + const notice = $( this ); + + notice.on( 'click', '.edd-promo-notice-dismiss', function( e ) { + // Only prevent default behavior for buttons, not links. + if ( ! $( this ).attr( 'href' ) ) { + e.preventDefault(); + } + + $.ajax( { + type: 'POST', + data: { + action: 'edd_dismiss_promo_notice', + notice_id: notice.data( 'id' ), + nonce: notice.data( 'nonce' ), + lifespan: notice.data( 'lifespan' ) + }, + url: ajaxurl, + success: function( response ) { + if ( $( '.' + overlayNoticeClass ).length ) { + $( '.' + overlayNoticeClass ).fadeOut(); + $( '.edd-extension-manager__key-notice' ).hide(); + } else { + notice.slideUp(); + } + } + } ); + } ); + + $( document ).on( 'keydown', function ( event ) { + if ( !$( '.' + overlayNoticeClass ).length ) { + return; + } + if ( 27 === event.keyCode ) { + $( '.' + overlayNoticeClass ).fadeOut(); + $( '.edd-extension-manager__key-notice' ).hide(); + } + } ); + } ); +} ); diff --git a/assets/js/admin/components/sortable-list/index.js b/assets/js/admin/components/sortable-list/index.js new file mode 100644 index 00000000000..4b12665b823 --- /dev/null +++ b/assets/js/admin/components/sortable-list/index.js @@ -0,0 +1,34 @@ +/** + * Sortables + * + * This makes certain settings sortable, and attempts to stash the results + * in the nearest .edd-order input value. + */ +jQuery( document ).ready( function( $ ) { + const edd_sortables = $( 'ul.edd-sortable-list' ); + + if ( edd_sortables.length > 0 ) { + edd_sortables.sortable( { + axis: 'y', + items: 'li', + cursor: 'move', + tolerance: 'pointer', + containment: 'parent', + distance: 2, + opacity: 0.7, + scroll: true, + + /** + * When sorting stops, assign the value to the previous input. + * This input should be a hidden text field + */ + stop: function() { + const keys = $.map( $( this ).children( 'li' ), function( el ) { + return $( el ).data( 'key' ); + } ); + + $( this ).prev( 'input.edd-order' ).val( keys ); + }, + } ); + } +} ); diff --git a/assets/js/admin/components/taxonomies/index.js b/assets/js/admin/components/taxonomies/index.js new file mode 100644 index 00000000000..bde84f5879b --- /dev/null +++ b/assets/js/admin/components/taxonomies/index.js @@ -0,0 +1,7 @@ +/* global jQuery */ + +jQuery( document ).ready( function ( $ ) { + if ( $( 'body' ).hasClass( 'taxonomy-download_category' ) || $( 'body' ).hasClass( 'taxonomy-download_tag' ) ) { + $( '.nav-tab-wrapper, .nav-tab-wrapper + br' ).detach().insertAfter( '.wp-header-end' ); + } +} ); diff --git a/assets/js/admin/components/tooltips/index.js b/assets/js/admin/components/tooltips/index.js new file mode 100644 index 00000000000..e24ce639dc0 --- /dev/null +++ b/assets/js/admin/components/tooltips/index.js @@ -0,0 +1,28 @@ +/** + * Attach tooltips + * + * @param {string} selector + */ +export const edd_attach_tooltips = function( selector ) { + selector.tooltip( { + content: function() { + return $( this ).prop( 'title' ); + }, + tooltipClass: 'edd-ui-tooltip', + position: { + my: 'center top', + at: 'center bottom+10', + collision: 'flipfit', + }, + hide: { + duration: 200, + }, + show: { + duration: 200, + }, + } ); +}; + +jQuery( document ).ready( function( $ ) { + edd_attach_tooltips( $( '.edd-help-tip' ) ); +} ); diff --git a/assets/js/admin/components/user-search/index.js b/assets/js/admin/components/user-search/index.js new file mode 100644 index 00000000000..3ef1fd80f2f --- /dev/null +++ b/assets/js/admin/components/user-search/index.js @@ -0,0 +1,69 @@ +jQuery( function ( $ ) { + // AJAX user search + $( '.edd-ajax-user-search' ) + + // Search + .on( 'keyup focus', function () { + let user_search = $( this ).val(), + exclude = ''; + + if ( $( this ).data( 'exclude' ) ) { + exclude = $( this ).data( 'exclude' ); + } + + $( '.edd_user_search_wrap' ).addClass( 'loading' ); + + const data = { + action: 'edd_search_users', + user_name: user_search, + exclude: exclude, + }; + + $.ajax( { + type: 'POST', + data: data, + dataType: 'json', + url: ajaxurl, + + success: function( search_response ) { + $( '.edd_user_search_wrap' ).removeClass( 'loading' ); + $( '.edd_user_search_results' ).removeClass( 'hidden' ); + $( '.edd_user_search_results span' ).html( '' ); + if ( search_response.results ) { + $( search_response.results ).appendTo( '.edd_user_search_results span' ); + } + }, + } ); + } ) + + // Hide + .on( 'blur', function () { + if ( edd_user_search_mouse_down ) { + edd_user_search_mouse_down = false; + } else { + $( this ).removeClass( 'loading' ); + $( '.edd_user_search_results' ).addClass( 'hidden' ); + } + } ); + + $( document.body ).on( 'click.eddSelectUser', '.edd_user_search_results span a', function( e ) { + e.preventDefault(); + const login = $( this ).data( 'login' ); + $( '.edd-ajax-user-search' ).val( login ); + $( '.edd_user_search_results' ).addClass( 'hidden' ); + $( '.edd_user_search_results span' ).html( '' ); + } ); + + $( document.body ).on( 'click.eddCancelUserSearch', '.edd_user_search_results a.edd-ajax-user-cancel', function( e ) { + e.preventDefault(); + $( '.edd-ajax-user-search' ).val( '' ); + $( '.edd_user_search_results' ).addClass( 'hidden' ); + $( '.edd_user_search_results span' ).html( '' ); + } ); + + // Cancel user-search.blur when picking a user + var edd_user_search_mouse_down = false; + $( '.edd_user_search_results' ).on( 'mousedown', function () { + edd_user_search_mouse_down = true; + } ); +} ); diff --git a/assets/js/admin/components/vertical-sections/index.js b/assets/js/admin/components/vertical-sections/index.js new file mode 100644 index 00000000000..9d4d5f5cc86 --- /dev/null +++ b/assets/js/admin/components/vertical-sections/index.js @@ -0,0 +1,61 @@ +jQuery( document ).ready( function( $ ) { + + const sectionSelector = '.edd-vertical-sections.use-js'; + // If the current screen doesn't have JS sections, return. + if ( 0 === $( sectionSelector ).length ) { + return; + } + + // Hides the section content. + $( `${ sectionSelector } .section-content` ).hide(); + + const hash = window.location.hash; + if ( hash && hash.includes( 'edd_' ) ) { + // Show the section content related to the URL. + $( sectionSelector ).find( hash ).show(); + + // Set the aria-selected for section titles to be false + $( `${ sectionSelector } .section-title` ).attr( 'aria-selected', 'false' ).removeClass( 'section-title--is-active' ); + + // Set aria-selected true on the related link. + $( sectionSelector ).find( '.section-title a[href="' + hash + '"]' ).parents( '.section-title' ).attr( 'aria-selected', 'true' ).addClass( 'section-title--is-active' ); + + } else { + // Shows the first section's content. + $( `${ sectionSelector } .section-content:first-child` ).show(); + + // Makes the 'aria-selected' attribute true for the first section nav item. + $( `${ sectionSelector } .section-nav li:first-child` ).attr( 'aria-selected', 'true' ).addClass( 'section-title--is-active' ); + } + + // When a section nav item is clicked. + $( `${ sectionSelector } .section-nav li a` ).on( 'click', + function( j ) { + // Prevent the default browser action when a link is clicked. + j.preventDefault(); + + // Get the `href` attribute of the item. + const them = $( this ), + href = them.attr( 'href' ), + rents = them.parents( '.edd-vertical-sections' ); + + // Hide all section content. + rents.find( '.section-content' ).hide(); + + // Find the section content that matches the section nav item and show it. + rents.find( href ).show(); + + // Set the `aria-selected` attribute to false for all section nav items. + rents.find( '.section-title' ).attr( 'aria-selected', 'false' ).removeClass( 'section-title--is-active' ); + + // Set the `aria-selected` attribute to true for this section nav item. + them.parent().attr( 'aria-selected', 'true' ).addClass( 'section-title--is-active' ); + + // Maybe re-Chosen + rents.find( 'div.chosen-container' ).css( 'width', '100%' ); + + // Add the current "link" to the page URL + window.history.pushState( 'object or string', '', href ); + } + ); // click() +} ); diff --git a/assets/js/admin/customers/index.js b/assets/js/admin/customers/index.js new file mode 100644 index 00000000000..625fa3d5143 --- /dev/null +++ b/assets/js/admin/customers/index.js @@ -0,0 +1,140 @@ +/** + * Customer management screen JS + */ +var EDD_Customer = { + + vars: { + customer_card_wrap_editable: $( '#edit-customer-info .editable' ), + customer_card_wrap_edit_item: $( '#edit-customer-info .edit-item' ), + user_id: $( 'input[name="customerinfo[user_id]"]' ), + }, + init: function() { + this.edit_customer(); + this.add_email(); + this.user_search(); + this.remove_user(); + this.cancel_edit(); + this.change_country(); + this.delete_checked(); + }, + edit_customer: function() { + $( document.body ).on( 'click', '#edit-customer', function( e ) { + e.preventDefault(); + + EDD_Customer.vars.customer_card_wrap_editable.hide(); + EDD_Customer.vars.customer_card_wrap_edit_item.show().css( 'display', 'block' ); + } ); + }, + add_email: function() { + $( document.body ).on( 'click', '#add-customer-email', function( e ) { + e.preventDefault(); + const button = $( this ), + wrapper = button.parent().parent().parent().parent(), + customer_id = wrapper.find( 'input[name="customer-id"]' ).val(), + email = wrapper.find( 'input[name="additional-email"]' ).val(), + primary = wrapper.find( 'input[name="make-additional-primary"]' ).is( ':checked' ), + nonce = wrapper.find( 'input[name="add_email_nonce"]' ).val(), + postData = { + edd_action: 'customer-add-email', + customer_id: customer_id, + email: email, + primary: primary, + _wpnonce: nonce, + }; + + wrapper.parent().find( '.notice-container' ).remove(); + wrapper.find( '.spinner' ).css( 'visibility', 'visible' ); + button.attr( 'disabled', true ); + + $.post( ajaxurl, postData, function( response ) { + setTimeout( function() { + if ( true === response.success ) { + window.location.href = response.redirect; + } else { + button.attr( 'disabled', false ); + wrapper.before( '

' + response.message + '

' ); + wrapper.find( '.spinner' ).css( 'visibility', 'hidden' ); + } + }, 342 ); + }, 'json' ); + } ); + }, + user_search: function() { + // Upon selecting a user from the dropdown, we need to update the User ID + $( document.body ).on( 'click.eddSelectUser', '.edd_user_search_results a', function( e ) { + e.preventDefault(); + const user_id = $( this ).data( 'userid' ); + EDD_Customer.vars.user_id.val( user_id ); + } ); + }, + remove_user: function() { + $( document.body ).on( 'click', '#disconnect-customer', function( e ) { + e.preventDefault(); + + if ( confirm( edd_vars.disconnect_customer ) ) { + const customer_id = $( 'input[name="customerinfo[id]"]' ).val(), + postData = { + edd_action: 'disconnect-userid', + customer_id: customer_id, + _wpnonce: $( '#edit-customer-info #_wpnonce' ).val(), + }; + + $.post( ajaxurl, postData, function( response ) { + // Weird + window.location.href = window.location.href; + }, 'json' ); + } + } ); + }, + cancel_edit: function() { + $( document.body ).on( 'click', '#edd-edit-customer-cancel', function( e ) { + e.preventDefault(); + EDD_Customer.vars.customer_card_wrap_edit_item.hide(); + EDD_Customer.vars.customer_card_wrap_editable.show(); + + $( '.edd_user_search_results' ).html( '' ); + } ); + }, + change_country: function() { + $( 'select[name="customerinfo[country]"]' ).on( 'change', function () { + const select = $( this ), + state_input = $( ':input[name="customerinfo[region]"]' ), + data = { + action: 'edd_get_shop_states', + country: select.val(), + nonce: select.data( 'nonce' ), + field_name: 'customerinfo[region]', + }; + + $.post( ajaxurl, data, function( response ) { + console.log( response ); + if ( 'nostates' === response ) { + state_input.replaceWith( '' ); + } else { + state_input.replaceWith( response ); + } + } ); + + return false; + } ); + }, + delete_checked: function() { + $( '#edd-customer-delete-confirm' ).on( 'change', function () { + const records_input = $( '#edd-customer-delete-records' ); + const submit_button = $( '#edd-delete-customer' ); + + if ( $( this ).prop( 'checked' ) ) { + records_input.attr( 'disabled', false ); + submit_button.attr( 'disabled', false ); + } else { + records_input.attr( 'disabled', true ); + records_input.prop( 'checked', false ); + submit_button.attr( 'disabled', true ); + } + } ); + }, +}; + +jQuery( document ).ready( function( $ ) { + EDD_Customer.init(); +} ); diff --git a/assets/js/admin/dashboard/index.js b/assets/js/admin/dashboard/index.js new file mode 100644 index 00000000000..55ab3c25394 --- /dev/null +++ b/assets/js/admin/dashboard/index.js @@ -0,0 +1,14 @@ +jQuery( document ).ready( function( $ ) { + if ( $( '#edd_dashboard_sales' ).length ) { + $.ajax( { + type: 'GET', + data: { + action: 'edd_load_dashboard_widget', + }, + url: ajaxurl, + success: function( response ) { + $( '#edd_dashboard_sales .edd-loading' ).html( response ); + }, + } ); + } +} ); diff --git a/assets/js/admin/discounts/index.js b/assets/js/admin/discounts/index.js new file mode 100644 index 00000000000..6eae1616afa --- /dev/null +++ b/assets/js/admin/discounts/index.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies. + */ +import { jQueryReady } from 'utils/jquery.js'; + +/** + * DOM ready. + */ +jQueryReady( () => { + const products = $( '#edd_products' ); + if ( ! products ) { + return; + } + + /** + * Show/hide conditions based on input value. + */ + products.on( 'change', function () { + $( '#edd-discount-product-conditions' ).toggle( null !== products.val() ); + } ); +} ); diff --git a/assets/js/admin/downloads/bulk-edit.js b/assets/js/admin/downloads/bulk-edit.js new file mode 100644 index 00000000000..a106136236d --- /dev/null +++ b/assets/js/admin/downloads/bulk-edit.js @@ -0,0 +1,18 @@ +jQuery( document ).ready( function( $ ) { + $( 'body' ).on( 'click', '#the-list .editinline', function() { + let post_id = $( this ).closest( 'tr' ).attr( 'id' ); + + post_id = post_id.replace( 'post-', '' ); + + const $edd_inline_data = $( '#post-' + post_id ); + + const regprice = $edd_inline_data.find( '.column-price .downloadprice-' + post_id ).val(); + + // If variable priced product disable editing, otherwise allow price changes + if ( regprice !== $( '#post-' + post_id + '.column-price .downloadprice-' + post_id ).val() ) { + $( '.regprice', '#edd-download-data' ).val( regprice ).attr( 'disabled', false ); + } else { + $( '.regprice', '#edd-download-data' ).val( edd_vars.quick_edit_warning ).attr( 'disabled', 'disabled' ); + } + } ); +} ); diff --git a/assets/js/admin/downloads/index.js b/assets/js/admin/downloads/index.js new file mode 100644 index 00000000000..206af19b770 --- /dev/null +++ b/assets/js/admin/downloads/index.js @@ -0,0 +1,411 @@ +/** + * Internal dependencies. + */ +import { getChosenVars } from 'utils/chosen.js'; +import { edd_attach_tooltips } from 'admin/components/tooltips'; +import './bulk-edit.js'; + +/** + * Download Configuration Metabox + */ +var EDD_Download_Configuration = { + init: function() { + this.add(); + this.move(); + this.remove(); + this.type(); + this.prices(); + this.files(); + this.updatePrices(); + this.showAdvanced(); + }, + clone_repeatable: function( row ) { + // Retrieve the highest current key + let key = 1; + let highest = 1; + row.parent().find( '.edd_repeatable_row' ).each( function() { + const current = $( this ).data( 'key' ); + if ( parseInt( current ) > highest ) { + highest = current; + } + } ); + key = highest += 1; + + const clone = row.clone(); + + clone.removeClass( 'edd_add_blank' ); + + clone.attr( 'data-key', key ); + clone.find( 'input, select, textarea' ).val( '' ).each( function() { + let elem = $( this ), + name = elem.attr( 'name' ), + id = elem.attr( 'id' ); + + if ( name ) { + name = name.replace( /\[(\d+)\]/, '[' + parseInt( key ) + ']' ); + elem.attr( 'name', name ); + } + + elem.attr( 'data-key', key ); + + if ( typeof id !== 'undefined' ) { + id = id.replace( /(\d+)/, parseInt( key ) ); + elem.attr( 'id', id ); + } + } ); + + /** manually update any select box values */ + clone.find( 'select' ).each( function() { + $( this ).val( row.find( 'select[name="' + $( this ).attr( 'name' ) + '"]' ).val() ); + } ); + + /** manually uncheck any checkboxes */ + clone.find( 'input[type="checkbox"]' ).each( function() { + // Make sure checkboxes are unchecked when cloned + const checked = $( this ).is( ':checked' ); + if ( checked ) { + $( this ).prop( 'checked', false ); + } + + // reset the value attribute to 1 in order to properly save the new checked state + $( this ).val( 1 ); + } ); + + clone.find( 'span.edd_price_id' ).each( function() { + $( this ).text( parseInt( key ) ); + } ); + + clone.find( 'input.edd_repeatable_index' ).each( function() { + $( this ).val( parseInt( $( this ).data( 'key' ) ) ); + } ); + + clone.find( 'span.edd_file_id' ).each( function() { + $( this ).text( parseInt( key ) ); + } ); + + clone.find( '.edd_repeatable_default_input' ).each( function() { + $( this ).val( parseInt( key ) ).removeAttr( 'checked' ); + } ); + + clone.find( '.edd_repeatable_condition_field' ).each( function() { + $( this ).find( 'option:eq(0)' ).prop( 'selected', 'selected' ); + } ); + + clone.find( 'label' ).each( function () { + var labelFor = $( this ).attr( 'for' ); + if ( labelFor ) { + $( this ).attr( 'for', labelFor.replace( /(\d+)/, parseInt( key ) ) ); + } + } ); + + // Remove Chosen elements + clone.find( '.search-choice' ).remove(); + clone.find( '.chosen-container' ).remove(); + edd_attach_tooltips( clone.find( '.edd-help-tip' ) ); + + return clone; + }, + + add: function() { + $( document.body ).on( 'click', '.edd_add_repeatable', function( e ) { + e.preventDefault(); + + const button = $( this ), + row = button.closest( '.edd_repeatable_table' ).find( '.edd_repeatable_row' ).last(), + clone = EDD_Download_Configuration.clone_repeatable( row ); + + clone.insertAfter( row ).find( 'input, textarea, select' ).filter( ':visible' ).eq( 0 ).focus(); + + // Setup chosen fields again if they exist + clone.find( '.edd-select-chosen' ).each( function() { + const el = $( this ); + el.chosen( getChosenVars( el ) ); + } ); + clone.find( '.edd-select-chosen' ).css( 'width', '100%' ); + clone.find( '.edd-select-chosen .chosen-search input' ).attr( 'placeholder', edd_vars.search_placeholder ); + } ); + }, + + move: function() { + $( '.edd_repeatable_table .edd-repeatables-wrap' ).sortable( { + axis: 'y', + handle: '.edd-draghandle-anchor', + items: '.edd_repeatable_row', + cursor: 'move', + tolerance: 'pointer', + containment: 'parent', + distance: 2, + opacity: 0.7, + scroll: true, + + update: function() { + let count = 0; + $( this ).find( '.edd_repeatable_row' ).each( function() { + $( this ).find( 'input.edd_repeatable_index' ).each( function() { + $( this ).val( count ); + } ); + count++; + } ); + }, + start: function( e, ui ) { + ui.placeholder.height( ui.item.height() - 2 ); + }, + } ); + }, + + remove: function() { + $( document.body ).on( 'click', '.edd-remove-row, .edd_remove_repeatable', function( e ) { + e.preventDefault(); + + let row = $( this ).parents( '.edd_repeatable_row' ), + count = row.parent().find( '.edd_repeatable_row' ).length, + type = $( this ).data( 'type' ), + repeatable = 'div.edd_repeatable_' + type + 's', + focusElement, + focusable, + firstFocusable; + + // Set focus on next element if removing the first row. Otherwise set focus on previous element. + if ( $( this ).is( '.ui-sortable .edd_repeatable_row:first-child .edd-remove-row, .ui-sortable .edd_repeatable_row:first-child .edd_remove_repeatable' ) ) { + focusElement = row.next( '.edd_repeatable_row' ); + } else { + focusElement = row.prev( '.edd_repeatable_row' ); + } + + focusable = focusElement.find( 'select, input, textarea, button' ).filter( ':visible' ); + firstFocusable = focusable.eq( 0 ); + + if ( type === 'price' ) { + const price_row_id = row.data( 'key' ); + /** remove from price condition */ + $( '.edd_repeatable_condition_field option[value="' + price_row_id + '"]' ).remove(); + } + + if ( count > 1 ) { + $( 'input, select', row ).val( '' ); + row.fadeOut( 'fast' ).remove(); + firstFocusable.focus(); + } else { + switch ( type ) { + case 'price' : + alert( edd_vars.one_price_min ); + break; + case 'file' : + $( 'input, select', row ).val( '' ); + break; + default: + alert( edd_vars.one_field_min ); + break; + } + } + + /* re-index after deleting */ + $( repeatable ).each( function( rowIndex ) { + $( this ).find( 'input, select' ).each( function() { + let name = $( this ).attr( 'name' ); + name = name.replace( /\[(\d+)\]/, '[' + rowIndex + ']' ); + $( this ).attr( 'name', name ).attr( 'id', name ); + } ); + } ); + } ); + }, + + type: function() { + $( document.body ).on( 'change', '#_edd_product_type', function( e ) { + const edd_products = $( '#edd_products' ), + edd_download_files = $( '#edd_download_files' ), + edd_download_limit_wrap = $( '#edd_download_limit_wrap' ); + + if ( 'bundle' === $( this ).val() ) { + edd_products.show(); + edd_download_files.hide(); + edd_download_limit_wrap.hide(); + } else { + edd_products.hide(); + edd_download_files.show(); + edd_download_limit_wrap.show(); + } + } ); + }, + + prices: function() { + $( document.body ).on( 'change', '#edd_variable_pricing', function( e ) { + const checked = $( this ).is( ':checked' ), + single = $( '#edd_regular_price_field' ), + variable = $( '#edd_variable_price_fields, .edd_repeatable_table .pricing' ), + bundleRow = $( '.edd-bundled-product-row, .edd-repeatable-row-standard-fields' ); + + if ( checked ) { + single.hide(); + variable.show(); + bundleRow.addClass( 'has-variable-pricing' ); + } else { + single.show(); + variable.hide(); + bundleRow.removeClass( 'has-variable-pricing' ); + } + } ); + }, + + files: function() { + var file_frame; + window.formfield = ''; + + $( document.body ).on( 'click', '.edd_upload_file_button', function( e ) { + e.preventDefault(); + + const button = $( this ); + + window.formfield = button.closest( '.edd_repeatable_upload_wrapper' ); + + // If the media frame already exists, reopen it. + if ( file_frame ) { + file_frame.open(); + return; + } + + // Create the media frame. + file_frame = wp.media.frames.file_frame = wp.media( { + title: button.data( 'uploader-title' ), + frame: 'post', + state: 'insert', + button: { text: button.data( 'uploader-button-text' ) }, + multiple: $( this ).data( 'multiple' ) === '0' ? false : true, // Set to true to allow multiple files to be selected + } ); + + file_frame.on( 'menu:render:default', function( view ) { + // Store our views in an object. + const views = {}; + + // Unset default menu items + view.unset( 'library-separator' ); + view.unset( 'gallery' ); + view.unset( 'featured-image' ); + view.unset( 'embed' ); + view.unset( 'playlist' ); + view.unset( 'video-playlist' ); + + // Initialize the views in our view object. + view.set( views ); + } ); + + // When an image is selected, run a callback. + file_frame.on( 'insert', function() { + const selection = file_frame.state().get( 'selection' ); + selection.each( function( attachment, index ) { + attachment = attachment.toJSON(); + + let selectedSize = 'image' === attachment.type ? $( '.attachment-display-settings .size option:selected' ).val() : false, + selectedURL = attachment.url, + selectedName = attachment.title.length > 0 ? attachment.title : attachment.filename; + + if ( selectedSize && typeof attachment.sizes[ selectedSize ] !== 'undefined' ) { + selectedURL = attachment.sizes[ selectedSize ].url; + } + + if ( 'image' === attachment.type ) { + if ( selectedSize && typeof attachment.sizes[ selectedSize ] !== 'undefined' ) { + selectedName = selectedName + '-' + attachment.sizes[ selectedSize ].width + 'x' + attachment.sizes[ selectedSize ].height; + } else { + selectedName = selectedName + '-' + attachment.width + 'x' + attachment.height; + } + } + + if ( 0 === index ) { + // place first attachment in field + window.formfield.find( '.edd_repeatable_attachment_id_field' ).val( attachment.id ); + window.formfield.find( '.edd_repeatable_thumbnail_size_field' ).val( selectedSize ); + window.formfield.find( '.edd_repeatable_upload_field' ).val( selectedURL ); + window.formfield.find( '.edd_repeatable_name_field' ).val( selectedName ); + } else { + // Create a new row for all additional attachments + const row = window.formfield, + clone = EDD_Download_Configuration.clone_repeatable( row ); + + clone.find( '.edd_repeatable_attachment_id_field' ).val( attachment.id ); + clone.find( '.edd_repeatable_thumbnail_size_field' ).val( selectedSize ); + clone.find( '.edd_repeatable_upload_field' ).val( selectedURL ); + clone.find( '.edd_repeatable_name_field' ).val( selectedName ); + clone.insertAfter( row ); + } + } ); + } ); + + // Finally, open the modal + file_frame.open(); + } ); + + // @todo Break this out and remove jQuery. + $( '.edd_repeatable_upload_field' ) + .on( 'focus', function() { + const input = $( this ); + + input.data( 'originalFile', input.val() ); + } ) + .on( 'change', function() { + const input = $( this ); + const originalFile = input.data( 'originalFile' ); + + if ( originalFile !== input.val() ) { + input + .closest( '.edd-repeatable-row-standard-fields' ) + .find( '.edd_repeatable_attachment_id_field' ) + .val( 0 ); + } + } ); + + var file_frame; + window.formfield = ''; + }, + + updatePrices: function() { + $( '#edd_price_fields' ).on( 'keyup', '.edd_variable_prices_name', function() { + const key = $( this ).parents( '.edd_repeatable_row' ).data( 'key' ), + name = $( this ).val(), + field_option = $( '.edd_repeatable_condition_field option[value=' + key + ']' ); + + if ( field_option.length > 0 ) { + field_option.text( name ); + } else { + $( '.edd_repeatable_condition_field' ).append( + $( '' ) + .attr( 'value', key ) + .text( name ) + ); + } + } ); + }, + + showAdvanced: function() { + // Toggle display of entire custom settings section for a price option + $( document.body ).on( 'click', '.toggle-custom-price-option-section', function( e ) { + e.preventDefault(); + + const toggle = $( this ), + show = toggle.html() === edd_vars.show_advanced_settings ? + true : + false; + + if ( show ) { + toggle.html( edd_vars.hide_advanced_settings ); + } else { + toggle.html( edd_vars.show_advanced_settings ); + } + + const header = toggle.parents( '.edd-repeatable-row-header' ); + header.siblings( '.edd-custom-price-option-sections-wrap' ).slideToggle(); + + let first_input; + if ( show ) { + first_input = $( ':input:not(input[type=button],input[type=submit],button):visible:first', header.siblings( '.edd-custom-price-option-sections-wrap' ) ); + } else { + first_input = $( ':input:not(input[type=button],input[type=submit],button):visible:first', header.siblings( '.edd-repeatable-row-standard-fields' ) ); + } + first_input.focus(); + } ); + } +}; + +jQuery( document ).ready( function( $ ) { + EDD_Download_Configuration.init(); +} ); diff --git a/assets/js/admin/index.js b/assets/js/admin/index.js new file mode 100755 index 00000000000..b72bc0da2da --- /dev/null +++ b/assets/js/admin/index.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies. + */ +import './components/date-picker'; +import './components/dialog'; +import './components/chosen'; +import './components/tooltips'; +import './components/vertical-sections'; +import './components/sortable-list'; +import './components/user-search'; +import './components/advanced-filters'; +import './components/taxonomies'; +import './components/location'; +import './components/promos'; +import './components/notifications'; +// Note: This is not common across all admin pages and at some point this code will be moved to a new file that only loads on the orders table page. +import './orders/list-table'; diff --git a/assets/js/admin/notes/index.js b/assets/js/admin/notes/index.js new file mode 100644 index 00000000000..7594c9474ab --- /dev/null +++ b/assets/js/admin/notes/index.js @@ -0,0 +1,134 @@ +/** + * Notes + */ +const EDD_Notes = { + init: function() { + this.enter_key(); + this.add_note(); + this.remove_note(); + }, + + enter_key: function() { + $( document.body ).on( 'keydown', '#edd-note', function( e ) { + if ( e.keyCode === 13 && ( e.metaKey || e.ctrlKey ) ) { + e.preventDefault(); + $( '#edd-add-note' ).click(); + } + } ); + }, + + /** + * Ajax handler for adding new notes + * + * @since 3.0 + */ + add_note: function() { + $( '#edd-add-note' ).on( 'click', function( e ) { + e.preventDefault(); + + const edd_button = $( this ), + edd_note = $( '#edd-note' ), + edd_notes = $( '.edd-notes' ), + edd_no_notes = $( '.edd-no-notes' ), + edd_spinner = $( '.edd-add-note .spinner' ), + edd_note_nonce = $( '#edd_note_nonce' ); + + const postData = { + action: 'edd_add_note', + nonce: edd_note_nonce.val(), + object_id: edd_button.data( 'object-id' ), + object_type: edd_button.data( 'object-type' ), + note: edd_note.val(), + }; + + if ( postData.note ) { + edd_button.prop( 'disabled', true ); + edd_spinner.css( 'visibility', 'visible' ); + + $.ajax( { + type: 'POST', + data: postData, + url: ajaxurl, + success: function( response ) { + let res = wpAjax.parseAjaxResponse( response ); + res = res.responses[ 0 ]; + + edd_notes.append( res.data ); + edd_no_notes.hide(); + edd_button.prop( 'disabled', false ); + edd_spinner.css( 'visibility', 'hidden' ); + edd_note.val( '' ); + }, + } ).fail( function( data ) { + if ( window.console && window.console.log ) { + console.log( data ); + } + edd_button.prop( 'disabled', false ); + edd_spinner.css( 'visibility', 'hidden' ); + } ); + } else { + const border_color = edd_note.css( 'border-color' ); + + edd_note.css( 'border-color', 'red' ); + + setTimeout( function() { + edd_note.css( 'border-color', border_color ); + }, userInteractionInterval ); + } + } ); + }, + + /** + * Ajax handler for deleting existing notes + * + * @since 3.0 + */ + remove_note: function() { + $( document.body ).on( 'click', '.edd-delete-note', function( e ) { + e.preventDefault(); + + const edd_link = $( this ), + edd_notes = $( '.edd-note' ), + edd_note = edd_link.parents( '.edd-note' ), + edd_no_notes = $( '.edd-no-notes' ), + edd_note_nonce = $( '#edd_note_nonce' ); + + if ( confirm( edd_vars.delete_note ) ) { + const postData = { + action: 'edd_delete_note', + nonce: edd_note_nonce.val(), + note_id: edd_link.data( 'note-id' ), + }; + + edd_note.addClass( 'deleting' ); + + $.ajax( { + type: 'POST', + data: postData, + url: ajaxurl, + success: function( response ) { + if ( '1' === response ) { + edd_note.remove(); + } + + if ( edd_notes.length === 1 ) { + edd_no_notes.show(); + } + + return false; + }, + } ).fail( function( data ) { + if ( window.console && window.console.log ) { + console.log( data ); + } + edd_note.removeClass( 'deleting' ); + } ); + return true; + } + } ); + }, +}; + +jQuery( document ).ready( function( $ ) { + EDD_Notes.init(); +} ); diff --git a/assets/js/admin/notices/index.js b/assets/js/admin/notices/index.js new file mode 100644 index 00000000000..807e7aefb20 --- /dev/null +++ b/assets/js/admin/notices/index.js @@ -0,0 +1,28 @@ +/** + * Deletes the debug log file and disables logging. + */ +; ( function ( document, $ ) { + 'use strict'; + + $( '#edd-disable-debug-log' ).on( 'click', function ( e ) { + e.preventDefault(); + $( this ).attr( 'disabled', true ); + var notice = $( '#edd-debug-log-notice' ); + $.ajax( { + type: "GET", + data: { + action: 'edd_disable_debugging', + nonce: $( '#edd_debug_log_delete' ).val(), + }, + url: ajaxurl, + success: function ( response ) { + notice.empty().append( response.data ); + setTimeout( function () { + notice.slideUp(); + }, 3000 ); + } + } ).fail( function ( response ) { + notice.empty().append( response.responseJSON.data ); + } ); + } ); +} )( document, jQuery ); diff --git a/assets/js/admin/onboarding/index.js b/assets/js/admin/onboarding/index.js new file mode 100644 index 00000000000..fb49e59ac02 --- /dev/null +++ b/assets/js/admin/onboarding/index.js @@ -0,0 +1,753 @@ +/** + * Onboarding Wizard. + */ + +/** + * Internal dependencies + */ +import { edd_attach_tooltips as setup_tooltips } from 'admin/components/tooltips'; + +var EDD_Onboarding = { + + vars: { + nonce: '', + }, + + /** + * Run when page is loaded + * to initialize logic. + * + * @since 3.1 + * + */ + init: function() { + EDD_Onboarding.vars.nonce = $( '#_wpnonce' ).val(); + + this.init_step_buttons(); + this.init_upload_buttons(); + this.start_onboarding(); + + // Run current step logic. + let current_step = EDD_Onboarding.get_step_class( $( '.edd-onboarding_current-step' ).val() ); + if ( current_step ) { + EDD_Onboarding[ current_step ].init(); + } + }, + + /** + * Toggle the loading overlay. + * + * @since 3.1 + * + * @param {bool} state True to show the loader, false to hide it. + */ + loading_state: function( state ) { + $( '.edd-onboarding__loading-status' ).empty(); + $( '.edd-onboarding__loading' ).toggle( state ); + $( '.edd-onboarding' ).toggleClass( 'edd-onboarding__loading-in-progress', state ); + }, + + /** + * Attach listeners to the control buttons. + * + * @since 3.1 + * + */ + init_step_buttons: function() { + // Go back button. + $( document.body ).on( 'click', '.edd-onboarding__button-back', function( e ) { + e.preventDefault(); + EDD_Onboarding.load_step( $( '.edd-onboarding_current-previous-step' ).val() ); + } ); + + // Skip step button. + $( document.body ).on( 'click', '.edd-onboarding__button-skip-step', function( e ) { + e.preventDefault(); + EDD_Onboarding.next_step( true ); + } ); + + // Save button. + $( document.body ).on( 'click', '.edd-onboarding__button-save-step', function( e ) { + e.preventDefault(); + let step_class = EDD_Onboarding.get_step_class( $( '.edd-onboarding_current-step' ).val() ); + if ( step_class ) { + EDD_Onboarding[ step_class ].save().then( function() { + EDD_Onboarding.next_step(); + } ); + } + } ); + + // Close and exit. + $( document.body ).on( 'click', '.edd-onboarding__dismiss', function ( e ) { + EDD_Onboarding.onboarding_skipped( true, e.target ); + } ); + }, + + /** + * Upload image buttons. + * + * @since 3.1 + * + */ + init_upload_buttons: function() { + let file_frame; + window.form_upload_field = false; + $( document.body ).on( 'click', '.edd_settings_upload_button', function( e ) { + e.preventDefault(); + + const button = $( this ); + + window.form_upload_field = $( button.data('input') ); + + // If the media frame already exists, reopen it. + if ( file_frame ) { + //file_frame.uploader.uploader.param( 'post_id', set_to_post_id ); + file_frame.open(); + return; + } + + // Create the media frame. + file_frame = wp.media.frames.file_frame = wp.media( { + title: button.data( 'uploader_title' ), + library: { type: 'image' }, + button: { text: button.data( 'uploader_button_text' ) }, + multiple: false, + } ); + + file_frame.on( 'menu:render:default', function( view ) { + // Store our views in an object. + const views = {}; + + // Unset default menu items + view.unset( 'library-separator' ); + view.unset( 'gallery' ); + view.unset( 'featured-image' ); + view.unset( 'embed' ); + view.unset( 'playlist' ); + view.unset( 'video-playlist' ); + + // Initialize the views in our view object. + view.set( views ); + } ); + + // When an image is selected, run a callback. + file_frame.on( 'select', function() { + const selection = file_frame.state().get( 'selection' ); + selection.each( function( attachment, index ) { + attachment = attachment.toJSON(); + window.form_upload_field.val( attachment.url ); + // Check if we have a field for attachment ID connected to this upload button. + if ( window.form_upload_field.data( 'attachment-id-field' ) ) { + $( window.form_upload_field.data( 'attachment-id-field' ) ).val( attachment.id ); + } + } ); + } ); + + // Finally, open the modal + file_frame.open(); + } ); + }, + + /** + * Attach listeners for when + * user starts its onboarding process. + * + * @since 3.1 + * + */ + start_onboarding: function() { + $( document.body ).on( 'click', '.edd-onboarding__welcome-screen-get-started', function( e ) { + e.preventDefault(); + EDD_Onboarding.loading_state( true ); + + var postData = { + action: 'edd_onboarding_started', + page: 'edd-onboarding-wizard', + _wpnonce: EDD_Onboarding.vars.nonce, + }; + $.post( + ajaxurl, + postData, + function() { + $( '.edd-onboarding__welcome-screen' ).hide(); + $( '.edd-onboarding__steps, .edd-onboarding__after-welcome-screen, .edd-onboarding__close-and-exit' ).show(); + EDD_Onboarding.loading_state( false ); + } + ); + + } ); + }, + + /** + * Mark the Onboarding process + * as completed and redirect user. + * + * @since 3.1 + * + */ + onboarding_completed: function( redirect ) { + EDD_Onboarding.loading_state( true ); + + var postData = { + action: 'edd_onboarding_completed', + page: 'edd-onboarding-wizard', + _wpnonce: EDD_Onboarding.vars.nonce, + }; + return $.post( + ajaxurl, + postData, + function() { + if ( redirect ) { + window.location = $( '#edd-onboarding__exit' ).val(); + } + } + ); + }, + + + /** + * Mark the Onboarding process + * as skipped and redirect user. + * + * @since 3.1 + * + */ + onboarding_skipped: function ( redirect, target ) { + if ( !target.classList.contains( 'edd-promo-notice-dismiss' ) ) { + EDD_Onboarding.loading_state( true ); + } + + var postData = { + action: 'edd_onboarding_skipped', + page: 'edd-onboarding-wizard', + _wpnonce: EDD_Onboarding.vars.nonce, + }; + return $.post( + ajaxurl, + postData, + function() { + if ( redirect ) { + window.location = $( '#edd-onboarding__exit' ).val(); + } + } + ); + }, + + + /** + * Fetch the HTML for a specific + * requested step and load it onto screen. + * + * @since 3.1 + * + * @param {string} step_name Step name. + */ + load_step: function( step_name ) { + EDD_Onboarding.loading_state( true ); + + $.ajax( { + type: 'GET', + dataType: 'html', + url: ajaxurl, + data: { + action: 'edd_onboarding_load_step', + page: 'edd-onboarding-wizard', + current_step: step_name, + _wpnonce: EDD_Onboarding.vars.nonce, + }, + success: function( data ) { + // Replace step screen. + $( '.edd-onboarding__current-step' ).html( data ); + + // Run step specific logic. + let step_class = EDD_Onboarding.get_step_class( step_name ); + if ( step_class ) { + EDD_Onboarding[ step_class ].init(); + } + + // Scroll step into view. + setTimeout( function() { + $('html, body').animate( { scrollTop: $( '.edd-onboarding__wrapper' ).offset().top - 45 }, 800 ); + }, 150 ); + + // Change GET parameter to the current step. + let query_params = new URLSearchParams( window.location.search ); + query_params.set( 'current_step', step_name ); + history.replaceState( null, null, '?' + query_params.toString() ); + + // Load tooltips. + setup_tooltips( $( '.edd-help-tip' ) ); + + // Reload email tags. + document.dispatchEvent( new Event( 'DOMContentLoaded' ) ); + + }, + } ).fail( function( response ) { + if ( window.console && window.console.log ) { + console.log( response ); + } + } ).done( function( response ) { + EDD_Onboarding.loading_state( false ); + } ); + }, + + /** + * Load next step in the sequence. + * + * @since 3.1 + * + */ + next_step: function( skipped = false ) { + let next_step = $( '.edd-onboarding_current-next-step' ).val(); + if ( '' === next_step ) { + if ( skipped ) { + EDD_Onboarding.onboarding_completed( true ); + } else { + return false; + } + } + + EDD_Onboarding.load_step( next_step ); + }, + + /** + * Transform step name to pascal case and return + * transformed string name. False if class object does not exist. + * + * @since 3.1 + * + * @param {string} step_name Step name. + */ + get_step_class: function( step_name ) { + let step_class = 'EDD_Onboarding_' + step_name.split( '_' ).map(element => { + return element.charAt( 0 ).toUpperCase() + element.slice( 1 ).toLowerCase(); + } ).join( '_' ); + + if ( typeof EDD_Onboarding[ step_class ] == 'undefined' ) { + return false; + } + + return step_class; + }, + + /** + * Specific steps logic. + */ + + EDD_Onboarding_Business_Info: { + /** + * Initialize step specific logic. + * + * @since 3.1 + * + */ + init: function() {}, + + /** + * Save settings fields. + * + * @since 3.1 + * + */ + save: function() { + return $.ajax( { + type: 'POST', + url: $('.edd-settings-form').attr("action"), + data: $('.edd-settings-form').serialize(), + beforeSend: function() { + EDD_Onboarding.loading_state( true ); + }, + success: function( data ) { + }, + } ).fail( function( response ) { + if ( window.console && window.console.log ) { + console.log( response ); + } + } ); + } + }, + + EDD_Onboarding_Payment_Methods: { + /** + * Initialize step specific logic. + * + * @since 3.1 + * + */ + init: function() { + // If Stripe connectioon exsists, fetch the current account details. + let stripe_connect_account = $( '#edds-stripe-connect-account' ); + let stripe_connect_actions = $( '#edds-stripe-disconnect-reconnect' ) + if ( stripe_connect_account ) { + $.ajax( { + type: 'POST', + url: ajaxurl, + data: { + action: 'edds_stripe_connect_account_info', + accountId: stripe_connect_account.data( 'account-id' ), + nonce: stripe_connect_account.data( 'nonce' ), + onboardingWizard: true, + }, + success: function( response ) { + // Account is sucessfully connected. + if ( response.success ) { + stripe_connect_account.html( response.data.message ); + stripe_connect_account.addClass( `notice-${ response.data.status }` ); + if ( response.data.actions ) { + stripe_connect_actions.html( response.data.actions ); + } + } else { + stripe_connect_account.html( response.data.message ); + stripe_connect_account.addClass( 'notice-error' ); + } + }, + } ).fail( function( response ) { + + } ); + } + }, + /** + * There is nothing to save in this step. + * + * @since 3.1 + * + */ + save: function() { + return Promise.resolve(); + }, + }, + + EDD_Onboarding_Configure_Emails: { + + vars: { + wp_editor: false, + }, + + /** + * Initialize step specific logic. + * + * @since 3.1 + * + */ + init: function() { + // If WP Editor is already initialized, we have to destroy it first. + if ( EDD_Onboarding.EDD_Onboarding_Configure_Emails.vars.wp_editor ) { + wp.editor.remove( 'edd_settings_purchase_receipt' ) + } + + wp.editor.initialize( + 'edd_settings_purchase_receipt', + { + tinymce: { + wpautop: true, + plugins : 'charmap colorpicker compat3x directionality fullscreen hr image lists media paste tabfocus textcolor wordpress wpautoresize wpdialogs wpeditimage wpemoji wpgallery wplink wptextpattern wpview', + toolbar1: 'bold italic underline strikethrough | bullist numlist | blockquote hr wp_more | alignleft aligncenter alignright | link unlink | fullscreen | wp_adv', + toolbar2: 'formatselect alignjustify forecolor | pastetext removeformat charmap | outdent indent | undo redo | wp_help' + }, + quicktags: true, + mediaButtons: true, + } + ); + + // Append "Insert marker" button. + $( '#edd-onboarding__insert-marker-button a' ).clone().appendTo( '.wp-media-buttons' ); + + EDD_Onboarding.EDD_Onboarding_Configure_Emails.vars.wp_editor = true; + }, + + /** + * Save settings fields. + * + * @since 3.1 + * + */ + save: function() { + let editor_id = 'edd_settings_purchase_receipt'; + let purchase_receipt_content = $( '#' + editor_id ).val(); + + if( tinymce.get( editor_id ) ) { + purchase_receipt_content = wp.editor.getContent(editor_id); + } + + $( 'textarea#edd_settings_purchase_receipt' ).val( purchase_receipt_content ); + + return $.ajax( { + type: 'POST', + url: $('.edd-settings-form').attr("action"), + data: $('.edd-settings-form').serialize(), + beforeSend: function() { + EDD_Onboarding.loading_state( true ); + }, + success: function( data ) { + }, + } ).fail( function( response ) { + if ( window.console && window.console.log ) { + console.log( response ); + } + } ); + } + }, + + EDD_Onboarding_Tools: { + /** + * Initialize step specific logic. + * + * @since 3.1 + * + */ + init: function() { + this.get_selected_plugins(); + $( document.body ).on( 'change', '.edd-onboarding__plugin-install', function() { + EDD_Onboarding.EDD_Onboarding_Tools.get_selected_plugins(); + } ); + }, + + /** + * Check which plugins are selected and + * update the UI accordingly. + * + * @since 3.1 + * + */ + get_selected_plugins: function() { + let selected_plugins = []; + + $( '.edd-onboarding__selected-plugins' ).show(); + $( '.edd-onboarding__plugin-install:checked:not(:disabled)' ).each( function() { + selected_plugins.push( $(this).data( 'plugin-name' ) ); + }); + + $( '.edd-onboarding__selected-plugins-text' ).html( selected_plugins.join( ', ' ) ); + + if ( selected_plugins.length === 0 ) { + $( '.edd-onboarding__selected-plugins' ).hide(); + } + }, + + /** + * Handle saving of user telemetry settings and + * installation/activation of selected plugins. + * If there is an error it will show a specific error page. + * + * @since 3.1 + * + */ + save: async function() { + EDD_Onboarding.loading_state( true ) + + // Save user telemetry details. + await $.post( + ajaxurl, + { + action: 'edd_onboarding_telemetry_settings', + page: 'edd-onboarding-wizard', + telemetry_toggle: $( '#edd-onboarding__telemery-toggle' ).is( ':checked' ), + _wpnonce: EDD_Onboarding.vars.nonce, + }, + function() { + } + ); + + // Get selected plugins. + let selected_plugins = []; + let installation_errors = []; + $( '.edd-onboarding__plugin-install:checked:not(:disabled)' ).each( function() { + selected_plugins.push({ + plugin_name: $(this).data( 'plugin-name' ), + plugin_file: $(this).data( 'plugin-file' ), + plugin_url: $(this).val(), + action: $(this).data( 'action' ), + }); + } ); + + // Install and activate selected plugins. + for ( let plugin in selected_plugins ) { + await new Promise((resolve, reject) => { + let action = selected_plugins[ plugin ].action; + let plugin_key = ''; + let ajax_action = ''; + let loader_text = ''; + + switch ( action ) { + case 'activate': + ajax_action = 'edd_activate_extension'; + loader_text = EDDExtensionManager.activating; + plugin_key = selected_plugins[ plugin ].plugin_file; + break; + + case 'install': + ajax_action = 'edd_install_extension'; + loader_text = EDDExtensionManager.installing; + plugin_key = selected_plugins[ plugin ].plugin_url; + break; + } + + // Update loading text. + $( '.edd-onboarding__loading-status' ).html( loader_text + ' ' + selected_plugins[plugin].plugin_name + '...' ); + + let data = { + action: ajax_action, + nonce: EDDExtensionManager.extension_manager_nonce, + plugin: plugin_key, + type: 'plugin', + }; + + $.post( ajaxurl, data ) + .done( function ( res ) { + if ( ! res.success ) { + installation_errors.push( selected_plugins[plugin].plugin_name ); + } + // Activation can happen very fast, so we want a fake delay for the UI. + setTimeout( function() { + resolve(); + }, 1500 ); + } ); + }) + } + + // All of the plugins were installed and activated. + if ( installation_errors.length === 0 ) { + if ( selected_plugins.length === 0 ) { + return Promise.resolve(); + } + + // Show success screen. + return new Promise( (resolve, reject) => { + EDD_Onboarding.loading_state( false ); + $( '.edd-onboarding' ).toggleClass( 'edd-onboarding__loading-in-progress', true ); + $( '.edd-onboarding__install-success-wrapper' ).show(); + setTimeout(() => { + $( '.edd-onboarding__install-success-wrapper' ).hide(); + $( '.edd-onboarding' ).toggleClass( 'edd-onboarding__loading-in-progress', false ); + resolve(); + }, 3200); + }); + } + + // There were some errors while installing/activating. + $( '.edd-onboarding__failed-plugins-text' ).html( installation_errors.join( ', ' ) ); + $( '.edd-onboarding__steps-indicator, .edd-onboarding__single-step-title, .edd-onboarding__single-step-subtitle, .edd-onboarding__single-step-footer, .edd-onboarding__install-plugins' ).slideUp(); + $( '.edd-onboarding__install-failed' ).slideDown(); + EDD_Onboarding.loading_state( false ) + + return Promise.reject(); + } + }, + + EDD_Onboarding_Products: { + /** + * Initialize step specific logic. + * + * @since 3.1 + * + */ + init: function() { + EDD_Onboarding.EDD_Onboarding_Products.init_variable_pricing_toggle(); + EDD_Onboarding.EDD_Onboarding_Products.init_files_toggle(); + $( '#edd_download_files' ).show(); + }, + + /** + * Toggle between single price + * and variable price options. + * + * @since 3.1 + * + */ + init_variable_pricing_toggle: function() { + $( document.body ).on( 'click', '.edd-onboarding__pricing-option-pill button', function( e ) { + e.preventDefault(); + let is_variable_pricing = $( this ).data( 'variable-pricing' ); + + // Toggle checkbox. + $( '#edd_variable_pricing' ).prop( 'checked', is_variable_pricing ); + $( '#edd_variable_pricing' ).trigger( 'change' ); + } ); + + + $( document.body ).on( 'change', '#edd_variable_pricing', function( e ) { + e.preventDefault(); + let is_variable_pricing = this.checked; + + // Active pill state. + $( '.edd-onboarding__pricing-option-pill .active' ).removeClass( 'active' ); + $( '.edd-onboarding__pricing-option-pill button[data-variable-pricing="' + is_variable_pricing + '"]' ).addClass( 'active' ); + + $( '.edd-onboarding__product-single-price' ).show(); + $( '.edd-onboarding__product-variable-price' ).hide(); + + // Toggle views. + if ( is_variable_pricing ) { + $( '.edd-onboarding__product-variable-price' ).show(); + $( '.edd-onboarding__product-single-price' ).hide(); + } + + $( '.edd-onboarding__product-files-wrapper' ).show(); + } ); + }, + + /** + * Toggle file uploading. + * + * @since 3.1 + * + */ + init_files_toggle: function() { + $( document.body ).on( 'change', '#_edd_upload_files', function( e ) { + e.preventDefault(); + $( '.edd-onboarding__product-files-row' ).toggle( this.checked ); + } ); + }, + + /** + * Save product details and upon + * success redirect to the created product. + * + * @since 3.1 + * + */ + save: function() { + let form = $('.edd-onboarding__create-product-form'); + if ( ! form[0].reportValidity() ) { + return Promise.reject(); + } + + let form_details = Object.fromEntries( new FormData( form[0] ) ); + + return $.ajax( { + type: 'POST', + url: ajaxurl, + data: { + action: 'edd_onboarding_create_product', + page: 'edd-onboarding-wizard', + _wpnonce: EDD_Onboarding.vars.nonce, + ...form_details + }, + beforeSend: function() { + EDD_Onboarding.loading_state( true ); + }, + success: function( data ) { + if ( data.success ) { + + $( '.edd-onboarding__edit-my-product' ).attr( 'href', decodeURI( data.redirect_url.replace(/&/g, "&") ) ); + $( '.edd-onboarding__single-step-inner' ).addClass( 'equal' ); + $( '.edd-onboarding__create-product-form, .edd-onboarding__single-step-title, .edd-onboarding__single-step-subtitle, .edd-onboarding__single-step-footer, .edd-onboarding__close-and-exit' ).hide(); + $( '.edd-onboarding__product-created' ).show(); + + EDD_Onboarding.onboarding_completed( false ); + } + + EDD_Onboarding.loading_state( false ); + }, + } ).fail( function( response ) { + if ( window.console && window.console.log ) { + console.log( response ); + } + } ); + } + } +}; + +jQuery( document ).ready( function( $ ) { + EDD_Onboarding.init(); +} ); diff --git a/assets/js/admin/orders/index.js b/assets/js/admin/orders/index.js new file mode 100644 index 00000000000..de6bf2e0040 --- /dev/null +++ b/assets/js/admin/orders/index.js @@ -0,0 +1,130 @@ +/** + * Internal dependencies + */ +import OrderOverview from './order-overview'; +import './order-details'; +import { jQueryReady } from 'utils/jquery.js'; + +jQueryReady( () => { + // Order Overview. + if ( window.eddAdminOrderOverview ) { + OrderOverview.render(); + + /** + * Add validation to Add/Edit Order form. + * + * @since 3.0 + */ + ( () => { + const overview = OrderOverview.options.state; + const orderItems = overview.get( 'items' ); + + const noItemErrorEl = document.getElementById( 'edd-add-order-no-items-error' ); + const noCustomerErrorEl = document.getElementById( 'edd-add-order-customer-error' ); + + const assignCustomerEl = document.getElementById( 'customer_id' ); + const newCustomerEmailEl = document.getElementById( 'edd_new_customer_email' ); + + [ + 'edd-add-order-form', + 'edd-edit-order-form', + ].forEach( ( form ) => { + const formEl = document.getElementById( form ); + + if ( ! formEl ) { + return; + } + + formEl.addEventListener( 'submit', submitForm ); + } ); + + /** + * Submits an Order form. + * + * @since 3.0 + * + * @param {Object} event Submit event. + */ + function submitForm( event ) { + let hasError = false; + + // Ensure `OrderItem`s. + if ( noItemErrorEl ) { + if ( 0 === orderItems.length ) { + noItemErrorEl.style.display = 'block'; + hasError = true; + } else { + noItemErrorEl.style.display = 'none'; + } + } + + // Ensure Customer. + if ( noCustomerErrorEl ) { + if ( '0' === assignCustomerEl.value && '' === newCustomerEmailEl.value ) { + noCustomerErrorEl.style.display = 'block'; + hasError = true; + } else { + noCustomerErrorEl.style.display = 'none'; + } + + if ( true === hasError ) { + event.preventDefault(); + } + } + } + + /** + * Remove `OrderItem` notice when an `OrderItem` is added. + * + * @since 3.0 + */ + orderItems.on( 'add', function() { + noItemErrorEl.style.display = 'none'; + } ); + + /** + * Remove Customer notice when a Customer is changed. + * + * Uses a jQuery binding for Chosen support. + * + * @since 3.0 + * + * @param {Object} event Change event. + */ + $( assignCustomerEl ).on( 'change', ( event ) => { + const val = event.target.value; + + if ( '0' !== val ) { + noCustomerErrorEl.style.display = 'none'; + } + } ) + + if ( newCustomerEmailEl ) { + /** + * Remove Customer notice when a Customer is set. + * + * @since 3.0 + * + * @param {Object} event Input event. + */ + newCustomerEmailEl.addEventListener( 'input', ( event ) => { + const val = event.target.value; + + if ( '' !== val ) { + noCustomerErrorEl.style.display = 'none'; + } + } ); + } + } )(); + } + + // Move `.update-nag` items below the top header. + // `#update-nag` is legacy styling, which core still supports. + // + // `.notice` items are properly moved, but WordPress core + // does not move `.update-nag`. + if ( 0 !== $( '.edit-post-editor-regions__header' ).length ) { + $( 'div.update-nag, div#update-nag' ).insertAfter( $( '.edit-post-editor-regions__header' ) ); + } + +} ); diff --git a/assets/js/admin/orders/list-table.js b/assets/js/admin/orders/list-table.js new file mode 100644 index 00000000000..821862bdfa0 --- /dev/null +++ b/assets/js/admin/orders/list-table.js @@ -0,0 +1,73 @@ +/* global $, ajaxurl, edd_vars */ + +/** + * Internal dependencies + */ +import { jQueryReady } from 'utils/jquery.js'; + +jQueryReady( () => { + + // Deleting a single order. + $( '.download_page_edd-payment-history .row-actions .delete a' ).on( 'click', function(e) { + e.preventDefault(); + let targetUrl = $(this).attr('href'); + + $("#edd-single-delete-dialog").dialog({ + buttons : [ + { + text : edd_vars.cancel_dialog_text, + class : 'button-secondary', + click: function() { + $(this).dialog('close'); + }, + }, + { + text : edd_vars.confirm_dialog_text, + class : 'button-primary', + click : function() { + $(this).dialog('close'); + window.location.href = targetUrl; + }, + }, + ] + }); + + $('#edd-single-delete-dialog').dialog('open'); + }); + + + // Trashed Orders. + $( '.download_page_edd-payment-history' ).on( 'click', '#doaction', function ( e ) { + let action = $( '#bulk-action-selector-top' ).val(), + form = $(this).closest( 'form' ); + + if ( 'delete' !== action ) { + return; + } + + e.preventDefault(); + + $("#edd-bulk-delete-dialog").dialog({ + buttons : [ + { + text : edd_vars.cancel_dialog_text, + class : 'button-secondary', + click: function() { + $(this).dialog('close'); + }, + }, + { + text : edd_vars.confirm_dialog_text, + class : 'button-primary', + click : function() { + $(this).dialog('close'); + form.submit(); + }, + }, + ] + }); + + $('#edd-bulk-delete-dialog').dialog('open'); + }); + +} ); diff --git a/assets/js/admin/orders/order-details/address.js b/assets/js/admin/orders/order-details/address.js new file mode 100644 index 00000000000..e772eea0625 --- /dev/null +++ b/assets/js/admin/orders/order-details/address.js @@ -0,0 +1,256 @@ +/* global $, ajaxurl, _ */ + +/** + * Internal dependencies + */ +import OrderOverview from './../order-overview'; +import { getChosenVars } from 'utils/chosen.js'; +import { jQueryReady } from 'utils/jquery.js'; + +// Store customer search results to help prefill address data. +let CUSTOMER_SEARCH_RESULTS = { + addresses: { + '0': { + address: '', + address2: '', + city: '', + region: '', + postal_code: '', + country: '', + }, + }, +}; + +jQueryReady( () => { + + /** + * Adjusts Overview tax configuration when the Customer's address changes. + * + * @since 3.0 + */ + ( () => { + const { state: overviewState } = OrderOverview.options; + + // No tax, do nothing. + if ( false === overviewState.get( 'hasTax' ) ) { + return; + } + + // Editing, do nothing. + if ( false === overviewState.get( 'isAdding' ) ) { + return; + } + + const countryInput = document.getElementById( + 'edd_order_address_country' + ); + const regionInput = document.getElementById( + 'edd_order_address_region' + ); + + if ( ! ( countryInput && regionInput ) ) { + return; + } + + /** + * Retrieves a tax rate based on the currently selected Address. + * + * @since 3.0 + */ + function getTaxRate() { + const country = $( '#edd_order_address_country' ).val(); + const region = $( '#edd_order_address_region' ).val(); + + const nonce = document.getElementById( 'edd_get_tax_rate_nonce' ) + .value; + + wp.ajax.send( 'edd_get_tax_rate', { + data: { + nonce, + country, + region, + }, + /** + * Updates the Overview's tax configuration on successful retrieval. + * + * @since 3.0 + * + * @param {Object} response AJAX response. + */ + success( response ) { + let { tax_rate: rate } = response; + + // Make a percentage. + rate = rate * 100; + + overviewState.set( 'hasTax', { + ...overviewState.get( 'hasTax' ), + country, + region, + rate, + } ); + }, + /* + * Updates the Overview's tax configuration on failed retrieval. + * + * @since 3.0 + */ + error() { + overviewState.set( 'hasTax', 'none' ); + }, + } ); + } + + // Update rate on Address change. + // + // Wait for Region field to be replaced when Country changes. + // Wait for typing when Regino field changes. + // jQuery listeners for Chosen compatibility. + $( '#edd_order_address_country' ).on( 'change', _.debounce( getTaxRate, 250 ) ); + + $( '#edd-order-address' ).on( 'change', '#edd_order_address_region', getTaxRate ); + $( '#edd-order-address' ).on( 'keyup', '#edd_order_address_region', _.debounce( getTaxRate, 250 ) ); + } )(); + + $( '.edd-payment-change-customer-input' ).on( 'change', function() { + const $this = $( this ), + data = { + action: 'edd_customer_addresses', + customer_id: $this.val(), + nonce: $( '#edd_add_order_nonce' ).val(), + }; + + $.post( ajaxurl, data, function( response ) { + const { success, data } = response; + + if ( ! success ) { + $( '.customer-address-select-wrap' ).hide(); + + return; + } + + // Store response for later use. + CUSTOMER_SEARCH_RESULTS = { + ...CUSTOMER_SEARCH_RESULTS, + ...data, + addresses: { + ...CUSTOMER_SEARCH_RESULTS.addresses, + ...data.addresses, + }, + }; + + if ( data.html ) { + $( '.customer-address-select-wrap' ).show(); + $( '.customer-address-select-wrap .edd-form-group__control' ).html( data.html ); + } else { + $( '.customer-address-select-wrap' ).hide(); + } + }, 'json' ); + + return false; + } ); + + /** + * Retrieves a list of states based on a Country HTML ' ); + } else { + state_wrapper + .replaceWith( regions ); + + $( '#edd_order_address_region' ).chosen( getChosenVars( $( '#edd_order_address_region' ) ) ); + } + } + + /** + * Handles replacing a Region field when a Country field changes. + * + * @since 3.0 + */ + function updateRegionFieldOnChange() { + getStates( + $( this ), + 'edd_order_address[region]', + 'edd_order_address_region' + ) + .done( replaceRegionField ); + } + + $( document.body ).on( 'change', '.customer-address-select-wrap .add-order-customer-address-select', function() { + const $this = $( this ), + val = $this.val(), + address = CUSTOMER_SEARCH_RESULTS.addresses[ val ]; + + $( '#edd-add-order-form input[name="edd_order_address[address]"]' ).val( address.address ); + $( '#edd-add-order-form input[name="edd_order_address[address2]"]' ).val( address.address2 ); + $( '#edd-add-order-form input[name="edd_order_address[postal_code]"]' ).val( address.postal_code ); + $( '#edd-add-order-form input[name="edd_order_address[city]"]' ).val( address.city ); + $( '#edd-add-order-form input[name="edd_order_address[address_id]"]' ).val( val ); + + // Remove global `change` event handling to prevent loop. + $( '#edd_order_address_country' ).off( 'change', updateRegionFieldOnChange ); + + // Set Country. + $( '#edd_order_address_country' ) + .val( address.country ) + .trigger( 'change' ) + .trigger( 'chosen:updated' ); + + // Set Region. + getStates( + $( '#edd_order_address_country' ), + 'edd_order_address[region]', + 'edd_order_address_region' + ) + .done( replaceRegionField ) + .done( ( response ) => { + $( '#edd_order_address_region' ) + .val( address.region ) + .trigger( 'change' ) + .trigger( 'chosen:updated' ); + } ); + + // Add back global `change` event handling. + $( '#edd_order_address_country' ).on( 'change', updateRegionFieldOnChange ); + + return false; + } ); + + // Country change. + $( '#edd_order_address_country' ).on( 'change', updateRegionFieldOnChange ); + +} ); diff --git a/assets/js/admin/orders/order-details/customer.js b/assets/js/admin/orders/order-details/customer.js new file mode 100644 index 00000000000..66cc2d276e3 --- /dev/null +++ b/assets/js/admin/orders/order-details/customer.js @@ -0,0 +1,69 @@ +/* global $ */ + +/** + * Internal dependencies + */ +import { jQueryReady } from 'utils/jquery.js'; + +jQueryReady( () => { + + // Change Customer. + $( '.edd-payment-change-customer-input' ).on( 'change', function() { + const $this = $( this ), + data = { + action: 'edd_customer_details', + customer_id: $this.val(), + nonce: $( '#edd_customer_details_nonce' ).val(), + }; + + if ( '' === data.customer_id ) { + return; + } + + $( '.customer-details' ).css( 'display', 'none' ); + $( '#customer-avatar' ).html( '' ); + + $.post( ajaxurl, data, function( response ) { + const { success, data } = response; + + if ( success ) { + $( '.customer-details' ).css( 'display', 'flex' ); + $( '.customer-details-wrap' ).css( 'display', 'flex' ); + + $( '#customer-avatar' ).html( data.avatar ); + $( '.customer-name' ).html( data.name ); + $( '.customer-since span' ).html( data.date_created_i18n ); + $( '.customer-record a' ).prop( 'href', data._links.self ); + } else { + $( '.customer-details-wrap' ).css( 'display', 'none' ); + } + }, 'json' ); + } ); + + $( '.edd-payment-change-customer-input' ).trigger( 'change' ); + + // New Customer. + $( '#edd-customer-details' ).on( 'click', '.edd-payment-new-customer, .edd-payment-new-customer-cancel', function( e ) { + e.preventDefault(); + + var new_customer = $( this ).hasClass( 'edd-payment-new-customer' ), + cancel = $( this ).hasClass( 'edd-payment-new-customer-cancel' ); + + if ( new_customer ) { + $( '.order-customer-info' ).hide(); + $( '.new-customer' ).show(); + } else if ( cancel ) { + $( '.order-customer-info' ).show(); + $( '.new-customer' ).hide(); + } + + var new_customer = $( '#edd-new-customer' ); + + if ( $( '.new-customer' ).is( ':visible' ) ) { + new_customer.val( 1 ); + } else { + new_customer.val( 0 ); + } + } ); + +} ); diff --git a/assets/js/admin/orders/order-details/index.js b/assets/js/admin/orders/order-details/index.js new file mode 100644 index 00000000000..c429e037cf3 --- /dev/null +++ b/assets/js/admin/orders/order-details/index.js @@ -0,0 +1,3 @@ +import './address.js'; +import './customer.js'; +import './receipt.js'; diff --git a/assets/js/admin/orders/order-details/receipt.js b/assets/js/admin/orders/order-details/receipt.js new file mode 100644 index 00000000000..40765d7e5e3 --- /dev/null +++ b/assets/js/admin/orders/order-details/receipt.js @@ -0,0 +1,26 @@ +/* global $, ajaxurl */ + +/** + * Internal dependencies + */ +import { jQueryReady } from 'utils/jquery.js'; + +jQueryReady( () => { + + const emailSelectSelector = '.edd-order-resend-receipt-email'; + const sendEmailButton = $( '#edd-resend-receipt' ); + const url = new URLSearchParams( sendEmailButton.attr( 'href' ) ); + + $( document.body ).on( 'change', emailSelectSelector, function() { + url.set( 'email', $( this ).val() ); + sendEmailButton.attr( 'href', decodeURIComponent( url.toString() ) ); + } ); + + // trigger initial value to be set on change + $( emailSelectSelector ).trigger( 'change' ); + + // confirm before sending + sendEmailButton.on( 'click', function() { + return confirm( edd_vars.resend_receipt ); + }); +} ); diff --git a/assets/js/admin/orders/order-overview/_refund.js b/assets/js/admin/orders/order-overview/_refund.js new file mode 100644 index 00000000000..067eabb06ec --- /dev/null +++ b/assets/js/admin/orders/order-overview/_refund.js @@ -0,0 +1,279 @@ +import { NumberFormat } from '@easy-digital-downloads/currency'; + +const number = new NumberFormat(); + +/* global eddAdminOrderOverview */ + +// Loads the modal when the refund button is clicked. +$(document.body).on('click', '.edd-refund-order', function (e) { + e.preventDefault(); + var link = $(this), + postData = { + action : 'edd_generate_refund_form', + order_id: $('input[name="edd_payment_id"]').val(), + }; + + $.ajax({ + type : 'POST', + data : postData, + url : ajaxurl, + success: function success(data) { + let modal_content = ''; + if (data.success) { + modal_content = data.html; + } else { + modal_content = data.message; + } + + $('#edd-refund-order-dialog').dialog({ + position: { my: 'top center', at: 'center center-25%' }, + width : '75%', + modal : true, + resizable: false, + draggable: false, + classes: { + 'ui-dialog': 'edd-dialog', + }, + closeText: eddAdminOrderOverview.i18n.closeText, + open: function( event, ui ) { + $(this).html( modal_content ); + }, + close: function( event, ui ) { + $( this ).html( '' ); + if ( $( this ).hasClass( 'did-refund' ) ) { + location.reload(); + } + } + }); + return false; + } + }).fail(function (data) { + $('#edd-refund-order-dialog').dialog({ + position: { my: 'top center', at: 'center center-25%' }, + width : '75%', + modal : true, + resizable: false, + draggable: false + }).html(data.message); + return false; + }); +}); + +$( document.body ).on( 'click', '.ui-widget-overlay', function ( e ) { + $( '#edd-refund-order-dialog' ).dialog( 'close' ); +} ); + +/** + * Listen for the bulk actions checkbox, since WP doesn't trigger a change on sub-items. + */ +$( document.body ).on( 'change', '#edd-refund-order-dialog #cb-select-all-1', function () { + const itemCheckboxes = $( '.edd-order-item-refund-checkbox' ); + const isChecked = $( this ).prop( 'checked' ); + + itemCheckboxes.each( function() { + $( this ).prop( 'checked', isChecked ).trigger( 'change' ); + } ); +} ); + +/** + * Listen for individual checkbox changes. + * When it does, trigger a quantity change. + */ +$( document.body ).on( 'change', '.edd-order-item-refund-checkbox', function () { + const parent = $( this ).parent().parent(); + const quantityField = parent.find( '.edd-order-item-refund-quantity' ); + + if ( quantityField.length ) { + if ( $( this ).prop( 'checked' ) ) { + // Triggering a change on the quantity field handles enabling the inputs. + quantityField.trigger( 'change' ); + } else { + // Disable inputs and recalculate total. + parent.find( '.edd-order-item-refund-input' ).prop( 'disabled', true ); + recalculateRefundTotal(); + } + } +} ); + +/** + * Handles quantity changes, which includes items in the refund. + */ +$( document.body ).on( 'change', '#edd-refund-order-dialog .edd-order-item-refund-input', function () { + let parent = $( this ).closest( '.refunditem' ), + quantityField = parent.find( '.edd-order-item-refund-quantity' ), + quantity = parseInt( quantityField.val() ); + + if ( quantity > 0 ) { + parent.addClass( 'refunded' ); + } else { + parent.removeClass( 'refunded' ); + } + + // Only auto calculate subtotal / tax if we've adjusted the quantity. + if ( $( this ).hasClass( 'edd-order-item-refund-quantity' ) ) { + // Enable/disable amount fields. + parent.find( '.edd-order-item-refund-input:not(.edd-order-item-refund-quantity)' ).prop( 'disabled', quantity === 0 ); + if ( quantity > 0 ) { + quantityField.prop( 'disabled', false ); + } + + let subtotalField = parent.find( '.edd-order-item-refund-subtotal' ), + taxField = parent.find( '.edd-order-item-refund-tax' ), + originalSubtotal = number.unformat( subtotalField.data( 'original' ) ), + originalTax = taxField.length ? number.unformat( taxField.data( 'original' ) ) : 0.00, + originalQuantity = parseInt( quantityField.data( 'max' ) ), + calculatedSubtotal = ( originalSubtotal / originalQuantity ) * quantity, + calculatedTax = taxField.length ? ( originalTax / originalQuantity ) * quantity : 0.00; + + // Make sure totals don't go over maximums. + if ( calculatedSubtotal > parseFloat( subtotalField.data( 'max' ) ) ) { + calculatedSubtotal = subtotalField.data( 'max' ); + } + if ( taxField.length && calculatedTax > parseFloat( taxField.data( 'max' ) ) ) { + calculatedTax = taxField.data( 'max' ); + } + + // Guess the subtotal and tax for the selected quantity. + subtotalField.val( number.format( calculatedSubtotal ) ); + if ( taxField.length ) { + taxField.val( number.format( calculatedTax ) ); + } + } + + recalculateRefundTotal(); +} ); + +/** + * Calculates all the final refund values. + */ +function recalculateRefundTotal() { + let newSubtotal = 0, + newTax = 0, + newTotal = 0, + canRefund = false, + allInputBoxes = $( '#edd-refund-order-dialog .edd-order-item-refund-input' ), + allReadOnly = $( '#edd-refund-order-dialog .edd-order-item-refund-input.readonly' ); + + // Set a readonly while we recalculate, to avoid race conditions in the browser. + allInputBoxes.prop( 'readonly', true ); + + // Loop over all order items. + $( '#edd-refund-order-dialog .edd-order-item-refund-quantity' ).each( function() { + const thisItemQuantity = parseInt( $( this ).val() ); + + if ( ! thisItemQuantity ) { + return; + } + + const thisItemParent = $( this ).closest( '.refunditem' ); + const thisItemSelected = thisItemParent.find( '.edd-order-item-refund-checkbox' ).prop( 'checked' ); + + if ( ! thisItemSelected ) { + thisItemParent.removeClass( 'refunded' ); + return; + } + + // Values for this item. + let thisItemTax = 0.00; + + let thisItemSubtotal = number.unformat( thisItemParent.find( '.edd-order-item-refund-subtotal' ).val() ); + + if ( thisItemParent.find( '.edd-order-item-refund-tax' ).length ) { + thisItemTax = number.unformat( thisItemParent.find( '.edd-order-item-refund-tax' ).val() ); + } + + let thisItemTotal = thisItemSubtotal + thisItemTax; + + thisItemParent.find( '.column-total span' ).text( number.format( thisItemTotal ) ); + + // Negate amounts if working with credit. + if ( thisItemParent.data( 'credit' ) ) { + thisItemSubtotal = thisItemSubtotal * -1; + thisItemTax = thisItemTax * -1; + thisItemTotal = thisItemTotal * -1; + } + + // Only include order items in the subtotal. + if ( thisItemParent.data( 'orderItem' ) ) { + newSubtotal += thisItemSubtotal; + } + + newTax += thisItemTax; + newTotal += thisItemTotal; + } ); + + if ( parseFloat( newTotal ) > 0 ) { + canRefund = true; + } + + $( '#edd-refund-submit-subtotal-amount' ).text( number.format( newSubtotal ) ); + $( '#edd-refund-submit-tax-amount' ).text( number.format( newTax ) ); + $( '#edd-refund-submit-total-amount' ).text( number.format( newTotal ) ); + + $( '#edd-submit-refund-submit' ).attr( 'disabled', ! canRefund ); + + // Remove the readonly. + allInputBoxes.prop( 'readonly', false ); + allReadOnly.prop( 'readonly', true ); +} + +/** + * Process the refund form after the button is clicked. + */ +$(document.body).on( 'click', '#edd-submit-refund-submit', function(e) { + e.preventDefault(); + $('.edd-submit-refund-message').removeClass('success').removeClass('fail'); + $( this ).removeClass( 'button-primary' ).attr( 'disabled', true ).addClass( 'updating-message' ); + $('#edd-submit-refund-status').hide(); + + const refundForm = $( '#edd-submit-refund-form' ); + const refundData = refundForm.serialize(); + + var postData = { + action: 'edd_process_refund_form', + data: refundData, + order_id: $('input[name="edd_payment_id"]').val() + }; + + $.ajax({ + type : 'POST', + data : postData, + url : ajaxurl, + success: function success(response) { + const message_target = $('.edd-submit-refund-message'), + url_target = $('.edd-submit-refund-url'); + + if ( response.success ) { + message_target.text(response.data.message).addClass('success'); + url_target.attr( 'href', response.data.refund_url ).show(); + + $( '#edd-submit-refund-status' ).show(); + url_target.focus(); + $( '#edd-refund-order-dialog' ).addClass( 'did-refund' ); + } else { + message_target.html(response.data).addClass('fail'); + url_target.hide(); + + $('#edd-submit-refund-status').show(); + $( '#edd-submit-refund-submit' ).attr( 'disabled', false ).removeClass( 'updating-message' ).addClass( 'button-primary' ); + } + } + } ).fail( function ( data ) { + const message_target = $('.edd-submit-refund-message'), + url_target = $('.edd-submit-refund-url'), + json = data.responseJSON; + + + message_target.text( json.data ).addClass( 'fail' ); + url_target.hide(); + + $( '#edd-submit-refund-status' ).show(); + $( '#edd-submit-refund-submit' ).attr( 'disabled', false ).removeClass( 'updating-message' ).addClass( 'button-primary' ); + return false; + }); +}); + +// Initialize WP toggle behavior for the modal. +$( document.body ).on( 'click', '.refund-items .toggle-row', function () { + $( this ).closest( 'tr' ).toggleClass( 'is-expanded' ); +} ); diff --git a/assets/js/admin/orders/order-overview/collections/order-adjustments.js b/assets/js/admin/orders/order-overview/collections/order-adjustments.js new file mode 100644 index 00000000000..32b9543d4e7 --- /dev/null +++ b/assets/js/admin/orders/order-overview/collections/order-adjustments.js @@ -0,0 +1,101 @@ +/* global Backbone */ + +/** + * Internal dependencies + */ +import { OrderAdjustment } from './../models/order-adjustment.js'; +import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js'; + +/** + * Collection of `OrderAdjustment`s. + * + * @since 3.0 + * + * @class Adjustments + * @augments Backbone.Collection + */ +export const OrderAdjustments = Backbone.Collection.extend( { + /** + * @since 3.0 + */ + comparator: 'type', + + /** + * Initializes the `OrderAdjustments` collection. + * + * @since 3.0 + * + * @constructs OrderAdjustments + * @augments Backbone.Collection + */ + initialize() { + this.getByType = this.getByType.bind( this ); + }, + + /** + * Determines which Model to use and instantiates it. + * + * @since 3.0 + * + * @param {Object} attributes Model attributes. + * @param {Object} options Model options. + */ + model( attributes, options ) { + let model; + + switch ( attributes.type ) { + case 'discount': + model = new OrderAdjustmentDiscount( attributes, options ); + break; + default: + model = new OrderAdjustment( attributes, options ); + } + + return model; + }, + + /** + * Defines the model's attribute that defines it's ID. + * + * Uses the `OrderAdjustment`'s Type ID. + * + * @since 3.0 + * + * @param {Object} attributes Model attributes. + * @return {number} + */ + modelId( attributes ) { + return `${ attributes.type }-${ attributes.typeId }-${ attributes.description }`; + }, + + /** + * Determines if `OrderAdjustments` contains a specific `OrderAdjustment`. + * + * @since 3.0 + * + * @param {OrderAdjustment} model Model to look for. + * @return {bool} True if the Collection contains the Model. + */ + has( model ) { + return ( + undefined !== + this.findWhere( { + typeId: model.get( 'typeId' ), + } ) + ); + }, + + /** + * Returns a list of `OrderAdjustment`s by type. + * + * @since 3.0 + * + * @param {string} type Type of adjustment to retrieve. `fee`, `credit`, or `discount`. + * @return {Array} List of type-specific adjustments. + */ + getByType( type ) { + return this.where( { + type, + } ); + }, +} ); diff --git a/assets/js/admin/orders/order-overview/collections/order-items.js b/assets/js/admin/orders/order-overview/collections/order-items.js new file mode 100644 index 00000000000..fa2ee824eac --- /dev/null +++ b/assets/js/admin/orders/order-overview/collections/order-items.js @@ -0,0 +1,137 @@ +/* global Backbone, $, _ */ + +/** + * External dependencies + */ +import uuid from 'uuid-random'; + +/** + * Internal dependencies + */ +import { OrderAdjustments } from './../collections/order-adjustments.js'; +import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js'; +import { OrderItem } from './../models/order-item.js'; + +/** + * Collection of `OrderItem`s. + * + * @since 3.0 + * + * @class OrderItems + * @augments Backbone.Collection + */ +export const OrderItems = Backbone.Collection.extend( { + /** + * @since 3.0 + * + * @type {OrderItem} + */ + model: OrderItem, + + /** + * Ensures `OrderItems` has access to the current state through a similar + * interface as Views. BackBone.Collection does not automatically set + * passed options as a property. + * + * @since 3.0 + * + * @param {null|Array} models List of Models. + * @param {Object} options Collection options. + */ + preinitialize( models, options ) { + this.options = options; + }, + + /** + * Determines if `OrderItems` contains a specific `OrderItem`. + * + * Uses the `OrderItem`s Product ID and Price ID to create a unique + * value to check against. + * + * @since 3.0 + * + * @param {OrderItem} model Model to look for. + * @return {bool} True if the Collection contains the Model. + */ + has( model ) { + const duplicates = this.filter( ( item ) => { + const itemId = + item.get( 'productId' ) + '_' + item.get( 'priceId' ); + const modelId = + model.get( 'productId' ) + '_' + model.get( 'priceId' ); + + return itemId === modelId; + } ); + + return duplicates.length > 0; + }, + + /** + * Updates the amounts for all current `OrderItem`s. + * + * @since 3.0 + * + * @return {$.promise} A jQuery promise representing zero or more requests. + */ + updateAmounts() { + const { options } = this; + const { state } = options; + + const items = state.get( 'items' ); + const discounts = new Backbone.Collection( + state.get( 'adjustments' ).getByType( 'discount' ) + ); + + const args = { + country: state.getTaxCountry(), + region: state.getTaxRegion(), + products: items.map( ( item ) => ( { + id: item.get( 'productId' ), + quantity: item.get( 'quantity' ), + options: { + price_id: item.get( 'priceId' ), + } + } ) ), + discountIds: discounts.pluck( 'typeId' ), + }; + + // Keep track of all jQuery Promises. + const promises = []; + + // Find each `OrderItem`'s amounts. + items.models.forEach( ( item ) => { + const getItemAmounts = item.getAmounts( args ); + + getItemAmounts + // Update `OrderItem`-level Adjustments. + .done( ( { adjustments } ) => { + // Map returned Discounts to `OrderAdjustmentDiscount`. + const orderItemDiscounts = adjustments.map( ( adjustment ) => { + return new OrderAdjustmentDiscount( { + ...adjustment, + id: uuid(), + objectId: item.get( 'id' ), + } ); + } ); + + // Gather existing `fee` and `credit` `OrderItem`-level Adjustments. + const orderItemAdjustments = item.get( 'adjustments' ).filter( ( adjustment ) => { + return [ 'fee', 'credit' ].includes( adjustment.type ); + } ); + + // Reset `OrderAdjustments` collection with new data. + item.set( 'adjustments', new OrderAdjustments( [ + ...orderItemDiscounts, + ...orderItemAdjustments, + ] ) ); + } ) + // Update individual `OrderItem`s and `OrderAdjustment`s with new amounts. + .done( ( response ) => item.setAmounts( response ) ); + + // Track jQuery Promise. + promises.push( getItemAmounts ); + } ); + + return $.when.apply( $, promises ); + }, +} ); diff --git a/assets/js/admin/orders/order-overview/collections/order-refunds.js b/assets/js/admin/orders/order-overview/collections/order-refunds.js new file mode 100644 index 00000000000..b10a1bc5ddf --- /dev/null +++ b/assets/js/admin/orders/order-overview/collections/order-refunds.js @@ -0,0 +1,21 @@ +/* global Backbone */ + +/** + * Internal dependencies + */ +import { OrderRefund } from './../models/order-refund.js'; + +/** + * Collection of `OrderRefund`s. + * + * @since 3.0 + * + * @class OrderRefunds + * @augments Backbone.Collection + */ +export const OrderRefunds = Backbone.Collection.extend( { + /** + * @since 3.0 + */ + model: OrderRefund, +} ); diff --git a/assets/js/admin/orders/order-overview/index.js b/assets/js/admin/orders/order-overview/index.js new file mode 100644 index 00000000000..bbdd2766275 --- /dev/null +++ b/assets/js/admin/orders/order-overview/index.js @@ -0,0 +1,106 @@ +/** + * Internal dependencies + */ +import { Currency, NumberFormat } from '@easy-digital-downloads/currency'; +import { Overview } from './views/overview.js'; +import { OrderItems } from './collections/order-items.js'; +import { OrderItem } from './models/order-item.js'; +import { OrderAdjustments } from './collections/order-adjustments.js'; +import { OrderRefunds } from './collections/order-refunds.js'; +import { State } from './models/state.js'; + +// Temporarily include old Refund flow. +import './_refund.js'; + +let overview; + +( () => { + if ( ! window.eddAdminOrderOverview ) { + return; + } + + const { + isAdding, + isRefund, + hasTax, + hasQuantity, + hasDiscounts, + order, + items, + adjustments, + refunds, + } = window.eddAdminOrderOverview; + + const currencyFormatter = new Currency( { + currency: order.currency, + currencySymbol: order.currencySymbol, + } ); + + // Create and hydrate state. + const state = new State( { + isAdding: '1' === isAdding, + isRefund: '1' === isRefund, + hasTax: '0' === hasTax ? false : hasTax, + hasQuantity: '1' === hasQuantity, + hasDiscounts: '1' === hasDiscounts, + formatters: { + currency: currencyFormatter, + // Backbone doesn't merge nested defaults. + number: new NumberFormat(), + }, + order, + } ); + + // Create collections and add to state. + state.set( { + items: new OrderItems( null, { + state, + } ), + adjustments: new OrderAdjustments( null, { + state, + } ), + refunds: new OrderRefunds( null, { + state, + } ), + } ); + + // Create Overview. + overview = new Overview( { + state, + } ); + + // Hydrate collections. + + // Hydrate `OrderItem`s. + // + // Models are created manually before being added to the collection to + // ensure attributes maintain schema with deep model attributes. + items.forEach( ( item ) => { + const orderItemAdjustments = new OrderAdjustments( item.adjustments ); + const orderItem = new OrderItem( { + ...item, + adjustments: orderItemAdjustments, + state, + } ); + + state.get( 'items' ).add( orderItem ); + } ); + + // Hyrdate `Order`-level `Adjustments`. + adjustments.forEach( ( adjustment ) => { + state.get( 'adjustments' ).add( { + state, + ...adjustment, + } ) + } ); + + // Hydrate `OrderRefund`s. + refunds.forEach( ( refund ) => { + state.get( 'refunds' ).add( { + state, + ...refund, + } ); + } ); +} ) (); + +export default overview; diff --git a/assets/js/admin/orders/order-overview/models/order-adjustment-discount.js b/assets/js/admin/orders/order-overview/models/order-adjustment-discount.js new file mode 100644 index 00000000000..b709222440c --- /dev/null +++ b/assets/js/admin/orders/order-overview/models/order-adjustment-discount.js @@ -0,0 +1,76 @@ +/* global _ */ + +/** + * Internal dependencies + */ +import { OrderAdjustment } from './order-adjustment.js'; + +/** + * OrderAdjustmentDiscount + * + * @since 3.0 + * + * @class OrderAdjustmentDiscount + * @augments Backbone.Model + */ +export const OrderAdjustmentDiscount = OrderAdjustment.extend( { + /** + * @since 3.0 + * + * @typedef {Object} OrderAdjustmentDiscount + */ + defaults: { + ...OrderAdjustment.prototype.defaults, + type: 'discount', + }, + + /** + * @since 3.0 + */ + idAttribute: 'typeId', + + /** + * Returns the `OrderAdjustmentDiscount`'s amount based on the current values + * of all `OrderItems` discounts. + * + * @since 3.0 + * + * @return {number} `OrderAdjustmentDiscount` amount. + */ + getAmount() { + let amount = 0; + + const state = this.get( 'state' ); + + // Return stored amount if viewing an existing Order. + if ( false === state.get( 'isAdding' ) ) { + return OrderAdjustment.prototype.getAmount.apply( this, arguments ); + } + + const { models: items } = state.get( 'items' ); + const { number } = state.get( 'formatters' ); + + items.forEach( ( item ) => { + const discount = item.get( 'adjustments' ).findWhere( { + typeId: this.get( 'typeId' ), + } ); + + if ( undefined !== discount ) { + amount += number.unformat( + number.format( discount.get( 'subtotal' ) ) + ); + } + } ); + + return amount; + }, + + /** + * Returns the `OrderAdjustment` total. + * + * @since 3.0 + */ + getTotal() { + return this.getAmount(); + }, +} ); diff --git a/assets/js/admin/orders/order-overview/models/order-adjustment.js b/assets/js/admin/orders/order-overview/models/order-adjustment.js new file mode 100644 index 00000000000..c1876e5c895 --- /dev/null +++ b/assets/js/admin/orders/order-overview/models/order-adjustment.js @@ -0,0 +1,101 @@ +/* global Backbone */ + +/** + * OrderAdjustment + * + * @since 3.0 + * + * @class OrderAdjustment + * @augments Backbone.Model + */ +export const OrderAdjustment = Backbone.Model.extend( { + /** + * @since 3.0 + * + * @typedef {Object} OrderAdjustment + */ + defaults: { + id: 0, + objectId: 0, + objectType: '', + typeId: 0, + type: '', + description: '', + subtotal: 0, + tax: 0, + total: 0, + dateCreated: '', + dateModified: '', + uuid: '', + }, + + /** + * Returns the `OrderAdjustment` amount. + * + * Separate from subtotal or total calculation so `OrderAdjustmentDiscount` + * can be calculated independently. + * + * @see OrderAdjustmentDiscount.prototype.getAmount() + * + * @since 3.0 + */ + getAmount() { + return this.get( 'subtotal' ); + }, + + /** + * Retrieves the `OrderAdjustment` tax. + * + * @since 3.0.0 + * + * @return {number} Total amount. + */ + getTax() { + return this.get( 'tax' ); + }, + + /** + * Returns the `OrderAdjustment` total. + * + * @since 3.0 + */ + getTotal() { + // Fees always have tax added exclusively. + // @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/2445#issuecomment-53215087 + // @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/f97f4f6f5454921a2014dc1fa8f4caa5f550108c/includes/cart/class-edd-cart.php#L1306-L1311 + return this.get( 'subtotal' ) + this.get( 'tax' ); + }, + + /** + * Recalculates the tax amount based on the current tax rate. + * + * @since 3.0.0 + */ + updateTax() { + const state = this.get( 'state' ); + const hasTax = state.get( 'hasTax' ); + + if ( + 'none' === hasTax || + '' === hasTax.country || + '' === hasTax.rate + ) { + return; + } + + const { number } = state.get( 'formatters' ); + const taxRate = hasTax.rate / 100; + const adjustments = state.get( 'adjustments' ).getByType( 'fee' ); + + adjustments.forEach( ( adjustment ) => { + if ( false === adjustment.get( 'isTaxable' ) ) { + return; + } + + const taxableAmount = adjustment.getAmount(); + const taxAmount = number.unformat( taxableAmount * taxRate ); + + adjustment.set( 'tax', taxAmount ); + } ); + } +} ); diff --git a/assets/js/admin/orders/order-overview/models/order-item.js b/assets/js/admin/orders/order-overview/models/order-item.js new file mode 100644 index 00000000000..8b45c29d7e2 --- /dev/null +++ b/assets/js/admin/orders/order-overview/models/order-item.js @@ -0,0 +1,250 @@ +/* global Backbone, _, $ */ + +/** + * Internal dependencies + */ +import { OrderAdjustments } from './../collections/order-adjustments.js'; + +/** + * OrderItem + * + * @since 3.0 + * + * @class OrderItem + * @augments Backbone.Model + */ +export const OrderItem = Backbone.Model.extend( { + /** + * @since 3.0 + * + * @typedef {Object} OrderItem + */ + defaults: { + id: 0, + orderId: 0, + productId: 0, + productName: '', + priceId: null, + cartIndex: 0, + type: 'download', + status: '', + statusLabel: '', + quantity: 1, + amount: 0, + subtotal: 0, + discount: 0, + tax: 0, + total: 0, + dateCreated: '', + dateModified: '', + uuid: '', + + // Track manually set amounts. + amountManual: 0, + taxManual: 0, + subtotalManual: 0, + + // Track if the amounts have been adjusted manually on addition. + _isAdjustingManually: false, + + // Track `OrderItem`-level adjustments. + // + // The handling of Adjustments in the API is currently somewhat + // fragmented with certain extensions creating Adjustments at the + // `Order` level, some at a duplicate `OrderItem` level, and some both. + adjustments: new OrderAdjustments(), + }, + + /** + * Returns the `OrderItem` subtotal amount. + * + * @since 3.0.0 + * + * @param {bool} includeTax If taxes should be included when retrieving the subtotal. + * This is needed in some scenarios with inclusive taxes. + * @return {number} Subtotal amount. + */ + getSubtotal( includeTax = false ) { + const state = this.get( 'state' ); + const subtotal = this.get( 'subtotal' ); + + // Use stored value if the record has already been created. + if ( false === state.get( 'isAdding' ) ) { + return subtotal; + } + + // Calculate subtotal. + if ( true === state.hasInclusiveTax() && false === includeTax ) { + return subtotal - this.getTax(); + } + + return subtotal; + }, + + /** + * Returns the Discount amount. + * + * If an Order is being added the amount is calculated based + * on the total of `OrderItem`-level Adjustments that are + * currently applied. + * + * If an Order has already been added use the amount stored + * directly in the database. + * + * @since 3.0 + * + * @return {number} Discount amount. + */ + getDiscountAmount() { + let amount = 0; + + const discounts = this.get( 'adjustments' ).getByType( 'discount' ); + + if ( 0 === discounts.length ) { + return this.get( 'discount' ); + } + + discounts.forEach( ( discount ) => { + amount += +discount.get( 'subtotal' ); + } ); + + return amount; + }, + + /** + * Retrieves the rounded Tax for the order item. + * + * Rounded to match storefront checkout. + * + * @since 3.0.0 + * + * @return {number} Total amount. + */ + getTax() { + const state = this.get( 'state' ); + const tax = this.get( 'tax' ); + + // Use stored value if the record has already been created. + if ( false === state.get( 'isAdding' ) ) { + return tax; + } + + // Calculate tax. + const { number } = state.get( 'formatters' ); + + return number.unformat( number.format( tax ) ); + }, + + /** + * Retrieves the Total for the order item. + * + * @since 3.0.0 + * + * @return {number} Total amount. + */ + getTotal() { + const state = this.get( 'state' ); + + // Use stored value if the record has already been created. + if ( false === state.get( 'isAdding' ) ) { + return this.get( 'total' ); + } + + // Calculate total. + if ( true === state.hasInclusiveTax() ) { + return this.get( 'subtotal' ) - this.getDiscountAmount(); + } + + return ( this.get( 'subtotal' ) - this.getDiscountAmount() ) + this.getTax(); + }, + + /** + * Retrieves amounts for the `OrderItem` based on other `OrderItem`s and `OrderAdjustment`s. + * + * @since 3.0 + * + * @param {Object} args Arguments to pass as data in the XHR request. + * @param {string} args.country Country code to determine tax rate. + * @param {string} args.region Region to determine tax rate. + * @param {Array} args.products List of current products added to the order. + * @param {Array} args.discountIds List of `OrderAdjustmentDiscount`s to calculate amounts against. + * @return {$.promise} A jQuery promise that represents the request. + */ + getAmounts( { + country = '', + region = '', + products = [], + discountIds = [], + } ) { + const { + nonces: { edd_admin_order_get_item_amounts: nonce }, + } = window.eddAdminOrderOverview; + + const { productId, priceId, quantity, amount, tax, subtotal } = _.clone( + this.attributes + ); + + return wp.ajax.send( 'edd-admin-order-get-item-amounts', { + data: { + nonce, + productId, + priceId, + quantity, + amount, + tax, + subtotal, + country, + region, + products: _.uniq( [ + ...products, + { + id: productId, + quantity, + options: { + price_id: priceId, + }, + }, + ], function( { id, options: { price_id } } ) { + return `${ id }_${ price_id }` + } ), + discounts: _.uniq( discountIds ), + }, + } ); + }, + + /** + * Bulk sets amounts. + * + * Only adjusts the Discount amount if adjusting manually. + * + * @since 3.0 + * + * @param {Object} amounts Amounts to set. + * @param {number} amounts.amount `OrderItem` unit price. + * @param {number} amounts.discount `OrderItem` discount amount. + * @param {number} amounts.tax `OrderItem` tax amount. + * @param {number} amounts.subtotal `OrderItem` subtotal amount. + * @param {number} amounts.total `OrderItem` total amount. + */ + setAmounts( { + amount = 0, + discount = 0, + tax = 0, + subtotal = 0, + total = 0, + } ) { + if ( true === this.get( '_isAdjustingManually' ) ) { + this.set( { + discount, + } ); + } else { + this.set( { + amount, + discount, + tax, + subtotal, + total, + } ); + } + }, +} ); diff --git a/assets/js/admin/orders/order-overview/models/order-refund.js b/assets/js/admin/orders/order-overview/models/order-refund.js new file mode 100644 index 00000000000..99be2a92205 --- /dev/null +++ b/assets/js/admin/orders/order-overview/models/order-refund.js @@ -0,0 +1,24 @@ +/* global Backbone */ + +/** + * OrderRefund + * + * @since 3.0 + * + * @class OrderRefund + * @augments Backbone.Model + */ +export const OrderRefund = Backbone.Model.extend( { + /** + * @since 3.0 + * + * @typedef {Object} OrderAdjustment + */ + defaults: { + id: 0, + number: '', + total: 0, + dateCreated: '', + dateCreatedi18n: '', + }, +} ); diff --git a/assets/js/admin/orders/order-overview/models/state.js b/assets/js/admin/orders/order-overview/models/state.js new file mode 100644 index 00000000000..40f281b1a5f --- /dev/null +++ b/assets/js/admin/orders/order-overview/models/state.js @@ -0,0 +1,233 @@ +/* global Backbone, _ */ + +/** + * Internal dependencies + */ +import { Currency, NumberFormat } from '@easy-digital-downloads/currency'; + +/** + * State + * + * Leverages `Backbone.Model` and subsequently `Backbone.Events` + * to easily track changes to top level state changes. + * + * @since 3.0 + * + * @class State + * @augments Backbone.Model + */ +export const State = Backbone.Model.extend( + /** Lends State.prototype */ { + /** + * @since 3.0 + * + * @typedef {Object} State + */ + defaults: { + isAdding: false, + isFetching: false, + hasQuantity: false, + hasTax: false, + items: [], + adjustments: [], + refunds: [], + formatters: { + currency: new Currency(), + number: new NumberFormat(), + }, + }, + + /** + * Returns the current tax rate's country code. + * + * @since 3.0 + * + * @return {string} Tax rate country code. + */ + getTaxCountry() { + return false !== this.get( 'hasTax' ) + ? this.get( 'hasTax' ).country + : ''; + }, + + /** + * Returns the current tax rate's region. + * + * @since 3.0 + * + * @return {string} Tax rate region. + */ + getTaxRegion() { + return false !== this.get( 'hasTax' ) + ? this.get( 'hasTax' ).region + : ''; + }, + + /** + * Retrieves the Order subtotal. + * + * @since 3.0 + * + * @param {bool} includeTax If taxes should be included when retrieving the subtotal. + * This is needed in some scenarios with inclusive taxes. + * @return {number} Order subtotal. + */ + getSubtotal( includeTax = false ) { + // Use stored value if the record has already been created. + if ( false === this.get( 'isAdding' ) ) { + return this.get( 'order' ).subtotal; + } + + const { models: items } = this.get( 'items' ); + + return items.reduce( + ( amount, item ) => { + return amount += +item.getSubtotal( includeTax ); + }, + 0 + ); + }, + + /** + * Retrieves the Order discount. + * + * @since 3.0 + * + * @return {number} Order discount. + */ + getDiscount() { + // Use stored value if the record has already been created. + if ( false === this.get( 'isAdding' ) ) { + return this.get( 'order' ).discount; + } + + const adjustments = this.get( 'adjustments' ).getByType( 'discount' ); + + return adjustments.reduce( + ( amount, adjustment ) => { + return amount += +adjustment.getAmount(); + }, + 0 + ); + }, + + /** + * Retrieves the Order tax. + * + * @since 3.0 + * + * @return {number} Order tax. + */ + getTax() { + // Use stored value if the record has already been created. + if ( false === this.get( 'isAdding' ) ) { + return this.get( 'order' ).tax; + } + + const items = this.get( 'items' ).models; + const feesTax = this.getFeesTax(); + + return items.reduce( + ( amount, item ) => { + return amount += +item.getTax(); + }, + feesTax + ); + }, + + /** + * Retrieves the Order tax amount for fees. + * + * @since 3.0 + * + * @return {number} Order tax amount for fees. + */ + getFeesTax() { + // Use stored value if the record has already been created. + if ( false === this.get( 'isAdding' ) ) { + return this.get( 'order' ).tax; + } + + const adjustments = this.get( 'adjustments' ).getByType( 'fee' ); + + return adjustments.reduce( + ( amount, item ) => { + return amount += +item.getTax(); + }, + 0 + ); + }, + + /** + * Retrieves the Order total. + * + * @since 3.0 + * + * @return {number} Order total. + */ + getTotal() { + // Use stored value if the record has already been created. + if ( false === this.get( 'isAdding' ) ) { + return this.get( 'order' ).total; + } + + // Calculate all adjustments that affect the total. + const { models: adjustments } = this.get( 'adjustments' ); + const includeTaxInSubtotal = true; + + const adjustedSubtotal = adjustments.reduce( + ( amount, adjustment ) => { + if ( + [ 'discount', 'credit' ].includes( + adjustment.get( 'type' ) + ) + ) { + return amount -= +adjustment.getAmount(); + } else { + return amount += +adjustment.get( 'subtotal' ); + } + }, + this.getSubtotal( includeTaxInSubtotal ) + ); + + if ( true === this.hasInclusiveTax() ) { + // Fees always have tax added exclusively. + // @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/2445#issuecomment-53215087 + // @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/f97f4f6f5454921a2014dc1fa8f4caa5f550108c/includes/cart/class-edd-cart.php#L1306-L1311 + return adjustedSubtotal + this.getFeesTax(); + } + + return adjustedSubtotal + this.getTax(); + }, + + /** + * Determines if the state has a new, valid, tax rate. + * + * @since 3.0 + * + * @return {bool} True if the rate has changed. + */ + hasNewTaxRate() { + const hasTax = this.get( 'hasTax' ); + + if ( false === hasTax ) { + return false; + } + + const prevHasTax = this.previous( 'hasTax' ); + + return ! _.isEqual( hasTax, prevHasTax ); + }, + + /** + * Determines if the state has prices entered inclusive of tax. + * + * @since 3.0 + * + * @returns {bool} True if prices are entered inclusive of tax. + */ + hasInclusiveTax() { + return this.get( 'hasTax' ).inclusive; + } + } +); diff --git a/assets/js/admin/orders/order-overview/views/actions.js b/assets/js/admin/orders/order-overview/views/actions.js new file mode 100644 index 00000000000..9673c3a9912 --- /dev/null +++ b/assets/js/admin/orders/order-overview/views/actions.js @@ -0,0 +1,91 @@ +/** + * Internal dependencies + */ +import { edd_attach_tooltips as setupTooltips } from 'admin/components/tooltips'; +import { FormAddOrderItem } from './form-add-order-item.js'; +import { FormAddOrderDiscount } from './form-add-order-discount.js'; +import { FormAddOrderAdjustment } from './form-add-order-adjustment.js'; + +/** + * Actions + * + * @since 3.0 + * + * @class Actions + * @augments wp.Backbone.View + */ +export const Actions = wp.Backbone.View.extend( { + /** + * @since 3.0 + */ + el: '#edd-order-overview-actions', + + /** + * @since 3.0 + */ + template: wp.template( 'edd-admin-order-actions' ), + + /** + * @since 3.0 + */ + events: { + 'click #add-item': 'onAddOrderItem', + 'click #add-discount': 'onAddOrderDiscount', + 'click #add-adjustment': 'onAddOrderAdjustment', + }, + + /** + * Ensures tooltips can be used after render. + * + * @since 3.0 + * + * @return {Object} + */ + render() { + wp.Backbone.View.prototype.render.apply( this, arguments ); + + // Setup Tooltips after render. + setupTooltips( $( '.edd-help-tip' ) ); + + return this; + }, + + /** + * Renders the "Add Item" flow. + * + * @since 3.0 + * + * @param {Object} e Click event. + */ + onAddOrderItem( e ) { + e.preventDefault(); + + new FormAddOrderItem( this.options ).openDialog().render(); + }, + + /** + * Renders the "Add Discount" flow. + * + * @since 3.0 + * + * @param {Object} e Click event. + */ + onAddOrderDiscount( e ) { + e.preventDefault(); + + new FormAddOrderDiscount( this.options ).openDialog().render(); + }, + + /** + * Renders the "Add Adjustment" flow. + * + * @since 3.0 + * + * @param {Object} e Click event. + */ + onAddOrderAdjustment( e ) { + e.preventDefault(); + + new FormAddOrderAdjustment( this.options ).openDialog().render(); + }, +} ); diff --git a/assets/js/admin/orders/order-overview/views/base.js b/assets/js/admin/orders/order-overview/views/base.js new file mode 100644 index 00000000000..000bfb91d7b --- /dev/null +++ b/assets/js/admin/orders/order-overview/views/base.js @@ -0,0 +1,258 @@ +/* global _, $ */ + +/** + * WordPress dependencies + */ +import { focus } from '@wordpress/dom'; + +/** + * Internal dependencies + */ +import { getChosenVars } from 'utils/chosen.js'; + +// Set noconflict when using Lodash (@wordpress packages) and Underscores. +// @todo Find a better place to set this up. Webpack? +window.lodash = _.noConflict(); + +/** + * Base + * + * Supplies additional functionality and helpers beyond + * what is provided by `wp.Backbone.View`. + * + * - Maintains focus and caret positioning on rendering. + * - Extends events via `addEvents()`. + * + * @since 3.0 + * + * @class Base + * @augments wp.Backbone.View + */ +export const Base = wp.Backbone.View.extend( { + /** + * Defines base events to help maintain focus and caret position. + * + * @since 3.0 + */ + events: { + 'keydown input': 'handleTabBehavior', + 'keydown textarea': 'handleTabBehavior', + + 'focus input': 'onFocus', + 'focus textarea': 'onFocus', + 'focus select': 'onFocus', + + 'change input': 'onChange', + 'change textarea': 'onChange', + 'change select': 'onChange', + 'input input': 'onChange', + 'input textarea': 'onChange', + 'input select': 'onChange', + }, + + /** + * Sets up additional properties. + * + * @since 3.0 + */ + preinitialize() { + this.focusedEl = null; + this.focusedElCaretPos = 0; + + wp.Backbone.View.prototype.preinitialize.apply( this, arguments ); + }, + + /** + * Merges additional events with existing events. + * + * @since 3.0 + * + * @param {Object} events Hash of events to add. + */ + addEvents( events ) { + this.delegateEvents( { + ...this.events, + ...events, + } ); + }, + + /** + * Moves the focus when dealing with tabbing. + * + * @since 3.0 + * + * @param {Object} e Keydown event. + */ + handleTabBehavior( e ) { + const { keyCode, shiftKey, target } = e; + + // 9 = TAB + if ( 9 !== keyCode ) { + return; + } + + const tabbables = focus.tabbable.find( this.el ); + + if ( ! tabbables.length ) { + return; + } + + const firstTabbable = tabbables[ 0 ]; + const lastTabbable = tabbables[ tabbables.length - 1 ]; + let toFocus; + + if ( shiftKey && target === firstTabbable ) { + toFocus = lastTabbable; + } else if ( ! shiftKey && target === lastTabbable ) { + toFocus = firstTabbable; + } else if ( shiftKey ) { + toFocus = focus.tabbable.findPrevious( target ); + } else { + toFocus = focus.tabbable.findNext( target ); + } + + if ( 'undefined' !== typeof toFocus ) { + this.focusedEl = toFocus; + this.focusedElCaretPos = toFocus.value.length; + } else { + this.focusedEl = null; + this.focusedElCaretPos = 0; + } + }, + + /** + * Tracks the current element when focusing. + * + * @since 3.0 + * + * @param {Object} e Change event. + */ + onFocus( e ) { + this.focusedEl = e.target; + }, + + /** + * Tracks the current cursor position when editing. + * + * @since 3.0 + * + * @param {Object} e Change event. + */ + onChange( e ) { + const { target, keyCode } = e; + + // 9 = TAB + if ( undefined !== typeof keyCode && 9 === keyCode ) { + return; + } + + try { + if ( target.selectionStart ) { + this.focusedElCaretPos = target.selectionStart; + } + } catch ( error ) { + this.focusedElCaretPos = target.value.length; + } + }, + + /** + * Prepares data to be used in `render` method. + * + * @since 3.0 + * + * @see wp.Backbone.View + * @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js + * + * @return {Object} The data for this view. + */ + prepare() { + return this.model + ? { + ...this.model.toJSON(), + state: this.model.get( 'state' ).toJSON(), + } + : {}; + }, + + /** + * Adds additional handling after initial render. + * + * @since 3.0 + */ + render() { + wp.Backbone.View.prototype.render.apply( this, arguments ); + + this.initializeSelects(); + this.setFocus(); + + return this; + }, + + /** + * Reinitializes special ' ); + } else { + this.$el.html( this.states ); + this.$el.find( 'select' ).each( function() { + const el = $( this ); + el.chosen( getChosenVars( el ) ); + } ); + } + }, +} ); + +export default RegionField; diff --git a/assets/js/admin/settings/tax-rates/views/table-add.js b/assets/js/admin/settings/tax-rates/views/table-add.js new file mode 100644 index 00000000000..bf96ae18ee6 --- /dev/null +++ b/assets/js/admin/settings/tax-rates/views/table-add.js @@ -0,0 +1,240 @@ +/* global wp */ + +/** + * Internal dependencies. + */ +import TaxRate from './../models/tax-rate.js'; +import RegionField from './../views/region-field.js'; +import { getChosenVars } from 'utils/chosen.js'; + +/** + * Add a new rate "form". + */ +const TableAdd = wp.Backbone.View.extend( { + // Use + tagName: 'tfoot', + + // Set class. + className: 'add-new', + + // See https://codex.wordpress.org/Javascript_Reference/wp.template + template: wp.template( 'edd-admin-tax-rates-table-add' ), + + // Watch events. + events: { + 'click button': 'addTaxRate', + 'keypress': 'maybeAddTaxRate', + + 'change #tax_rate_country': 'setCountry', + + // Can be select or input. + 'keyup #tax_rate_region': 'setRegion', + 'change #tax_rate_region': 'setRegion', + + 'change input[type="checkbox"]': 'setGlobal', + + // Can be click increase or keyboard. + 'keyup #tax_rate_amount': 'setAmount', + 'change #tax_rate_amount': 'setAmount', + }, + + /** + * Set initial state and bind changes to model. + */ + initialize: function() { + this.model = new TaxRate( { + global: true, + unsaved: true, + } ); + + this.listenTo( this.model, 'change:country', this.updateRegion ); + this.listenTo( this.model, 'change:global', this.updateRegion ); + }, + + /** + * Render. Only overwritten so we can reinit chosen once cleared. + */ + render: function() { + wp.Backbone.View.prototype.render.apply( this, arguments ); + + this.$el.find( 'select' ).each( function() { + const el = $( this ); + el.chosen( getChosenVars( el ) ); + } ); + + return this; + }, + + /** + * Show a list of states or an input field. + */ + updateRegion: function() { + const self = this; + + const data = { + action: 'edd_get_shop_states', + country: this.model.get( 'country' ), + nonce: eddTaxRates.nonce, + field_name: 'tax_rate_region', + }; + + $.post( ajaxurl, data, function( response ) { + self.views.set( '#tax_rate_region_wrapper', new RegionField( { + states: response, + global: self.model.get( 'global' ), + } ) ); + } ); + }, + + /** + * Set a country value. + * + * @param {Object} event Event. + */ + setCountry: function( event ) { + let country = event.target.options[ event.target.selectedIndex ].value; + let regionGlobalCheckbox = document.getElementById( "tax_rate_region_global" ); + if ( 'all' === country ) { + country = '*'; + regionGlobalCheckbox.checked = true; + this.model.set( 'region', '' ); + this.model.set( 'global', true ); + regionGlobalCheckbox.readOnly = true; + regionGlobalCheckbox.disabled = true; + } else { + regionGlobalCheckbox.disabled = false; + regionGlobalCheckbox.readOnly = false; + } + + this.model.set( 'country', country ); + }, + + /** + * Set a region value. + * + * @param {Object} event Event. + */ + setRegion: function( event ) { + let value = false; + + if ( event.target.value ) { + value = event.target.value; + } else { + value = event.target.options[ event.target.selectedIndex ].value; + } + + this.model.set( 'region', value ); + }, + + /** + * Set a global scope. + * + * @param {Object} event Event. + */ + setGlobal: function( event ) { + let isChecked = event.target.checked; + this.model.set( 'global', isChecked ); + if ( true === isChecked ) { + this.model.set( 'region', '' ); + } + }, + + /** + * Set an amount value. + * + * @param {Object} event Event. + */ + setAmount: function( event ) { + this.model.set( 'amount', event.target.value ); + }, + + /** + * Monitors keyepress for "Enter" key. + * + * We cannot use the `submit` event because we cannot nest
+ * elements inside the settings API. + * + * @param {Object} event Keypress event. + */ + maybeAddTaxRate: function( event ) { + if ( 13 !== event.keyCode ) { + return; + } + + this.addTaxRate( event ); + }, + + /** + * Add a single rate when the "form" is submitted. + * + * @param {Object} event Event. + */ + addTaxRate: function( event ) { + event.preventDefault(); + + const { i18n } = eddTaxRates; + + if ( ! this.model.get( 'country' ) ) { + alert( i18n.emptyCountry ); + + return; + } + + let addingRegion = this.model.get( 'region' ); + let addingCountry = this.model.get( 'country' ); + let addingGlobal = '' === this.model.get( 'region' ); + + // For the purposes of this query, the * is really an empty query. + if ( '*' === addingCountry ) { + addingCountry = ''; + addingRegion = ''; + addingGlobal = false; + } + + const existingCountryWide = this.collection.where( { + region: addingRegion, + country: addingCountry, + global: addingGlobal, + status: 'active', + } ); + + if ( existingCountryWide.length > 0 ) { + const countryString = '' === addingCountry + ? '*' + : addingCountry; + + const regionString = '' === addingRegion + ? '' + : ': ' + addingRegion; + + const taxRateString = countryString + regionString; + + alert( i18n.duplicateRate.replace( '%s', `"${ taxRateString }"` ) ); + + return; + } + + if ( this.model.get( 'amount' ) < 0 ) { + alert( i18n.negativeTax ); + + return; + } + + if ( this.model.get( 'amount' ) == 0 ) { + confirm( i18n.emptyTax ); + } + + // Merge cid as ID to make this a unique model. + this.collection.add( _.extend( + this.model.attributes, + { + id: this.model.cid, + } + ) ); + + this.render(); + this.initialize(); + }, +} ); + +export default TableAdd; diff --git a/assets/js/admin/settings/tax-rates/views/table-meta.js b/assets/js/admin/settings/tax-rates/views/table-meta.js new file mode 100644 index 00000000000..9c0ae0544e6 --- /dev/null +++ b/assets/js/admin/settings/tax-rates/views/table-meta.js @@ -0,0 +1,33 @@ +/* global wp, _ */ + +/** + * Output a table header and footer. + */ +const TableMeta = wp.Backbone.View.extend( { + // See https://codex.wordpress.org/Javascript_Reference/wp.template + template: wp.template( 'edd-admin-tax-rates-table-meta' ), + + // Watch events. + events: { + 'change [type="checkbox"]': 'selectAll', + }, + + /** + * Select all items in the collection. + * + * @param {Object} event Event. + */ + selectAll: function( event ) { + const checked = event.target.checked; + + _.each( this.collection.models, ( model ) => { + // Check individual models. + model.set( 'selected', checked ); + + // Add to global selection. + this.collection.selected.push( model.cid ); + } ); + }, +} ); + +export default TableMeta; diff --git a/assets/js/admin/settings/tax-rates/views/table-row-empty.js b/assets/js/admin/settings/tax-rates/views/table-row-empty.js new file mode 100644 index 00000000000..2f313fd979c --- /dev/null +++ b/assets/js/admin/settings/tax-rates/views/table-row-empty.js @@ -0,0 +1,17 @@ +/* global wp */ + +/** + * Empty tax rates table. + */ +const TableRowEmpty = wp.Backbone.View.extend( { + // Insert as a + tagName: 'tr', + + // Set class. + className: 'edd-tax-rate-row edd-tax-rate-row--is-empty', + + // See https://codex.wordpress.org/Javascript_Reference/wp.template + template: wp.template( 'edd-admin-tax-rates-table-row-empty' ), +} ); + +export default TableRowEmpty; diff --git a/assets/js/admin/settings/tax-rates/views/table-row.js b/assets/js/admin/settings/tax-rates/views/table-row.js new file mode 100644 index 00000000000..cbfebcac207 --- /dev/null +++ b/assets/js/admin/settings/tax-rates/views/table-row.js @@ -0,0 +1,119 @@ +/* global wp, _ */ + +/** + * A row inside a table of rates. + */ +const TableRow = wp.Backbone.View.extend( { + // Insert as a + tagName: 'tr', + + // Set class. + className: function() { + return 'edd-tax-rate-row edd-tax-rate-row--' + this.model.get( 'status' ); + }, + + // See https://codex.wordpress.org/Javascript_Reference/wp.template + template: wp.template( 'edd-admin-tax-rates-table-row' ), + + // Watch events. + events: { + 'click .remove': 'removeRow', + 'click .activate': 'activateRow', + 'click .deactivate': 'deactivateRow', + 'change [type="checkbox"]': 'selectRow', + }, + + /** + * Bind model to view. + */ + initialize: function() { + this.listenTo( this.model, 'change', this.render ); + }, + + /** + * Render + */ + render: function() { + this.$el.html( this.template( { + ...this.model.toJSON(), + formattedAmount: this.model.formattedAmount(), + } ) ); + + // Ensure the wrapper class has the new name. + this.$el.attr( 'class', _.result( this, 'className' ) ); + }, + + /** + * Remove a rate (can only be done if it has not been saved to the database). + * + * Don't use this.model.destroy() to avoid sending a DELETE request. + * + * @param {Object} event Event. + */ + removeRow: function( event ) { + event.preventDefault(); + + this.collection.remove( this.model ); + }, + + /** + * Activate a rate. + * + * @param {Object} event Event. + */ + activateRow: function( event ) { + event.preventDefault(); + + const { i18n } = eddTaxRates; + const existingCountryWide = this.collection.where( { + region: this.model.get( 'region' ), + country: this.model.get( 'country' ), + global: '' === this.model.get( 'region' ), + status: 'active', + } ); + + if ( existingCountryWide.length > 0 ) { + const regionString = '' === this.model.get( 'region' ) + ? '' + : ': ' + this.model.get( 'region' ); + + const taxRateString = this.model.get( 'country' ) + regionString; + + alert( i18n.duplicateRate.replace( '%s', `"${ taxRateString }"` ) ); + + return; + } + + this.model.set( 'status', 'active' ); + }, + + /** + * Deactivate a rate. + * + * @param {Object} event Event. + */ + deactivateRow: function( event ) { + event.preventDefault(); + + this.model.set( 'status', 'inactive' ); + }, + + /** + * Select or deselect for bulk actions. + * + * @param {Object} event Event. + */ + selectRow: function( event ) { + const checked = event.target.checked; + + if ( ! checked ) { + this.collection.selected = _.reject( this.collection.selected, ( cid ) => { + return cid === this.model.cid; + } ); + } else { + this.collection.selected.push( this.model.cid ); + } + }, +} ); + +export default TableRow; diff --git a/assets/js/admin/settings/tax-rates/views/table-rows.js b/assets/js/admin/settings/tax-rates/views/table-rows.js new file mode 100644 index 00000000000..08c3bef383e --- /dev/null +++ b/assets/js/admin/settings/tax-rates/views/table-rows.js @@ -0,0 +1,65 @@ +/* global wp, _ */ + +/** + * Internal dependencies. + */ +import TableRowEmpty from './table-row-empty.js'; +import TableRow from './table-row.js'; + +/** + * A bunch of rows inside a table of rates. + */ +const TableRows = wp.Backbone.View.extend( { + // Insert as a + tagName: 'tbody', + + /** + * Bind events to collection. + */ + initialize: function() { + this.listenTo( this.collection, 'add', this.render ); + this.listenTo( this.collection, 'remove', this.render ); + this.listenTo( this.collection, 'filtered change', this.filtered ); + }, + + /** + * Render a collection of rows. + */ + render: function() { + // Clear to handle sorting. + this.views.remove(); + + // Show empty placeholder. + if ( 0 === this.collection.models.length ) { + return this.views.add( new TableRowEmpty() ); + } + + // Add items. + _.each( this.collection.models, ( rate ) => { + this.views.add( new TableRow( { + collection: this.collection, + model: rate, + } ) ); + } ); + }, + + /** + * Show an empty state if all items are deactivated. + */ + filtered: function() { + const disabledRates = this.collection.where( { + status: 'inactive', + } ); + + // Check if all rows are invisible, and show the "No Items" row if so + if ( disabledRates.length === this.collection.models.length && ! this.collection.showAll ) { + this.views.add( new TableRowEmpty() ); + + // Possibly re-render the view + } else { + this.render(); + } + }, +} ); + +export default TableRows; diff --git a/assets/js/admin/settings/tax-rates/views/table.js b/assets/js/admin/settings/tax-rates/views/table.js new file mode 100644 index 00000000000..f6f39871341 --- /dev/null +++ b/assets/js/admin/settings/tax-rates/views/table.js @@ -0,0 +1,50 @@ +/* global wp */ + +/** + * Internal dependencies. + */ +import TableMeta from './table-meta.js'; +import TableRows from './table-rows.js'; +import TableAdd from './table-add.js'; + +/** + * Manage the tax rate rows in a table. + */ +const Table = wp.Backbone.View.extend( { + // Render as a tag. + tagName: 'table', + + // Set class. + className: 'wp-list-table widefat fixed tax-rates', + + // Set ID. + id: 'edd_tax_rates', + + /** + * Output a table with a header, body, and footer. + */ + render: function() { + this.views.add( new TableMeta( { + tagName: 'thead', + collection: this.collection, + } ) ); + + this.views.add( new TableRows( { + collection: this.collection, + } ) ); + + this.views.add( new TableAdd( { + collection: this.collection, + } ) ); + + this.views.add( new TableMeta( { + tagName: 'tfoot', + collection: this.collection, + } ) ); + + // Trigger the `filtered` action to show/hide rows accordingly + this.collection.trigger( 'filtered' ); + }, +} ); + +export default Table; diff --git a/assets/js/admin/tools/export/index.js b/assets/js/admin/tools/export/index.js new file mode 100644 index 00000000000..ee2e3bd8a02 --- /dev/null +++ b/assets/js/admin/tools/export/index.js @@ -0,0 +1,93 @@ +/** + * Export screen JS + */ +const EDD_Export = { + + init: function() { + this.submit(); + }, + + submit: function() { + const self = this; + + $( document.body ).on( 'submit', '.edd-export-form', function( e ) { + e.preventDefault(); + + const form = $( this ), + submitButton = form.find( 'button[type="submit"]' ).first(); + + if ( submitButton.hasClass( 'button-disabled' ) || submitButton.is( ':disabled' ) ) { + return; + } + + const data = form.serialize(); + + if ( submitButton.hasClass( 'button-primary' ) ) { + submitButton.removeClass( 'button-primary' ).addClass( 'button-secondary' ); + } + submitButton.attr( 'disabled', true ).addClass( 'updating-message' ); + form.find( '.notice-wrap' ).remove(); + form.append( '
' ); + + // start the process + self.process_step( 1, data, self ); + } ); + }, + + process_step: function( step, data, self ) { + $.ajax( { + type: 'POST', + url: ajaxurl, + data: { + form: data, + action: 'edd_do_ajax_export', + step: step, + }, + dataType: 'json', + success: function( response ) { + if ( 'done' === response.step || response.error || response.success ) { + // We need to get the actual in progress form, not all forms on the page + const export_form = $( '.edd-export-form' ).find( '.edd-progress' ).parent().parent(); + const notice_wrap = export_form.find( '.notice-wrap' ); + + export_form.find( 'button' ).removeClass( 'updating-message' ).addClass( 'updated-message' ); + setTimeout( function () { + export_form.find( 'button' ).attr( 'disabled', false ).removeClass( 'updated-message' ) + }, 3000 ); + export_form.find( 'button .spinner' ).hide().css( 'visibility', 'visible' ); + + if ( response.error ) { + const error_message = response.message; + notice_wrap.html( '

' + error_message + '

' ); + } else if ( response.success ) { + const success_message = response.message; + notice_wrap.html( '

' + success_message + '

' ); + if ( response.data ) { + $.each( response.data, function ( key, value ) { + $( '.edd_' + key ).html( value ); + } ); + } + } else { + notice_wrap.remove(); + window.location = response.url; + } + } else { + $( '.edd-progress div' ).animate( { + width: response.percentage + '%', + }, 50, function() { + // Animation complete. + } ); + self.process_step( parseInt( response.step ), data, self ); + } + }, + } ).fail( function( response ) { + if ( window.console && window.console.log ) { + console.log( response ); + } + } ); + }, +}; + +jQuery( document ).ready( function( $ ) { + EDD_Export.init(); +} ); diff --git a/assets/js/admin/tools/import/index.js b/assets/js/admin/tools/import/index.js new file mode 100644 index 00000000000..83a85145c60 --- /dev/null +++ b/assets/js/admin/tools/import/index.js @@ -0,0 +1,174 @@ +/** + * Import screen JS + */ +var EDD_Import = { + + init: function() { + this.submit(); + }, + + submit: function() { + const self = this; + + $( '.edd-import-form' ).ajaxForm( { + beforeSubmit: self.before_submit, + success: self.success, + complete: self.complete, + dataType: 'json', + error: self.error, + } ); + }, + + before_submit: function( arr, form, options ) { + form.find( '.notice-wrap' ).remove(); + form.append( '
' ); + + //check whether client browser fully supports all File API + if ( window.File && window.FileReader && window.FileList && window.Blob ) { + + // HTML5 File API is supported by browser + + } else { + const import_form = $( '.edd-import-form' ).find( '.edd-progress' ).parent().parent(); + const notice_wrap = import_form.find( '.notice-wrap' ); + + import_form.find( '.button:disabled' ).attr( 'disabled', false ); + + //Error for older unsupported browsers that doesn't support HTML5 File API + notice_wrap.html( '

' + edd_vars.unsupported_browser + '

' ); + return false; + } + }, + + success: function( responseText, statusText, xhr, form ) {}, + + complete: function( xhr ) { + const self = $( this ), + response = jQuery.parseJSON( xhr.responseText ); + + if ( response.success ) { + const form = $( '.edd-import-form .notice-wrap' ).parent(); + + form.find( '.edd-import-file-wrap,.notice-wrap' ).remove(); + form.find( '.edd-import-options' ).slideDown(); + + // Show column mapping + let select = form.find( 'select.edd-import-csv-column' ), + row = select.parents( 'tr' ).first(), + options = '', + columns = response.data.columns.sort( function( a, b ) { + if ( a < b ) { + return -1; + } + if ( a > b ) { + return 1; + } + return 0; + } ); + + $.each( columns, function( key, value ) { + options += ''; + } ); + + select.append( options ); + + select.on( 'change', function() { + const key = $( this ).val(); + + if ( ! key ) { + $( this ).parent().next().html( '' ); + } else if ( false !== response.data.first_row[ key ] ) { + $( this ).parent().next().html( response.data.first_row[ key ] ); + } else { + $( this ).parent().next().html( '' ); + } + } ); + + $.each( select, function() { + $( this ).val( $( this ).attr( 'data-field' ) ).change(); + } ); + + $( document.body ).on( 'click', '.edd-import-proceed', function( e ) { + e.preventDefault(); + + form.find( '.edd-import-proceed.button-primary' ).addClass( 'updating-message' ); + form.append( '
' ); + + response.data.mapping = form.serialize(); + + EDD_Import.process_step( 1, response.data, self ); + } ); + } else { + EDD_Import.error( xhr ); + } + }, + + error: function( xhr ) { + // Something went wrong. This will display error on form + + const response = jQuery.parseJSON( xhr.responseText ); + const import_form = $( '.edd-import-form' ).find( '.edd-progress' ).parent().parent(); + const notice_wrap = import_form.find( '.notice-wrap' ); + + import_form.find( '.button:disabled' ).attr( 'disabled', false ); + + if ( response.data.error ) { + notice_wrap.html( '

' + response.data.error + '

' ); + } else { + notice_wrap.remove(); + } + }, + + process_step: function( step, import_data, self ) { + $.ajax( { + type: 'POST', + url: ajaxurl, + data: { + form: import_data.form, + nonce: import_data.nonce, + class: import_data.class, + upload: import_data.upload, + mapping: import_data.mapping, + action: 'edd_do_ajax_import', + step: step, + }, + dataType: 'json', + success: function( response ) { + if ( 'done' === response.data.step || response.data.error ) { + // We need to get the actual in progress form, not all forms on the page + const import_form = $( '.edd-import-form' ).find( '.edd-progress' ).parent().parent(); + const notice_wrap = import_form.find( '.notice-wrap' ); + + import_form.find( '.button:disabled' ).attr( 'disabled', false ); + + if ( response.data.error ) { + notice_wrap.html( '

' + response.data.error + '

' ); + } else { + import_form.find( '.edd-import-options' ).hide(); + $( 'html, body' ).animate( { + scrollTop: import_form.parent().offset().top, + }, 500 ); + + notice_wrap.html( '

' + response.data.message + '

' ); + } + } else { + $( '.edd-progress div' ).animate( { + width: response.data.percentage + '%', + }, 50, function() { + // Animation complete. + } ); + + EDD_Import.process_step( parseInt( response.data.step ), import_data, self ); + } + }, + } ).fail( function( response ) { + if ( window.console && window.console.log ) { + console.log( response ); + } + } ); + }, +}; + +jQuery( document ).ready( function( $ ) { + EDD_Import.init(); +} ); diff --git a/assets/js/admin/tools/index.js b/assets/js/admin/tools/index.js new file mode 100644 index 00000000000..593ddc93fa7 --- /dev/null +++ b/assets/js/admin/tools/index.js @@ -0,0 +1,121 @@ +/** + * Tools screen JS + */ +const EDD_Tools = { + + init: function() { + this.revoke_api_key(); + this.regenerate_api_key(); + this.create_api_key(); + this.recount_stats(); + }, + + revoke_api_key: function() { + $( document.body ).on( 'click', '.edd-revoke-api-key', function( e ) { + return confirm( edd_vars.revoke_api_key ); + } ); + }, + regenerate_api_key: function() { + $( document.body ).on( 'click', '.edd-regenerate-api-key', function( e ) { + return confirm( edd_vars.regenerate_api_key ); + } ); + }, + create_api_key: function() { + $( document.body ).on( 'submit', '#api-key-generate-form', function( e ) { + const input = $( 'input[type="text"][name="user_id"]' ); + + input.css( 'border-color', '#ddd' ); + + const user_id = input.val(); + if ( user_id.length < 1 || user_id === 0 ) { + input.css( 'border-color', '#ff0000' ); + return false; + } + } ); + }, + recount_stats: function() { + $( document.body ).on( 'change', '#recount-stats-type', function() { + const export_form = $( '#edd-tools-recount-form' ), + selected_type = $( 'option:selected', this ).data( 'type' ), + submit_button = $( '#recount-stats-submit' ), + products = $( '#tools-product-dropdown' ); + + // Reset the form + export_form.find( '.notice-wrap' ).remove(); + submit_button.attr( 'disabled', false ).removeClass( 'updated-message' ); + products.hide(); + $( '.edd-recount-stats-descriptions span' ).hide(); + + if ( 'recount-download' === selected_type ) { + products.show(); + products.find( '.edd-select-chosen' ).css( 'width', 'auto' ); + } else if ( 'reset-stats' === selected_type ) { + export_form.append( '
' ); + const notice_wrap = export_form.find( '.notice-wrap' ); + notice_wrap.html( '

' ); + + $( '#recount-stats-submit' ).attr( 'disabled', true ); + } else { + products.hide(); + products.val( 0 ); + } + + $( '#' + selected_type ).show(); + } ); + + $( document.body ).on( 'change', '#confirm-reset', function() { + const checked = $( this ).is( ':checked' ); + if ( checked ) { + $( '#recount-stats-submit' ).attr( 'disabled', false ); + } else { + $( '#recount-stats-submit' ).attr( 'disabled', true ); + } + } ); + + $( '#edd-tools-recount-form' ).submit( function( e ) { + e.preventDefault(); + + const selection = $( '#recount-stats-type' ).val(), + export_form = $( this ), + selected_type = $( 'option:selected', this ).data( 'type' ); + + if ( 'reset-stats' === selected_type ) { + const is_confirmed = $( '#confirm-reset' ).is( ':checked' ); + if ( is_confirmed ) { + return true; + } + has_errors = true; + } + + export_form.find( '.notice-wrap' ).remove(); + export_form.append( '
' ); + + var notice_wrap = export_form.find( '.notice-wrap' ), + has_errors = false; + + if ( null === selection || 0 === selection ) { + // Needs to pick a method edd_vars.batch_export_no_class + notice_wrap.html( '

' + edd_vars.batch_export_no_class + '

' ); + has_errors = true; + } + + if ( 'recount-download' === selected_type ) { + const selected_download = $( 'select[name="download_id"]' ).val(); + if ( selected_download === 0 ) { + // Needs to pick download edd_vars.batch_export_no_reqs + notice_wrap.html( '

' + edd_vars.batch_export_no_reqs + '

' ); + has_errors = true; + } + } + + if ( has_errors ) { + export_form.find( 'button:disabled' ).attr( 'disabled', false ).removeClass( 'updated-message' ); + return false; + } + } ); + }, +}; + +jQuery( document ).ready( function( $ ) { + EDD_Tools.init(); +} ); diff --git a/assets/js/admin/upgrades/index.js b/assets/js/admin/upgrades/index.js new file mode 100644 index 00000000000..e4e3fbb150f --- /dev/null +++ b/assets/js/admin/upgrades/index.js @@ -0,0 +1 @@ +import './v3'; diff --git a/assets/js/admin/upgrades/v3/index.js b/assets/js/admin/upgrades/v3/index.js new file mode 100644 index 00000000000..7995028037a --- /dev/null +++ b/assets/js/admin/upgrades/v3/index.js @@ -0,0 +1,211 @@ +const EDD_v3_Upgrades = { + inProgress: false, + + init: function() { + // Listen for toggle on the checkbox. + $( '.edd-v3-migration-confirmation' ).on( 'change', function( e ) { + const wrapperForm = $( this ).closest( '.edd-v3-migration' ); + const formSubmit = wrapperForm.find( 'button' ); + + if ( e.target.checked ) { + formSubmit.removeClass( 'disabled' ).prop( 'disabled', false ); + } else { + formSubmit.addClass( 'disabled' ).prop( 'disabled', true ); + } + } ); + + $( '.edd-v3-migration' ).on( 'submit', function( e ) { + e.preventDefault(); + + if ( EDD_v3_Upgrades.inProgress ) { + return; + } + + EDD_v3_Upgrades.inProgress = true; + + const migrationForm = $( this ); + const upgradeKeyField = migrationForm.find( 'input[name="upgrade_key"]' ); + let upgradeKey = false; + + if ( upgradeKeyField.length && upgradeKeyField.val() ) { + upgradeKey = upgradeKeyField.val(); + } + + // Disable submit button. + migrationForm.find( 'button' ) + .removeClass( 'button-primary' ) + .addClass( 'button-secondary disabled updating-message' ) + .prop( 'disabled', true ); + + // Disable checkbox. + migrationForm.find( 'input' ).prop( 'disabled', true ); + + // If this is the main migration, reveal the steps & mark the first non-complete item as in progress. + if ( 'edd-v3-migration' === migrationForm.attr( 'id' ) ) { + $( '#edd-migration-progress' ).removeClass( 'edd-hidden' ); + const firstNonCompleteUpgrade = $( '#edd-migration-progress li:not(.edd-upgrade-complete)' ); + if ( firstNonCompleteUpgrade.length && ! upgradeKey ) { + upgradeKey = firstNonCompleteUpgrade.data( 'upgrade' ); + } + } + + EDD_v3_Upgrades.processStep( upgradeKey, 1, migrationForm.find( 'input[name="_wpnonce"]' ).val() ); + } ) + }, + + processStep: function( upgrade_key, step, nonce ) { + let data = { + action: 'edd_process_v3_upgrade', + _ajax_nonce: nonce, + upgrade_key: upgrade_key, + step: step + } + + EDD_v3_Upgrades.clearErrors(); + + if ( upgrade_key ) { + EDD_v3_Upgrades.markUpgradeInProgress( upgrade_key ); + } + + $.ajax( { + type: 'POST', + data: data, + url: ajaxurl, + success: function( response ) { + if ( ! response.success ) { + EDD_v3_Upgrades.showError( upgrade_key, response.data ); + return; + } + + if ( response.data.upgrade_completed ) { + EDD_v3_Upgrades.markUpgradeComplete( response.data.upgrade_processed ); + + // If we just completed legacy data removal then we're all done! + if ( 'v30_legacy_data_removed' === response.data.upgrade_processed ) { + EDD_v3_Upgrades.legacyDataRemovalComplete(); + + return; + } + } else if( response.data.percentage ) { + // Update percentage for the upgrade we just processed. + EDD_v3_Upgrades.updateUpgradePercentage( response.data.upgrade_processed, response.data.percentage ); + } + + if ( response.data.next_upgrade && 'v30_legacy_data_removed' === response.data.next_upgrade && 'v30_legacy_data_removed' !== response.data.upgrade_processed ) { + EDD_v3_Upgrades.inProgress = false; + + // Legacy data removal is next, which we do not start automatically. + EDD_v3_Upgrades.showLegacyDataRemoval(); + } else if ( response.data.next_upgrade ) { + // Start the next upgrade (or continuation of current) automatically. + EDD_v3_Upgrades.processStep( response.data.next_upgrade, response.data.next_step, response.data.nonce ); + } else { + EDD_v3_Upgrades.inProgress = false; + EDD_v3_Upgrades.stopAllSpinners(); + } + } + } ).fail( ( data ) => { + // @todo + } ) + }, + + clearErrors: function() { + $( '.edd-v3-migration-error' ).addClass( 'edd-hidden' ).html( '' ); + }, + + showError: function( upgradeKey, message ) { + let container = $( '#edd-v3-migration' ); + if ( 'v30_legacy_data_removed' === upgradeKey ) { + container = $( '#edd-v3-remove-legacy-data' ); + } + const errorWrapper = container.find( '.edd-v3-migration-error' ); + + errorWrapper.html( '

' + message + '

' ).removeClass( 'edd-hidden' ); + + // Stop processing and allow form resubmission. + EDD_v3_Upgrades.inProgress = false; + container.find( 'input' ).prop( 'disabled', false ); + container.find( 'button' ) + .prop( 'disabled', false ) + .addClass( 'button-primary' ) + .removeClass( 'button-secondary disabled updating-message' ); + }, + + markUpgradeInProgress: function( upgradeKey ) { + const upgradeRow = $( '#edd-v3-migration-' + upgradeKey ); + if ( ! upgradeRow.length ) { + return; + } + + const statusIcon = upgradeRow.find( '.dashicons' ); + if ( statusIcon.length ) { + statusIcon.removeClass( 'dashicons-minus' ).addClass( 'dashicons-update' ); + } + + upgradeRow.find( '.edd-migration-percentage' ).removeClass( 'edd-hidden' ); + }, + + updateUpgradePercentage: function( upgradeKey, newPercentage ) { + const upgradeRow = $( '#edd-v3-migration-' + upgradeKey ); + if ( ! upgradeRow.length ) { + return; + } + + upgradeRow.find( '.edd-migration-percentage-value' ).text( newPercentage ); + }, + + markUpgradeComplete: function( upgradeKey ) { + const upgradeRow = $( '#edd-v3-migration-' + upgradeKey ); + if ( ! upgradeRow.length ) { + return; + } + + upgradeRow.addClass( 'edd-upgrade-complete' ); + + const statusIcon = upgradeRow.find( '.dashicons' ); + if ( statusIcon.length ) { + statusIcon.removeClass( 'dashicons-minus dashicons-update' ).addClass( 'dashicons-yes' ); + } + + const statusLabel = upgradeRow.find( '.edd-migration-status .screen-reader-text' ); + if ( statusLabel.length ) { + statusLabel.text( edd_admin_upgrade_vars.migration_complete ); + } + + // Update percentage to 100%; + upgradeRow.find( '.edd-migration-percentage-value' ).text( 100 ); + }, + + showLegacyDataRemoval: function() { + // Un-spin the main submit button. + $( '#edd-v3-migration-button' ).removeClass( 'updating-message' ); + + // Show the "migration complete" message. + $( '#edd-v3-migration-complete' ).removeClass( 'edd-hidden' ); + + const dataRemovalWrapper = $( '#edd-v3-remove-legacy-data' ); + if ( ! dataRemovalWrapper.length ) { + return; + } + + dataRemovalWrapper.removeClass( 'edd-hidden' ); + }, + + legacyDataRemovalComplete: function() { + const wrapper = $( '#edd-v3-remove-legacy-data' ); + if ( ! wrapper.length ) { + return; + } + + wrapper.find( 'form' ).addClass( 'edd-hidden' ); + wrapper.find( '#edd-v3-legacy-data-removal-complete' ).removeClass( 'edd-hidden' ); + }, + + stopAllSpinners: function() { + + } +} + +jQuery( document ).ready( function( $ ) { + EDD_v3_Upgrades.init(); +} ); diff --git a/assets/js/alpine.min.js b/assets/js/alpine.min.js new file mode 100644 index 00000000000..03fb0b2a821 --- /dev/null +++ b/assets/js/alpine.min.js @@ -0,0 +1,5 @@ +(()=>{var Fe=!1,je=!1,J=[];function wt(e){Kr(e)}function Kr(e){J.includes(e)||J.push(e),zr()}function zr(){!je&&!Fe&&(Fe=!0,queueMicrotask(Vr))}function Vr(){Fe=!1,je=!0;for(let e=0;ee.effect(t,{scheduler:r=>{ze?wt(r):r()}}),Ke=e.raw}function Ve(e){C=e}function At(e){let t=()=>{};return[n=>{let i=C(n);e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),V(i))}},()=>{t()}]}var Ot=[],Tt=[],Rt=[];function Ct(e){Rt.push(e)}function Mt(e){Tt.push(e)}function Nt(e){Ot.push(e)}function kt(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function Be(e,t){!e._x_attributeCleanups||Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}var qe=new MutationObserver(He),Ue=!1;function We(){qe.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),Ue=!0}function Hr(){Br(),qe.disconnect(),Ue=!1}var Z=[],Ge=!1;function Br(){Z=Z.concat(qe.takeRecords()),Z.length&&!Ge&&(Ge=!0,queueMicrotask(()=>{qr(),Ge=!1}))}function qr(){He(Z),Z.length=0}function m(e){if(!Ue)return e();Hr();let t=e();return We(),t}var Ye=!1,pe=[];function Dt(){Ye=!0}function Pt(){Ye=!1,He(pe),pe=[]}function He(e){if(Ye){pe=pe.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;os.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{Be(s,o)}),n.forEach((o,s)=>{Ot.forEach(a=>a(s,o))});for(let o of t)r.includes(o)||Rt.forEach(s=>s(o));for(let o of r)t.includes(o)||Tt.forEach(s=>s(o));t=null,r=null,n=null,i=null}function B(e,t,r){return e._x_dataStack=[t,...Q(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function Je(e,t){let r=e._x_dataStack[0];Object.entries(t).forEach(([n,i])=>{r[n]=i})}function Q(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?Q(e.host):e.parentNode?Q(e.parentNode):[]}function X(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,c=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...c,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function It(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(n).forEach(([o,s])=>{let a=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,a,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,a)})};return r(e)}function me(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>Ur(n,i),s=>Ze(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function Ur(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function Ze(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),Ze(e[t[0]],t.slice(1),r)}}var Lt={};function x(e,t){Lt[e]=t}function ee(e,t){return Object.entries(Lt).forEach(([r,n])=>{Object.defineProperty(e,`$${r}`,{get(){return n(t,{Alpine:S,interceptor:me})},enumerable:!1})}),e}function b(e,t,r={}){let n;return h(e,t)(i=>n=i,r),n}function h(...e){return $t(...e)}var $t=Qe;function Ft(e){$t=e}function Qe(e,t){let r={};ee(r,e);let n=[r,...Q(e)];if(typeof t=="function")return Wr(n,t);let i=Gr(n,t);return Yr.bind(null,e,t,i)}function Wr(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(X([n,...e]),i);he(r,o)}}var Xe={};function Jr(e){if(Xe[e])return Xe[e];let t=Object.getPrototypeOf(async function(){}).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)/.test(e)?`(() => { ${e} })()`:e,n=new t(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`);return Xe[e]=n,n}function Gr(e,t){let r=Jr(t);return(n=()=>{},{scope:i={},params:o=[]}={})=>{r.result=void 0,r.finished=!1;let s=X([i,...e]),a=r(r,s);r.finished?he(n,r.result,s,o):a.then(c=>{he(n,c,s,o)})}}function he(e,t,r,n){if(typeof t=="function"){let i=t.apply(r,n);i instanceof Promise?i.then(o=>he(e,o,r,n)):e(i)}else e(t)}function Yr(e,t,r,...n){try{return r(...n)}catch(i){throw console.warn(`Alpine Expression Error: ${i.message} + +Expression: "${t}" + +`,e),i}}var et="x-";function A(e=""){return et+e}function jt(e){et=e}var Kt={};function p(e,t){Kt[e]=t}function te(e,t,r){let n={};return Array.from(t).map(zt((o,s)=>n[o]=s)).filter(Vt).map(Qr(n,r)).sort(Xr).map(o=>Zr(e,o))}function Bt(e){return Array.from(e).map(zt()).filter(t=>!Vt(t))}var tt=!1,re=new Map,Ht=Symbol();function qt(e){tt=!0;let t=Symbol();Ht=t,re.set(t,[]);let r=()=>{for(;re.get(t).length;)re.get(t).shift()();re.delete(t)},n=()=>{tt=!1,r()};e(r),n()}function Zr(e,t){let r=()=>{},n=Kt[t.type]||r,i=[],o=d=>i.push(d),[s,a]=At(e);i.push(a);let c={Alpine:S,effect:s,cleanup:o,evaluateLater:h.bind(h,e),evaluate:b.bind(b,e)},l=()=>i.forEach(d=>d());kt(e,t.original,l);let u=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,c),n=n.bind(n,e,t,c),tt?re.get(Ht).push(n):n())};return u.runCleanups=l,u}var ge=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),_e=e=>e;function zt(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=Ut.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var Ut=[];function H(e){Ut.push(e)}function Vt({name:e}){return Wt().test(e)}var Wt=()=>new RegExp(`^${et}([^:^.]+)\\b`);function Qr(e,t){return({name:r,value:n})=>{let i=r.match(Wt()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var rt="DEFAULT",ye=["ignore","ref","data","bind","init","for","model","transition","show","if",rt,"element"];function Xr(e,t){let r=ye.indexOf(e.type)===-1?rt:e.type,n=ye.indexOf(t.type)===-1?rt:t.type;return ye.indexOf(r)-ye.indexOf(n)}function $(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}var nt=[],it=!1;function q(e){nt.push(e),queueMicrotask(()=>{it||setTimeout(()=>{xe()})})}function xe(){for(it=!1;nt.length;)nt.shift()()}function Gt(){it=!0}function N(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>N(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)N(n,t,!1),n=n.nextElementSibling}function be(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function Jt(){document.body||be("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` + notices ) ) { + $this->notices = array(); + } + + // Parse args + $r = wp_parse_args( $args, array( + 'id' => '', + 'message' => '', + 'class' => false, + 'is_dismissible' => true, + ) ); + + // Prevent a notice from being added more than once. + if ( ! empty( $r['id'] ) && array_key_exists( $r['id'], $this->notices ) ) { + return; + } + + $default_class = 'updated'; + + // One message as string + if ( is_string( $r['message'] ) ) { + $message = '

' . $this->esc_notice( $r['message'] ) . '

'; + + } elseif ( is_array( $r['message'] ) ) { + $message = '

' . implode( '

', array_map( array( $this, 'esc_notice' ), $r['message'] ) ) . '

'; + + // Messages as objects + } elseif ( is_wp_error( $r['message'] ) ) { + $default_class = 'is-error'; + $errors = $r['message']->get_error_messages(); + + switch ( count( $errors ) ) { + case 0: + return false; + + case 1: + $message = '

' . $this->esc_notice( $errors[0] ) . '

'; + break; + + default: + $escaped = array_map( array( $this, 'esc_notice' ), $errors ); + $message = '
    ' . "\n\t" . '
  • ' . implode( '
  • ' . "\n\t" . '
  • ', $escaped ) . '
  • ' . "\n" . '
'; + break; + } + + // Message is an unknown format, so bail + } else { + return false; + } + + // CSS Classes + $classes = array( $default_class ); + if ( ! empty( $r['class'] ) ) { + $classes = explode( ' ', $r['class'] ); + } + + // Add dismissible class + if ( ! empty( $r['is_dismissible'] ) ) { + array_push( $classes, 'is-dismissible' ); + } + + // Assemble the message + $message = '
' . $message . '
'; + $message = str_replace( "'", "\'", $message ); + + // Add notice to notices array + $this->notices[ $r['id'] ] = $message; + } + + /** + * Add all admin area notices + * + * @since 3.0 + */ + public function add_notices() { + + // User can view shop reports + if ( current_user_can( 'view_shop_reports' ) ) { + $this->add_reports_notices(); + } + + // User can manage the entire shop + if ( current_user_can( 'manage_shop_settings' ) ) { + $this->add_data_notices(); + $this->add_settings_notices(); + $this->add_order_upgrade_notice(); + } + + // Generic notices + if ( ! empty( $_REQUEST['edd-message'] ) ) { + $this->add_user_action_notices( $_REQUEST['edd-message'] ); + } + + $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'edd-message' ), $_SERVER['REQUEST_URI'] ); + } + + /** + * Dismiss admin notices when dismiss links are clicked + * + * @since 2.3 + */ + public function dismiss_notices() { + + // Bail if no notices to dismiss + if ( empty( $_GET['edd_notice'] ) || empty( $_GET['_wpnonce'] ) ) { + return; + } + + // Construct key we are dismissing + $key = sanitize_key( $_GET['edd_notice'] ); + + // Bail if sanitized notice is empty + if ( empty( $key ) ) { + return; + } + + // Bail if nonce does not verify + if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'edd_notice_nonce' ) ) { + return; + } + + // Dismiss notice + update_user_meta( get_current_user_id(), "_edd_{$key}_dismissed", 1 ); + edd_redirect( remove_query_arg( array( 'edd_action', 'edd_notice', '_wpnonce' ) ) ); + } + + /** + * Output all admin area notices + * + * @since 2.6.0 bbPress (r6771) + */ + public function display_notices() { + $screen = get_current_screen(); + if ( 'site-health' === $screen->id ) { + return; + } + + $this->show_debugging_notice(); + + // Bail if no notices + if ( empty( $this->notices ) || ! is_array( $this->notices ) ) { + return; + } + + // Start an output buffer + ob_start(); + + // Loop through notices, and add them to buffer + foreach ( $this->notices as $notice ) { + echo $notice; + } + + // Output the current buffer + $notices = ob_get_clean(); + + // Only echo if not empty + if ( ! empty( $notices ) ) { + echo $notices; + } + } + + /** + * Remove unneed admin notices from EDD admin screens. + * + * @since 3.1.1 + * @return void + */ + public function remove_notices() { + if ( ! edd_is_admin_page() ) { + return; + } + + global $wp_filter; + $all_hooks = $wp_filter['admin_notices']->callbacks; + + foreach ( $all_hooks as $priority => $priority_actions ) { + + $priority_functions = wp_list_pluck( $priority_actions, 'function' ); + + foreach ( $priority_functions as $key => $function ) { + if ( is_array( $function ) ) { + + if ( ! empty( $function[0] ) && is_object( $function[0] ) ) { + $class_name = strtolower( get_class( $function[0] ) ); + + if ( false === strpos( $class_name, 'edd' ) ) { + unset( $all_hooks[ $priority ][ $key ] ); + } + } + } elseif ( is_string( $function ) ) { + + $function = strtolower( $function ); + if ( false === strpos( $function, 'edd' ) ) { + + unset( $all_hooks[ $priority ][ $key ] ); + } + } + } + } + $wp_filter['admin_notices']->callbacks = $all_hooks; + } + + /** Private Methods *******************************************************/ + + /** + * Notices about missing pages + * + * @since 3.0 + * @deprecated 3.1.2 + */ + private function add_page_notices() { + + // Checkout page is missing + $purchase_page = edd_get_option( 'purchase_page', '' ); + if ( empty( $purchase_page ) || ( 'trash' === get_post_status( $purchase_page ) ) ) { + $this->add_notice( array( + 'id' => 'edd-no-purchase-page', + /* translators: %s: URL to the settings page */ + 'message' => sprintf( __( 'No checkout page is configured. Set one in Settings.', 'easy-digital-downloads' ), esc_url( edd_get_admin_url( array( 'page' => 'edd-settings', 'tab' => 'general', 'section' => 'pages' ) ) ) ), + 'class' => 'error', + 'is_dismissible' => false, + ) ); + } + } + + /** + * Notices about reports + * + * @since 3.0 + */ + private function add_reports_notices() { + + } + + /** + * Notices for the entire shop + * + * @since 3.0 + * @deprecated 3.1.2 + */ + private function add_system_notices() { + + // Bail if not an EDD admin page + if ( ! edd_is_admin_page() || edd_is_dev_environment() || edd_is_admin_page( 'index.php' ) ) { + return; + } + + // Bail if user cannot manage options + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + // Bail if uploads directory is protected + if ( edd_is_uploads_url_protected() ) { + return; + } + + // Get the upload directory + $upload_directory = edd_get_upload_dir(); + + // Running NGINX + $show_nginx_notice = apply_filters( 'edd_show_nginx_redirect_notice', true ); + if ( $show_nginx_notice && ! empty( $GLOBALS['is_nginx'] ) && ! get_user_meta( get_current_user_id(), '_edd_nginx_redirect_dismissed', true ) ) { + $dismiss_notice_url = wp_nonce_url( add_query_arg( array( + 'edd_action' => 'dismiss_notices', + 'edd_notice' => 'nginx_redirect' + ) ), 'edd_notice_nonce' ); + + $this->add_notice( array( + 'id' => 'edd-nginx', + 'class' => 'error', + 'is_dismissible' => false, + 'message' => array( + /* translators: %s: Uploads directory */ + sprintf( __( 'The files in %s are not currently protected.', 'easy-digital-downloads' ), '' . $upload_directory . '' ), + __( 'To protect them, you must add this NGINX redirect rule.', 'easy-digital-downloads' ), + /* translators: %s: Dismiss notice URL */ + sprintf( __( 'If you have already done this, or it does not apply to your site, you may permenently %s.', 'easy-digital-downloads' ), '' . __( 'dismiss this notice', 'easy-digital-downloads' ) . '' ) + ) + ) ); + } + + // Running Apache + if ( ! empty( $GLOBALS['is_apache'] ) && ! edd_htaccess_exists() && ! get_user_meta( get_current_user_id(), '_edd_htaccess_missing_dismissed', true ) ) { + $dismiss_notice_url = wp_nonce_url( add_query_arg( array( + 'edd_action' => 'dismiss_notices', + 'edd_notice' => 'htaccess_missing' + ) ), 'edd_notice_nonce' ); + + $this->add_notice( array( + 'id' => 'edd-apache', + 'class' => 'error', + 'is_dismissible' => false, + 'message' => array( + sprintf( __( 'The .htaccess file is missing from: %s', 'easy-digital-downloads' ), '' . $upload_directory . '' ), + sprintf( __( 'First, please resave the Misc settings tab a few times. If this warning continues to appear, create a file called ".htaccess" in the %s directory, and copy the following into it:', 'easy-digital-downloads' ), '' . $upload_directory . '' ), + sprintf( __( 'If you have already done this, or it does not apply to your site, you may permenently %s.', 'easy-digital-downloads' ), '' . __( 'dismiss this notice', 'easy-digital-downloads' ) . '' ), + '
' . edd_get_htaccess_rules() . '
' + ) + ) ); + } + } + + /** + * Notices about data (migrations, etc...) + * + * @since 3.0 + */ + private function add_data_notices() { + + // Recount earnings + if ( class_exists( 'EDD_Recount_Earnings' ) ) { + $this->add_notice( array( + 'id' => 'edd-recount-earnings', + 'class' => 'error', + 'is_dismissible' => false, + 'message' => sprintf( + /* translators: 1. link to the recount tool; 2. link to the plugins screen. */ + __( 'Easy Digital Downloads 2.5 contains a built in recount tool. Please deactivate the Easy Digital Downloads - Recount Earnings plugin', 'easy-digital-downloads' ), + esc_url( edd_get_admin_url( array( 'page' => 'edd-tools', 'tab' => 'general' ) ) ), + esc_url( admin_url( 'plugins.php' ) ) + ) + ) ); + } + } + + /** + * Adds a notice about the deprecated Default Rate for Taxes. + * + * @since 3.0 + * @since 3.0.2 - We've found a way to add default tax rates. Leaving the method in case anyone (for some reason) is calling it. + */ + private function add_tax_rate_notice() { + + // Default tax rate not detected. + if ( false === edd_get_option( 'tax_rate' ) ) { + return; + } + + // On Rates page, settings notice is shown. + if ( ! empty( $_GET['page'] ) && 'edd-settings' === $_GET['page'] && ! empty( $_GET['section'] ) && 'rates' === $_GET['section'] ) { + return; + } + + // URL to fix this + $url = edd_get_admin_url( array( + 'page' => 'edd-settings', + 'tab' => 'taxes', + 'section' => 'rates' + ) ); + + // Link + $link = '' . __( 'Review Tax Rates', 'easy-digital-downloads' ) . ''; + + // Add the notice + $this->add_notice( array( + 'id' => 'edd-default-tax-rate', + 'class' => 'error', + /* translators: Link to review existing tax rates. */ + 'message' => '' . __( 'A default tax rate was detected.', 'easy-digital-downloads' ) . '

' . __( 'This setting is no longer used in this version of Easy Digital Downloads. Please confirm your regional tax rates are properly configured and update tax settings to remove this notice.', 'easy-digital-downloads' ) . '

' . $link, + 'is_dismissible' => false + ) ); + } + + /** + * Notices about settings (updating, etc...) + * + * @since 3.0 + */ + private function add_settings_notices() { + + // Settings area + if ( empty( $_GET['page'] ) || ( 'edd-settings' !== $_GET['page'] ) ) { + return; + } + + // Settings updated + if ( ! empty( $_GET['settings-updated'] ) ) { + $this->add_notice( + array( + 'id' => 'edd-notices', + 'message' => __( 'Settings updated.', 'easy-digital-downloads' ) + ) + ); + } + + if ( 'accounting' === filter_input( INPUT_GET, 'section', FILTER_SANITIZE_STRING ) ) { + if ( ! empty( edd_get_option( 'sequential_start_update_failed', false ) ) ) { + $order_number = new \EDD\Orders\Number(); + $this->add_notice( + array( + 'id' => 'edd-sequential-order-numbers-not-updated', + 'message' => sprintf( + /* translators: %s: Next order number */ + __( 'The sequential starting number could not be updated because it could conflict with existing orders. The next assigned order number is %s.', 'easy-digital-downloads' ), + '' . $order_number->format( (int) get_option( 'edd_next_order_number' ) ) . '' + ), + 'class' => 'error', + ) + ); + edd_delete_option( 'sequential_start_update_failed' ); + } + } + } + + /** + * Adds a notice if an order migration is running. + * This is only shown if the migration is running via UI by a different user or on another screen. + * + * @since 3.1.2 + * @return void + */ + private function add_order_upgrade_notice() { + if ( edd_has_upgrade_completed( 'migrate_orders' ) ) { + return; + } + if ( ! get_option( '_edd_v30_doing_order_migration', false ) ) { + return; + } + if ( get_option( 'edd_v30_cli_migration_running', false ) ) { + return; + } + $this->add_notice( + array( + 'id' => 'edd-v30-order-migration-running', + 'class' => 'updated', + 'message' => __( 'Easy Digital Downloads is migrating orders. Sales and earnings data for your store will be updated when all orders have been migrated.', 'easy-digital-downloads' ), + 'is_dismissible' => false, + ) + ); + } + + /** + * Notices about actions that the user has taken + * + * @since 3.0 + * + * @param string $notice + */ + private function add_user_action_notices( $notice = '' ) { + + // Sanitize notice key + $notice = sanitize_key( $notice ); + + // Bail if notice is empty + if ( empty( $notice ) ) { + return; + } + + // Shop discounts errors + if ( current_user_can( 'manage_shop_discounts' ) ) { + switch ( $notice ) { + case 'discount_added' : + $this->add_notice( array( + 'id' => 'edd-discount-added', + 'message' => __( 'Discount code added.', 'easy-digital-downloads' ) + ) ); + break; + case 'discount_add_failed' : + $this->add_notice( array( + 'id' => 'edd-discount-add-fail', + 'message' => __( 'There was a problem adding that discount code, please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_exists' : + $this->add_notice( array( + 'id' => 'edd-discount-exists', + 'message' => __( 'A discount with that code already exists, please use a different code.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_updated' : + $this->add_notice( array( + 'id' => 'edd-discount-updated', + 'message' => __( 'Discount code updated.', 'easy-digital-downloads' ) + ) ); + break; + case 'discount_not_changed' : + $this->add_notice( array( + 'id' => 'edd-discount-not-changed', + 'message' => __( 'No changes were made to that discount code.', 'easy-digital-downloads' ) + ) ); + break; + case 'discount_update_failed' : + $this->add_notice( array( + 'id' => 'edd-discount-updated-fail', + 'message' => __( 'There was a problem updating that discount code, please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_validation_failed' : + $this->add_notice( array( + 'id' => 'edd-discount-validation-fail', + 'message' => __( 'The discount code could not be added because one or more of the required fields was empty, please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_invalid_code': + $this->add_notice( array( + 'id' => 'edd-discount-invalid-code', + 'message' => __( 'The discount code entered is invalid; only alphanumeric characters are allowed, please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_invalid_amount' : + $this->add_notice( array( + 'id' => 'edd-discount-invalid-amount', + 'message' => __( 'The discount amount must be a valid percentage or numeric flat amount. Please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_deleted': + $this->add_notice( array( + 'id' => 'edd-discount-deleted', + 'message' => __( 'Discount code deleted.', 'easy-digital-downloads' ) + ) ); + break; + case 'discount_delete_failed': + $this->add_notice( array( + 'id' => 'edd-discount-delete-fail', + 'message' => __( 'There was a problem deleting that discount code, please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_activated': + $this->add_notice( array( + 'id' => 'edd-discount-activated', + 'message' => __( 'Discount code activated.', 'easy-digital-downloads' ) + ) ); + break; + case 'discount_activation_failed': + $this->add_notice( array( + 'id' => 'edd-discount-activation-fail', + 'message' => __( 'There was a problem activating that discount code, please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'discount_deactivated': + $this->add_notice( array( + 'id' => 'edd-discount-deactivated', + 'message' => __( 'Discount code deactivated.', 'easy-digital-downloads' ) + ) ); + break; + case 'discount_deactivation_failed': + $this->add_notice( array( + 'id' => 'edd-discount-deactivation-fail', + 'message' => __( 'There was a problem deactivating that discount code, please try again.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + } + } + + // Shop reports errors + if ( current_user_can( 'view_shop_reports' ) ) { + switch( $notice ) { + case 'refreshed-reports' : + $this->add_notice( array( + 'id' => 'edd-refreshed-reports', + 'message' => __( 'The reports have been refreshed.', 'easy-digital-downloads' ) + ) ); + break; + } + } + + // Shop settings errors + if ( current_user_can( 'manage_shop_settings' ) ) { + switch( $notice ) { + case 'settings-imported' : + $this->add_notice( array( + 'id' => 'edd-settings-imported', + 'message' => __( 'The settings have been imported.', 'easy-digital-downloads' ) + ) ); + break; + case 'api-key-generated' : + $this->add_notice( array( + 'id' => 'edd-api-key-generated', + 'message' => __( 'API keys successfully generated.', 'easy-digital-downloads' ) + ) ); + break; + case 'api-key-exists' : + $this->add_notice( array( + 'id' => 'edd-api-key-exists', + 'message' => __( 'The specified user already has API keys.', 'easy-digital-downloads' ), + 'class' => 'error' + ) ); + break; + case 'api-key-regenerated' : + $this->add_notice( array( + 'id' => 'edd-api-key-regenerated', + 'message' => __( 'API keys successfully regenerated.', 'easy-digital-downloads' ) + ) ); + break; + case 'api-key-revoked' : + $this->add_notice( array( + 'id' => 'edd-api-key-revoked', + 'message' => __( 'API keys successfully revoked.', 'easy-digital-downloads' ) + ) ); + break; + case 'test-purchase-email-sent': + $this->add_notice( + array( + 'id' => 'edd-test-purchase-receipt-sent', + 'message' => __( 'The test email was sent successfully.', 'easy-digital-downloads' ) + ) + ); + break; + case 'test-summary-email-sent': + $this->add_notice( + array( + 'id' => 'edd-test-summary-email-sent', + 'message' => __( 'The test email summary was sent successfully.', 'easy-digital-downloads' ) + ) + ); + break; + + case 'missing-pass-key': + $this->add_notice( + array( + 'id' => 'edd-missing-pass-key', + 'message' => __( 'Your extensions could not be refreshed because you have not verified your license key.', 'easy-digital-downloads' ), + 'class' => 'error', + ) + ); + break; + } + } + + // Shop payments errors + if ( current_user_can( 'edit_shop_payments' ) ) { + switch( $notice ) { + case 'note-added' : + $this->add_notice( array( + 'id' => 'edd-note-added', + 'message' => __( 'The note has been added successfully.', 'easy-digital-downloads' ) + ) ); + break; + case 'payment-updated' : + $this->add_notice( array( + 'id' => 'edd-payment-updated', + 'message' => __( 'The order has been updated successfully.', 'easy-digital-downloads' ) + ) ); + break; + case 'order_added' : + $this->add_notice( array( + 'id' => 'edd-order-added', + 'message' => __( 'Order successfully created.', 'easy-digital-downloads' ) + ) ); + break; + case 'order_trashed' : + $this->add_notice( array( + 'id' => 'edd-order-trashed', + 'message' => __( 'The order has been moved to the trash.', 'easy-digital-downloads' ) + ) ); + break; + case 'order_restored' : + $this->add_notice( array( + 'id' => 'edd-order-restored', + 'message' => __( 'The order has been restored.', 'easy-digital-downloads' ) + ) ); + break; + case 'payment_deleted' : + $this->add_notice( array( + 'id' => 'edd-payment-deleted', + 'message' => __( 'The order has been deleted.', 'easy-digital-downloads' ) + ) ); + break; + case 'email_sent' : + $this->add_notice( array( + 'id' => 'edd-payment-sent', + 'message' => __( 'The purchase receipt has been resent.', 'easy-digital-downloads' ) + ) ); + break; + case 'email_send_failed': + $this->add_notice( array( + 'id' => 'edd-payment-sent', + 'message' => __( 'Failed to send purchase receipt.', 'easy-digital-downloads' ) + ) ); + break; + case 'payment-note-deleted' : + $this->add_notice( array( + 'id' => 'edd-note-deleted', + 'message' => __( 'The order note has been deleted.', 'easy-digital-downloads' ) + ) ); + break; + } + } + + // Customer Notices + if ( current_user_can( 'edit_shop_payments' ) ) { + switch( $notice ) { + case 'customer-deleted' : + $this->add_notice( array( + 'id' => 'edd-customer-deleted', + 'message' => __( 'Customer successfully deleted.', 'easy-digital-downloads' ), + ) ); + break; + case 'user-verified' : + $this->add_notice( array( + 'id' => 'edd-user-verified', + 'message' => __( 'User successfully verified.', 'easy-digital-downloads' ), + ) ); + break; + case 'email-added' : + $this->add_notice( array( + 'id' => 'edd-customer-email-added', + 'message' => __( 'Customer email added.', 'easy-digital-downloads' ), + ) ); + break; + case 'email-removed' : + $this->add_notice( array( + 'id' => 'edd-customer-email-removed', + 'message' => __( 'Customer email deleted.', 'easy-digital-downloads' ), + ) ); + break; + case 'email-remove-failed' : + $this->add_notice( array( + 'id' => 'edd-customer-email-remove-failed', + 'message' => __( 'Failed to delete customer email.', 'easy-digital-downloads' ), + 'class' => 'error', + ) ); + break; + case 'primary-email-updated' : + $this->add_notice( array( + 'id' => 'eddedd-customer-primary-email-updated', + 'message' => __( 'Primary email updated for customer.', 'easy-digital-downloads' ) + ) ); + break; + case 'primary-email-failed' : + $this->add_notice( array( + 'id' => 'edd-customer-primary-email-failed', + 'message' => __( 'Failed to set primary email.', 'easy-digital-downloads' ), + 'class' => 'error', + ) ); + break; + case 'address-removed' : + $this->add_notice( array( + 'id' => 'edd-customer-address-removed', + 'message' => __( 'Customer address deleted.', 'easy-digital-downloads' ) + ) ); + break; + case 'address-remove-failed' : + $this->add_notice( array( + 'id' => 'edd-customer-address-remove-failed', + 'message' => __( 'Failed to delete customer address.', 'easy-digital-downloads' ), + 'class' => 'error', + ) ); + break; + } + } + + if ( 'one-click-upgrade' === $notice && edd_is_pro() && current_user_can( 'install_plugins' ) && edd_is_admin_page( 'settings' ) ) { + $this->add_notice( + array( + 'id' => 'edd-upgraded', + 'message' => sprintf( + /* Translators: 1. opening strong tag, do not translate; 2. closing strong tag, do not translate */ + __( 'Congratulations! You are now running %1$sEasy Digital Downloads (Pro)%2$s.', 'easy-digital-downloads' ), + '', + '' + ), + ) + ); + } + } + + /** + * Show a notice if debugging is enabled in the EDD settings. + * Does not show if only the `EDD_DEBUG_MODE` constant is defined. + * + * @since 2.11.5 + * @return void + */ + private function show_debugging_notice() { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + return; + } + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + if ( ! edd_get_option( 'debug_mode', false ) ) { + return; + } + + /** + * The notices JS needs to be output wherever the notice is displayed, not just EDD screens. + * If more notices add to the script then this enqueue will need to be moved. + * + * @since 3.0 + */ + wp_enqueue_script( 'edd-admin-notices', EDD_PLUGIN_URL . 'assets/js/edd-admin-notices.js', array( 'jquery' ), EDD_VERSION, true ); + $view_url = add_query_arg( + array( + 'post_type' => 'download', + 'page' => 'edd-tools', + 'tab' => 'debug_log', + ), + admin_url( 'edit.php' ) + ); + ?> +

+

+ +

+

+ + + +

+
+ clear_log_file(); + wp_send_json_success( wpautop( __( 'The debug log has been cleared and logging has been disabled.', 'easy-digital-downloads' ) ) ); + } + + /** + * Escape message string output + * + * @since 2.6.0 bbPress (r6775) + * + * @param string $message + * + * @return string + */ + private function esc_notice( $message = '' ) { + $tags = wp_kses_allowed_html( 'post' ); + $text = wp_kses( $message, $tags ); + + return $text; + } +} diff --git a/includes/admin/class-list-table.php b/includes/admin/class-list-table.php new file mode 100644 index 00000000000..efb5e4b583a --- /dev/null +++ b/includes/admin/class-list-table.php @@ -0,0 +1,244 @@ + 0 + ); + + /** + * Get a request var, or return the default if not set. + * + * @since 3.0 + * + * @param string $var + * @param mixed $default + * @return mixed Un-sanitized request var + */ + public function get_request_var( $var = '', $default = false ) { + return isset( $_REQUEST[ $var ] ) + ? $_REQUEST[ $var ] + : $default; + } + + /** + * Get a status request var, if set. + * + * @since 3.0 + * + * @param mixed $default + * @return string + */ + protected function get_status( $default = '' ) { + return sanitize_key( $this->get_request_var( 'status', $default ) ); + } + + /** + * Retrieve the current page number. + * + * @since 3.0 + * + * @return int Current page number. + */ + protected function get_paged() { + return absint( $this->get_request_var( 'paged', 1 ) ); + } + + /** + * Retrieve the current page number. + * + * @since 3.0 + * + * @return int Current page number. + */ + protected function get_search() { + return urldecode( trim( $this->get_request_var( 's', '' ) ) ); + } + + /** + * Retrieves the data to be populated into the list table. + * + * @since 3.0 + * + * @return array Array of list table data. + */ + abstract public function get_data(); + + /** + * Retrieve the view types + * + * @since 1.4 + * + * @return array $views All the views available + */ + public function get_views() { + + // Get the current status + $current = $this->get_status(); + + // Args to remove + $remove = array( 'edd-message', 'status', 'paged', '_wpnonce' ); + + // Base URL + $url = remove_query_arg( $remove, $this->get_base_url() ); + + // Is all selected? + $class = in_array( $current, array( '', 'all' ), true ) + ? ' class="current"' + : ''; + + // All + $count = ' (' . esc_attr( $this->counts['total'] ) . ')'; + $label = __( 'All', 'easy-digital-downloads' ) . $count; + $views = array( + 'all' => sprintf( '%s', esc_url( $url ), $class, $label ), + ); + + // Remove total from counts array + $counts = $this->counts; + unset( $counts['total'] ); + + // Loop through statuses. + if ( ! empty( $counts ) ) { + foreach ( $counts as $status => $count ) { + $count_url = add_query_arg( array( + 'status' => sanitize_key( $status ), + 'paged' => false, + ), $url ); + + $class = ( $current === $status ) + ? ' class="current"' + : ''; + + $count = ' (' . absint( $this->counts[ $status ] ) . ')'; + + $label = edd_get_status_label( $status ) . $count; + $views[ $status ] = sprintf( '%s', esc_url( $count_url ), $class, $label ); + } + } + + return $views; + } + + /** + * Parse pagination query arguments into keys & values that the Query class + * can understand and use to retrieve the correct results from the database. + * + * @since 3.0 + * + * @param array $args + * + * @return array + */ + public function parse_pagination_args( $args = array() ) { + + // Get pagination values + $order = isset( $_GET['order'] ) ? sanitize_text_field( $_GET['order'] ) : 'DESC'; // WPCS: CSRF ok. + $orderby = isset( $_GET['orderby'] ) ? sanitize_text_field( $_GET['orderby'] ) : 'id'; // WPCS: CSRF ok. + $paged = $this->get_paged(); + + // Only perform paged math if numeric and greater than 1 + if ( ! empty( $paged ) && is_numeric( $paged ) && ( $paged > 1 ) ) { + $offset = ceil( $this->per_page * ( $paged - 1 ) ); + + // Otherwise, default to the first page of results + } else { + $offset = 0; + } + + // Parse pagination args into passed args + $r = wp_parse_args( $args, array( + 'number' => $this->per_page, + 'offset' => $offset, + 'order' => $order, + 'orderby' => $orderby + ) ); + + // Return args + return array_filter( $r ); + } + + /** + * Show the search field. + * + * @since 3.0 + * + * @param string $text Label for the search box + * @param string $input_id ID of the search box + */ + public function search_box( $text, $input_id ) { + + // Bail if no customers and no search + if ( ! $this->get_search() && ! $this->has_items() ) { + return; + } + + $orderby = $this->get_request_var( 'orderby' ); + $order = $this->get_request_var( 'order' ); + $input_id = $input_id . '-search-input'; + + if ( ! empty( $orderby ) ) { + echo ''; + } + + if ( ! empty( $order ) ) { + echo ''; + } + + ?> + + + + add_section( array( + 'id' => 'general', + 'label' => esc_html__( 'General', 'easy-digital-downloads' ), + 'callback' => array( $this, 'section_general' ) + ) ); + } elseif ( count( $sections ) ) { + foreach ( $sections as $section ) { + $this->add_section( $section ); + } + } + } + + /** + * Setup the sections for the current item + * + * @since 3.0 + * + * @param object $item + */ + public function set_item( $item = false ) { + $this->item = $item; + } + + /** + * Output the contents + * + * @since 3.0 + */ + public function display() { + $use_js = ! empty( $this->use_js ) + ? ' use-js' + : ''; + $role = $this->use_js ? 'tablist' : 'menu'; + + ob_start(); ?> + +
+
+
    + get_all_section_links(); ?> +
+ +
+ get_all_section_contents(); ?> +
+
+
+ nonce_field(); + + if ( ! empty( $this->item ) ) : ?> + + + + +
+ + sections[] = (object) wp_parse_args( $section, array( + 'id' => '', + 'label' => '', + 'icon' => 'admin-settings', + 'callback' => '' + ) ); + } + + /** + * Is a section the current section? + * + * @since 3.0 + * + * @param string $section_id + * + * @return bool + */ + private function is_current_section( $section_id = '' ) { + return ( $section_id === $this->current_section ); + } + + /** + * Output the nonce field for the meta box + * + * @since 3.0 + */ + protected function nonce_field() { + wp_nonce_field( + 'edd_' . $this->id . '_sections_nonce', + 'edd_' . $this->id . 'nonce_sections', + true + ); + } + + /** + * Get all section links + * + * @since 3.0 + * + * @return string + */ + protected function get_all_section_links() { + ob_start(); + + // Loop through sections + foreach ( $this->sections as $section ) : + + $url = $this->use_js + ? '#' . esc_attr( $this->id . $section->id ) + : add_query_arg( 'view', sanitize_key( $section->id ), $this->base_url ); + + // Special selected section + $selected = ! $this->use_js && $this->is_current_section( $section->id ) + ? 'aria-current="true"' + : ''; + $class = $this->is_current_section( $section->id ) ? 'section-title section-title--is-active' : 'section-title'; + $aria_attributes = $this->use_js ? 'role="tab" aria-controls="' . esc_attr( $this->id . $section->id ) . '"' : 'role="menuitem"'; + ?> + +
  • > + + + label; // Allow HTML ?> + +
  • + + use_js ) + ? wp_filter_object_list( $this->sections, array( 'id' => $this->current_section ) ) + : $this->sections; + + // Bail if no sections + if ( empty( $sections ) ) { + return; + } + + // Loop through sections + foreach ( $sections as $section ) : + + // Special selected section + $selected = ! $this->is_current_section( $section->id ) + ? 'style="display: none;"' + : ''; ?> + +
    >callback ) ) { + if ( is_callable( $section->callback ) ) { + call_user_func( $section->callback, $this->item ); + + } elseif ( is_array( $section->callback ) && is_callable( $section->callback[0] ) ) { + isset( $section->callback[1] ) + ? call_user_func_array( $section->callback[0], $section->callback[1] ) + : call_user_func_array( $section->callback[0], array() ); + + } else { + _e( 'Invalid section', 'easy-digital-downloads' ); + } + } else { + die; + do_action( 'edd_' . $section->id . 'section_contents', $this ); + } + + ?>
    + + + +
    + + + + + + + +
    + + + — +
    + + 'address', + 'plural' => 'addresses', + 'ajax' => false + ) ); + + $this->process_bulk_action(); + $this->get_counts(); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'address'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 3.0 + * + * @param array $item Contains all the data of the customers + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + + case 'type' : + $value = edd_get_address_type_label( $item['type'] ); + if ( ! empty( $item['is_primary'] ) ) { + $value .= '  ' . esc_html__( 'Primary', 'easy-digital-downloads' ) . ''; + } + break; + + case 'date_created' : + $value = ''; + break; + + default: + $value = ! empty( $item[ $column_name ] ) + ? esc_html( $item[ $column_name ] ) + : '—'; + break; + } + + // Filter & return + return apply_filters( 'edd_customers_column_' . $column_name, $value, $item['id'] ); + } + + /** + * Return the contents of the "Name" column + * + * @since 3.0 + * + * @param array $item + * @return string + */ + public function column_address( $item ) { + $state = $extra = ''; + $status = $this->get_status(); + $address = ! empty( $item['address'] ) ? $item['address'] : '—'; + $address2 = ! empty( $item['address2'] ) ? $item['address2'] : ''; + $city = ! empty( $item['city'] ) ? $item['city'] : ''; + $code = ! empty( $item['postal_code'] ) ? $item['postal_code'] : ''; + + // Address2 + if ( ! empty( $address2 ) ) { + $extra .= '
    ' . $address2; + } + + // City & Zip + if ( ! empty( $city ) || ! empty( $code ) ) { + $extra .= '
    ' . implode( ' ', array( $city, $code ) ); + } + + // Get the item status + $item_status = ! empty( $item['status'] ) + ? $item['status'] + : 'verified'; + + // Get the customer ID + $customer_id = ! empty( $item['customer_id'] ) + ? absint( $item['customer_id'] ) + : 0; + + // Link to customer + $customer_url = edd_get_admin_url( array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => absint( $customer_id ), + ) ); + + // State + if ( ( ! empty( $status ) && ( $status !== $item_status ) ) || ( $item_status !== 'active' ) ) { + switch ( $status ) { + case 'pending' : + $value = __( 'Pending', 'easy-digital-downloads' ); + break; + case 'verified' : + case '' : + default : + $value = __( 'Active', 'easy-digital-downloads' ); + break; + } + + $state = ' — ' . $value; + } + + // Concatenate and return + return '' . esc_html( $address ) . '' . esc_html( $state ) . '' . $extra . $this->row_actions( $this->get_row_actions( $item ) ); + } + + /** + * Gets the row actions for the customer address. + * + * @since 3.0 + * @param array $item + * @return array + */ + private function get_row_actions( $item ) { + // Link to customer + $customer_url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $item['customer_id'] ), + ) + ); + + // Actions + $actions = array( + 'view' => '' . esc_html__( 'View', 'easy-digital-downloads' ) . '' + ); + + if ( empty( $item['is_primary'] ) ) { + $delete_url = wp_nonce_url( edd_get_admin_url( array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $item['id'] ), + 'edd_action' => 'customer-remove-address' + ) ), 'edd-remove-customer-address' ); + $actions['delete'] = '' . esc_html__( 'Delete', 'easy-digital-downloads' ) . ''; + } + + /** + * Filter the customer address row actions. + * + * @since 3.0 + * @param array $actions The array of row actions. + * @param array $item The specific item (customer address). + */ + return apply_filters( 'edd_customer_address_row_actions', $actions, $item ); + } + + /** + * Return the contents of the "Name" column + * + * @since 3.0 + * + * @param array $item + * @return string + */ + public function column_customer( $item ) { + + // Get the customer ID + $customer_id = ! empty( $item['customer_id'] ) + ? absint( $item['customer_id'] ) + : 0; + + // Bail if no customer ID + if ( empty( $customer_id ) ) { + return '—'; + } + + // Try to get the customer + $customer = edd_get_customer( $customer_id ); + + // Bail if customer no longer exists + if ( empty( $customer ) ) { + return '—'; + } + + // Link to customer + $customer_url = edd_get_admin_url( array( + 'page' => 'edd-customers', + 'page_type' => 'physical', + 's' => 'c:' . absint( $customer_id ) + ) ); + + // Concatenate and return + return '' . esc_html( $customer->name ) . ''; + } + + /** + * Render the checkbox column + * + * @access public + * @since 3.0 + * + * @param array $item Address object. + * + * @return string Displays a checkbox + */ + public function column_cb( $item ) { + $customer = edd_get_customer_by( 'id', $item['customer_id'] ); + $name = sprintf( + /* translators: customer address id */ + __( 'Address ID: %s', 'easy-digital-downloads' ), + $item['id'] + ); + if ( ! empty( $customer->name ) ) { + $name = $customer->name; + } + return sprintf( + '', + /*$1%s*/ 'customer', + /*$2%s*/ esc_attr( $item['id'] ), + /* translators: customer name or address id */ + esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $name ) ) + ); + } + + /** + * Retrieve the customer counts + * + * @access public + * @since 3.0 + * @return void + */ + public function get_counts() { + $this->counts = edd_get_customer_address_counts(); + } + + /** + * Retrieve the table columns + * + * @since 3.0 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return apply_filters( 'edd_report_customer_columns', array( + 'cb' => '', + 'address' => __( 'Address', 'easy-digital-downloads' ), + 'region' => __( 'Region', 'easy-digital-downloads' ), + 'country' => __( 'Country', 'easy-digital-downloads' ), + 'customer' => __( 'Customer', 'easy-digital-downloads' ), + 'type' => __( 'Type', 'easy-digital-downloads' ), + 'date_created' => __( 'Date', 'easy-digital-downloads' ) + ) ); + } + + /** + * Get the sortable columns + * + * @since 2.1 + * @return array Array of all the sortable columns + */ + public function get_sortable_columns() { + return array( + 'date_created' => array( 'date_created', true ), + 'address' => array( 'address', false ), + 'region' => array( 'region', true ), + 'country' => array( 'country', true ), + 'customer' => array( 'customer_id', false ), + 'type' => array( 'type', false ) + ); + } + + /** + * Retrieve the bulk actions + * + * @access public + * @since 3.0 + * @return array Array of the bulk actions + */ + public function get_bulk_actions() { + return array( + 'delete' => __( 'Delete', 'easy-digital-downloads' ) + ); + } + + /** + * Process the bulk actions + * + * @access public + * @since 3.0 + */ + public function process_bulk_action() { + if ( empty( $_REQUEST['_wpnonce'] ) ) { + return; + } + + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-addresses' ) ) { + return; + } + + $ids = isset( $_GET['customer'] ) + ? $_GET['customer'] + : false; + + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + foreach ( $ids as $id ) { + switch ( $this->current_action() ) { + case 'delete' : + edd_delete_customer_address( $id ); + break; + } + } + } + + /** + * Get all of the items to display, given the current filters + * + * @since 3.0 + * + * @return array $data All the row data + */ + public function get_data() { + $data = array(); + $search = $this->get_search(); + $args = array( 'status' => $this->get_status() ); + + // Customer ID + if ( strpos( $search, 'c:' ) !== false ) { + $args['customer_id'] = trim( str_replace( 'c:', '', $search ) ); + + // Country + } elseif ( strpos( $search, 'country:' ) !== false ) { + $search = substr( $search, strlen( 'country:' ) ); + $args['search'] = $search; + $args['search_columns'] = array( 'country' ); + + // Zip + } elseif ( strpos( $search, 'zip:' ) !== false ) { + $search = substr( $search, strlen( 'zip:' ) ); + $args['search'] = $search; + $args['search_columns'] = array( 'zip' ); + + // Region + } elseif ( strpos( $search, 'region:' ) !== false ) { + $search = substr( $search, strlen( 'region:' ) ); + $args['search'] = $search; + $args['search_columns'] = array( 'region' ); + + // City + } elseif ( strpos( $search, 'city:' ) !== false ) { + $search = substr( $search, strlen( 'city:' ) ); + $args['search'] = $search; + $args['search_columns'] = array( 'city' ); + + // Any... + } else { + $args['search'] = $search; + $args['search_columns'] = array( 'address', 'address2', 'city', 'region', 'country', 'postal_code' ); + } + + // Parse pagination + $this->args = $this->parse_pagination_args( $args ); + + // Get the data + $addresses = edd_get_customer_addresses( $this->args ); + + if ( ! empty( $addresses ) ) { + foreach ( $addresses as $address ) { + $data[] = array( + 'id' => $address->id, + 'customer_id' => $address->customer_id, + 'status' => $address->status, + 'type' => $address->type, + 'address' => $address->address, + 'address2' => $address->address2, + 'city' => $address->city, + 'region' => $address->region, + 'postal_code' => $address->postal_code, + 'country' => $address->country, + 'date_created' => $address->date_created, + 'date_modified' => $address->date_modified, + 'is_primary' => $address->is_primary, + ); + } + } + + return $data; + } + + /** + * Setup the final data for the table + * + * @since 3.0 + * @return void + */ + public function prepare_items() { + $this->_column_headers = array( + $this->get_columns(), + array(), + $this->get_sortable_columns() + ); + + $this->items = $this->get_data(); + + $status = $this->get_status( 'total' ); + + // Setup pagination + $this->set_pagination_args( array( + 'total_pages' => ceil( $this->counts[ $status ] / $this->per_page ), + 'total_items' => $this->counts[ $status ], + 'per_page' => $this->per_page + ) ); + } + + /** + * Generate the table navigation above or below the table. + * We're overriding this to turn off the referer param in `wp_nonce_field()`. + * + * @param string $which + * @since 3.1.0.4 + */ + protected function display_tablenav( $which ) { + if ( 'top' === $which ) { + wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false ); + } + ?> +
    + + has_items() ) : ?> +
    + bulk_actions( $which ); ?> +
    + extra_tablenav( $which ); + $this->pagination( $which ); + ?> + +
    +
    + 'email', + 'plural' => 'emails', + 'ajax' => false + ) ); + + $this->process_bulk_action(); + $this->get_counts(); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'email'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 3.0 + * + * @param array $item Contains all the data of the customers + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + + case 'id' : + $value = $item['id']; + break; + + case 'email' : + $value = '' . esc_html( $item['email'] ) . ''; + break; + + case 'type' : + $value = ( 'primary' === $item['type'] ) + ? esc_html__( 'Primary', 'easy-digital-downloads' ) + : esc_html__( 'Secondary', 'easy-digital-downloads' ); + break; + + case 'date_created' : + $value = ''; + break; + + default: + $value = isset( $item[ $column_name ] ) + ? $item[ $column_name ] + : null; + break; + } + + // Filter & return + return apply_filters( 'edd_customers_column_' . $column_name, $value, $item['id'] ); + } + + /** + * Return the contents of the "Name" column + * + * @since 3.0 + * + * @param array $item + * @return string + */ + public function column_email( $item ) { + $state = ''; + $status = $this->get_status(); + $email = ! empty( $item['email'] ) ? $item['email'] : '—'; + + // Get the item status + $item_status = ! empty( $item['status'] ) + ? $item['status'] + : 'verified'; + + // Get the customer ID + $customer_id = ! empty( $item['customer_id'] ) + ? absint( $item['customer_id'] ) + : 0; + + // Link to customer + $customer_url = edd_get_admin_url( array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => absint( $customer_id ), + ) ); + + // State + if ( ( ! empty( $status ) && ( $status !== $item_status ) ) || ( $item_status !== 'active' ) ) { + switch ( $status ) { + case 'pending' : + $value = __( 'Pending', 'easy-digital-downloads' ); + break; + case 'verified' : + case '' : + default : + $value = __( 'Active', 'easy-digital-downloads' ); + break; + } + + $state = ' — ' . $value; + } + + // Concatenate and return + return '' . esc_html( $email ) . '' . esc_html( $state ) . '' . $this->row_actions( $this->get_row_actions( $item ) ); + } + + /** + * Gets the row actions for the customer email address. + * + * @since 3.0 + * @param array $item + * @return array + */ + private function get_row_actions( $item ) { + // Link to customer + $customer_url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $item['customer_id'] ), + ) + ); + + // Actions + $actions = array( + 'view' => '' . __( 'View', 'easy-digital-downloads' ) . '', + ); + + // Non-primary email actions + if ( ! empty( $item['email'] ) && ( empty( $item['type'] ) || 'primary' !== $item['type'] ) ) { + $delete_url = wp_nonce_url( edd_get_admin_url( array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $item['customer_id'] ), + 'email' => rawurlencode( $item['email'] ), + 'edd_action' => 'customer-remove-email', + ) ), 'edd-remove-customer-email' ); + $actions['delete'] = '' . esc_html__( 'Delete', 'easy-digital-downloads' ) . ''; + } + + /** + * Filter the customer email address row actions. + * + * @since 3.0 + * @param array $actions The array of row actions. + * @param array $item The specific item (customer email address). + */ + return apply_filters( 'edd_customer_email_address_row_actions', $actions, $item ); + } + + /** + * Return the contents of the "Name" column + * + * @since 3.0 + * + * @param array $item + * @return string + */ + public function column_customer( $item ) { + + // Get the customer ID + $customer_id = ! empty( $item['customer_id'] ) + ? absint( $item['customer_id'] ) + : 0; + + // Bail if no customer ID + if ( empty( $customer_id ) ) { + return '—'; + } + + // Try to get the customer + $customer = edd_get_customer( $customer_id ); + + // Bail if customer no longer exists + if ( empty( $customer ) ) { + return '—'; + } + + // Link to customer + $customer_url = edd_get_admin_url( array( + 'page' => 'edd-customers', + 'page_type' => 'emails', + 's' => 'c:' . absint( $customer_id ) + ) ); + + // Concatenate and return + return '' . esc_html( $customer->name ) . ''; + } + + /** + * Render the checkbox column + * + * @access public + * @since 3.0 + * + * @param array $item + * + * @return string Displays a checkbox + */ + public function column_cb( $item ) { + $is_primary = ! empty( $item['type'] ) && 'primary' === $item['type']; + $primary_attributes = $is_primary ? ' disabled' : ''; + $title = $is_primary ? __( 'Primary email addresses cannot be deleted.', 'easy-digital-downloads' ) : ''; + + return sprintf( + '', + /*$1%s*/ esc_attr( 'customer' ), + /*$2%s*/ esc_attr( $item['id'] ), + /* translators: customer email */ + esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $item['email'] ) ), + /*$4%s*/ esc_attr( $title ), + /*$5%s*/ $primary_attributes + ); + } + + /** + * Retrieve the customer counts + * + * @access public + * @since 3.0 + * @return void + */ + public function get_counts() { + $this->counts = edd_get_customer_email_address_counts(); + } + + /** + * Retrieve the table columns + * + * @since 3.0 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return apply_filters( 'edd_report_customer_columns', array( + 'cb' => '', + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'customer' => __( 'Customer', 'easy-digital-downloads' ), + 'type' => __( 'Type', 'easy-digital-downloads' ), + 'date_created' => __( 'Date', 'easy-digital-downloads' ) + ) ); + } + + /** + * Get the sortable columns + * + * @since 2.1 + * @return array Array of all the sortable columns + */ + public function get_sortable_columns() { + return array( + 'date_created' => array( 'date_created', true ), + 'email' => array( 'email', true ), + 'customer' => array( 'customer_id', false ), + 'type' => array( 'type', false ) + ); + } + + /** + * Retrieve the bulk actions + * + * @access public + * @since 3.0 + * @return array Array of the bulk actions + */ + public function get_bulk_actions() { + return array( + 'delete' => __( 'Delete', 'easy-digital-downloads' ) + ); + } + + /** + * Process the bulk actions + * + * @access public + * @since 3.0 + */ + public function process_bulk_action() { + if ( empty( $_REQUEST['_wpnonce'] ) ) { + return; + } + + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-emails' ) ) { + return; + } + + $ids = isset( $_GET['customer'] ) + ? $_GET['customer'] + : false; + + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + /* + * Only non-primary email addresses can be deleted, so we're building up a safelist using the provided + * IDs. Each ID will be matched against this prior to deletion. + */ + $non_primary_address_ids = edd_get_customer_email_addresses( array( + 'id__in' => $ids, + 'type__not_in' => array( 'primary' ), + 'fields' => 'id' + ) ); + + foreach ( $ids as $id ) { + switch ( $this->current_action() ) { + case 'delete' : + if ( in_array( $id, $non_primary_address_ids ) ) { + edd_delete_customer_email_address( $id ); + } + break; + } + } + } + + /** + * Get all of the items to display, given the current filters + * + * @since 3.0 + * + * @return array $data All the row data + */ + public function get_data() { + $data = array(); + $search = $this->get_search(); + $args = array( 'status' => $this->get_status() ); + + // Account for search stripping the "+" from emails. + if ( strpos( $search, ' ' ) ) { + $original_query = $search; + $search = str_replace( ' ', '+', $search ); + if ( ! is_email( $search ) ) { + $search = $original_query; + } + } + + // Email. + if ( is_email( $search ) ) { + $args['email'] = $search; + + // Address ID. + } elseif ( is_numeric( $search ) ) { + $args['id'] = $search; + + // Customer ID. + } elseif ( strpos( $search, 'c:' ) !== false ) { + $args['customer_id'] = trim( str_replace( 'c:', '', $search ) ); + + // Any... + } else { + $args['search'] = $search; + $args['search_columns'] = array( 'email' ); + } + + // Parse pagination. + $this->args = $this->parse_pagination_args( $args ); + + // Get the data. + $emails = edd_get_customer_email_addresses( $this->args ); + + if ( ! empty( $emails ) ) { + foreach ( $emails as $customer ) { + $data[] = array( + 'id' => $customer->id, + 'email' => $customer->email, + 'customer_id' => $customer->customer_id, + 'status' => $customer->status, + 'type' => $customer->type, + 'date_created' => $customer->date_created, + ); + } + } + + return $data; + } + + /** + * Setup the final data for the table + * + * @since 3.0 + * @return void + */ + public function prepare_items() { + $this->_column_headers = array( + $this->get_columns(), + array(), + $this->get_sortable_columns() + ); + + $this->items = $this->get_data(); + + $status = $this->get_status( 'total' ); + + // Setup pagination + $this->set_pagination_args( array( + 'total_pages' => ceil( $this->counts[ $status ] / $this->per_page ), + 'total_items' => $this->counts[ $status ], + 'per_page' => $this->per_page + ) ); + } + + /** + * Generate the table navigation above or below the table. + * We're overriding this to turn off the referer param in `wp_nonce_field()`. + * + * @param string $which + * @since 3.1.0.4 + */ + protected function display_tablenav( $which ) { + if ( 'top' === $which ) { + wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false ); + } + ?> +
    + + has_items() ) : ?> +
    + bulk_actions( $which ); ?> +
    + extra_tablenav( $which ); + $this->pagination( $which ); + ?> + +
    +
    + 'customer', + 'plural' => 'customers', + 'ajax' => false + ) ); + + $this->process_bulk_action(); + $this->get_counts(); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 1.5 + * + * @param array $item Contains all the data of the customers + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + + $timezone_abbreviation = edd_get_timezone_abbr(); + + switch ( $column_name ) { + + case 'id' : + $value = esc_html( $item['id'] ); + break; + + case 'email' : + $value = '' . esc_html( $item['email'] ) . ''; + break; + + case 'order_count' : + $url = edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'customer' => rawurlencode( $item['id'] ), + ) ); + $value = '' . esc_html( number_format_i18n( $item['order_count'] ) ) . ''; + break; + + case 'spent' : + $value = edd_currency_filter( edd_format_amount( $item[ $column_name ] ) ); + break; + + case 'date_created' : + $value = ''; + break; + + default: + $value = isset( $item[ $column_name ] ) + ? esc_html( $item[ $column_name ] ) + : null; + break; + } + + // Filter & return + return apply_filters( 'edd_customers_column_' . esc_attr( $column_name ), $value, $item['id'] ); + } + + /** + * Return the contents of the "Name" column + * + * @since 3.0 + * + * @param array $item + * @return string + */ + public function column_name( $item ) { + $state = ''; + $status = $this->get_status(); + $name = ! empty( $item['name'] ) ? $item['name'] : '—'; + + $item_status = ! empty( $item['status'] ) + ? $item['status'] + : 'active'; + + // State + if ( ( ! empty( $status ) && ( $status !== $item_status ) ) || ( $item_status !== 'active' ) ) { + switch ( $status ) { + case 'pending' : + $value = __( 'Pending', 'easy-digital-downloads' ); + break; + case 'active' : + case '' : + default : + $value = __( 'Active', 'easy-digital-downloads' ); + break; + } + + $state = ' — ' . $value; + } + + // Get the customer's avatar + $avatar = get_avatar( $item['email'], 32 ); + + // View URL + $view_url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $item['id'] ), + ) + ); + + // Concatenate and return + return $avatar . '' . esc_html( $name ) . '' . esc_html( $state ) . '' . $this->row_actions( $this->get_row_actions( $item ) ); + } + + /** + * Gets the row actions for the customer. + * + * @since 3.0 + * @param array $item + * @return array + */ + private function get_row_actions( $item ) { + $view_url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $item['id'] ), + ) + ); + $logs_url = edd_get_admin_url( + array( + 'page' => 'edd-tools', + 'tab' => 'logs', + 'customer' => urlencode( $item['id'] ), + ) + ); + $delete_url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'delete', + 'id' => urlencode( $item['id'] ), + ) + ); + $actions = array( + 'view' => '' . __( 'Edit', 'easy-digital-downloads' ) . '', + 'logs' => '' . __( 'Logs', 'easy-digital-downloads' ) . '', + 'delete' => '' . __( 'Delete', 'easy-digital-downloads' ) . '', + ); + + /** + * Filter the customer row actions. + * + * @since 3.0 + * @param array $actions The array of row actions. + * @param array $item The specific item (customer). + */ + return apply_filters( 'edd_customer_row_actions', $actions, $item ); + } + + /** + * Render the checkbox column + * + * @since 3.0 + * + * @param array $item Customer data. + * + * @return string Displays a checkbox + */ + public function column_cb( $item ) { + $name = empty( $item['name'] ) ? $item['email'] : $item['name']; + + return sprintf( + '', + /*$1%s*/ 'customer', + /*$2%s*/ esc_attr( $item['id'] ), + /* translators: customer name or email */ + esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $name ) ) + ); + } + + /** + * Retrieve the customer counts + * + * @since 3.0 + * @return void + */ + public function get_counts() { + $this->counts = edd_get_customer_counts(); + } + + /** + * Retrieve the table columns + * + * @since 1.5 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return apply_filters( 'edd_report_customer_columns', array( + 'cb' => '', + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'order_count' => __( 'Orders', 'easy-digital-downloads' ), + 'spent' => __( 'Spent', 'easy-digital-downloads' ), + 'date_created' => __( 'Date', 'easy-digital-downloads' ) + ) ); + } + + /** + * Get the sortable columns + * + * @since 2.1 + * @return array Array of all the sortable columns + */ + public function get_sortable_columns() { + return array( + 'date_created' => array( 'date_created', true ), + 'name' => array( 'name', true ), + 'email' => array( 'email', true ), + 'order_count' => array( 'purchase_count', false ), + 'spent' => array( 'purchase_value', false ) + ); + } + + /** + * Retrieve the bulk actions + * + * @since 3.0 + * @return array Array of the bulk actions + */ + public function get_bulk_actions() { + return array( + 'delete' => __( 'Delete', 'easy-digital-downloads' ) + ); + } + + /** + * Process the bulk actions + * + * @since 3.0 + */ + public function process_bulk_action() { + if ( empty( $_REQUEST['_wpnonce'] ) ) { + return; + } + + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-customers' ) ) { + return; + } + + check_admin_referer( 'bulk-customers' ); + + $ids = isset( $_GET['customer'] ) + ? $_GET['customer'] + : false; + + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + foreach ( $ids as $id ) { + switch ( $this->current_action() ) { + case 'delete' : + edd_delete_customer( $id ); + break; + } + } + } + + /** + * Builds and retrieves all the reports data. + * + * @since 1.5 + * @deprecated 3.0 Use get_data() + * + * @return array All the data for customer reports. + */ + public function reports_data() { + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Customer_Reports_Table::get_data()' ); + + return $this->get_data(); + } + + /** + * Retrieves all of the items to display, given the current filters. + * + * @since 3.0 + * + * @return array $data All the row data. + */ + public function get_data() { + $data = array(); + $search = $this->get_search(); + $args = array( 'status' => $this->get_status() ); + + // Account for search stripping the "+" from emails. + if ( strpos( $search, ' ' ) ) { + $original_query = $search; + $search = str_replace( ' ', '+', $search ); + if ( ! is_email( $search ) ) { + $search = $original_query; + } + } + + // Email search. + if ( is_email( $search ) ) { + $args['email'] = $search; + + // Customer ID. + } elseif ( is_numeric( $search ) ) { + $args['id'] = $search; + } elseif ( strpos( $search, 'c:' ) !== false ) { + $args['id'] = trim( str_replace( 'c:', '', $search ) ); + + // User ID. + } elseif ( strpos( $search, 'user:' ) !== false ) { + $args['user_id'] = trim( str_replace( 'u:', '', $search ) ); + } elseif ( strpos( $search, 'u:' ) !== false ) { + $args['user_id'] = trim( str_replace( 'u:', '', $search ) ); + + // Other... + } else { + $args['search'] = $search; + $args['search_columns'] = array( 'name', 'email' ); + } + + // Parse pagination + $this->args = $this->parse_pagination_args( $args ); + + if ( is_email( $search ) ) { + $customer_emails = new EDD\Database\Queries\Customer_Email_Address(); + $customer_ids = $customer_emails->query( + array( + 'fields' => 'customer_id', + 'email' => $search, + ) + ); + + $customers = edd_get_customers( + array( + 'id__in' => $customer_ids, + ) + ); + } else { + $customers = edd_get_customers( $this->args ); + } + + // Get the data + if ( ! empty( $customers ) ) { + foreach ( $customers as $customer ) { + $data[] = array( + 'id' => $customer->id, + 'user_id' => $customer->user_id, + 'name' => $customer->name, + 'email' => $customer->email, + 'order_count' => $customer->purchase_count, + 'spent' => $customer->purchase_value, + 'date_created' => $customer->date_created, + ); + } + } + + return $data; + } + + /** + * Setup the final data for the table + * + * @since 1.5 + * @return void + */ + public function prepare_items() { + $this->_column_headers = array( + $this->get_columns(), + array(), + $this->get_sortable_columns() + ); + + $this->items = $this->get_data(); + + $status = $this->get_status( 'total' ); + + // Add condition to be sure we don't divide by zero. + // If $this->per_page is 0, then set total pages to 1. + $total_pages = $this->per_page ? ceil( (int) $this->counts[ $status ] / (int) $this->per_page ) : 1; + + // Setup pagination + $this->set_pagination_args( array( + 'total_pages' => $total_pages, + 'total_items' => $this->counts[ $status ], + 'per_page' => $this->per_page, + ) ); + } + + /** + * Generate the table navigation above or below the table. + * We're overriding this to turn off the referer param in `wp_nonce_field()`. + * + * @param string $which + * @since 3.1.0.4 + */ + protected function display_tablenav( $which ) { + if ( 'top' === $which ) { + wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false ); + } + ?> +
    + + has_items() ) : ?> +
    + bulk_actions( $which ); ?> +
    + extra_tablenav( $which ); + $this->pagination( $which ); + ?> + +
    +
    + '', + 'email' => '', + 'date_created' => '', + 'user_id' => 0 + ) ); + + if ( ! is_email( $customer_info['email'] ) ) { + edd_set_error( 'edd-invalid-email', __( 'Please enter a valid email address.', 'easy-digital-downloads' ) ); + } + + if ( (int) $customer_info['user_id'] !== (int) $customer->user_id ) { + + // Make sure we don't already have this user attached to a customer + if ( ! empty( $customer_info['user_id'] ) && false !== edd_get_customer_by( 'user_id', $customer_info['user_id'] ) ) { + /* translators: %d: user ID */ + edd_set_error( 'edd-invalid-customer-user_id', sprintf( __( 'The User ID %d is already associated with a different customer.', 'easy-digital-downloads' ), $customer_info['user_id'] ) ); + } + + // Make sure it's actually a user + $user = get_user_by( 'id', $customer_info['user_id'] ); + if ( ! empty( $customer_info['user_id'] ) && false === $user ) { + /* translators: %d: user ID */ + edd_set_error( 'edd-invalid-user_id', sprintf( __( 'The User ID %d does not exist. Please assign an existing user.', 'easy-digital-downloads' ), $customer_info['user_id'] ) ); + } + } + + // Record this for later + $previous_user_id = $customer->user_id; + + // Bail if errors exist. + if ( edd_get_errors() ) { + return false; + } + + $user_id = absint( $customer_info['user_id'] ); + + if ( empty( $user_id ) && ! empty( $customer_info['user_login'] ) ) { + + // See if they gave an email, otherwise we'll assume login + $user_by_field = is_email( $customer_info['user_login'] ) + ? 'email' + : 'login'; + + $user = get_user_by( $user_by_field, $customer_info['user_login'] ); + + if ( $user ) { + $user_id = $user->ID; + } else { + /* translators: %s: user login or email address */ + edd_set_error( 'edd-invalid-user-string', sprintf( __( 'Failed to attach user. The login or email address %s was not found.', 'easy-digital-downloads' ), $customer_info['user_login'] ) ); + } + } + + // Setup the customer address, if present. + $address = array(); + + $address['address'] = isset( $customer_info['address'] ) + ? $customer_info['address'] + : ''; + + $address['address2'] = isset( $customer_info['address2'] ) + ? $customer_info['address2'] + : ''; + + $address['city'] = isset( $customer_info['city'] ) + ? $customer_info['city'] + : ''; + + $address['country'] = isset( $customer_info['country'] ) + ? $customer_info['country'] + : ''; + + $address['postal_code'] = isset( $customer_info['postal_code'] ) + ? $customer_info['postal_code'] + : ''; + + $address['region'] = isset( $customer_info['region'] ) + ? $customer_info['region'] + : ''; + + // Sanitize the inputs + $customer_data = array(); + $customer_data['name'] = strip_tags( stripslashes( $customer_info['name'] ) ); + $customer_data['email'] = $customer_info['email']; + $customer_data['user_id'] = $user_id; + $customer_data['date_created'] = gmdate( 'Y-m-d H:i:s', strtotime( $customer_info['date_created'] ) ); + + $customer_data = apply_filters( 'edd_edit_customer_info', $customer_data, $customer_id ); + $address = apply_filters( 'edd_edit_customer_address', $address, $customer_id ); + + $customer_data = array_map( 'sanitize_text_field', $customer_data ); + $address = array_map( 'sanitize_text_field', $address ); + + do_action( 'edd_pre_edit_customer', $customer_id, $customer_data, $address ); + + $output = array(); + $previous_email = $customer->email; + + // Add new address before update to skip exists checks + if ( $previous_email !== $customer_data['email'] ) { + $customer->add_email( $customer_data['email'], true ); + } + + // Update customer + if ( $customer->update( $customer_data ) ) { + $current_address = $customer->get_address(); + $address['customer_id'] = $customer->id; + + if ( $current_address ) { + edd_update_customer_address( $current_address->id, $address ); + } else { + $address['is_primary'] = true; + edd_add_customer_address( $address ); + } + + $output['success'] = true; + $customer_data = array_merge( $customer_data, $address ); + $output['customer_info'] = $customer_data; + } else { + $output['success'] = false; + } + + do_action( 'edd_post_edit_customer', $customer_id, $customer_data ); + + if ( edd_doing_ajax() ) { + wp_send_json( $output ); + } + + return $output; +} +add_action( 'edd_edit-customer', 'edd_edit_customer', 10, 1 ); + +/** + * Add an email address to the customer from within the admin and log a customer note + * + * @since 2.6 + * @param array $args Array of arguments: nonce, customer id, and email address + * @return mixed Echos JSON if doing AJAX. Returns array of success (bool) and message (string) if not AJAX. + */ +function edd_add_customer_email( $args = array() ) { + $customer_edit_role = edd_get_edit_customers_role(); + + if ( ! is_admin() || ! current_user_can( $customer_edit_role ) ) { + wp_die( __( 'You do not have permission to edit this customer.', 'easy-digital-downloads' ) ); + } + + $output = array(); + + if ( empty( $args ) || empty( $args['email'] ) || empty( $args['customer_id'] ) ) { + + $output['success'] = false; + + if ( empty( $args['email'] ) ) { + $output['message'] = __( 'Email address is missing.', 'easy-digital-downloads' ); + } else if ( empty( $args['customer_id'] ) ) { + $output['message'] = __( 'Customer ID is required.', 'easy-digital-downloads' ); + } else { + $output['message'] = __( 'An error has occured. Please try again.', 'easy-digital-downloads' ); + } + + } else if ( ! wp_verify_nonce( $args['_wpnonce'], 'edd-add-customer-email' ) ) { + $output = array( + 'success' => false, + 'message' => __( 'Nonce verification failed.', 'easy-digital-downloads' ), + ); + + } else if ( ! is_email( $args['email'] ) ) { + $output = array( + 'success' => false, + 'message' => __( 'Invalid email address.', 'easy-digital-downloads' ), + ); + + } else { + $email = sanitize_email( $args['email'] ); + $customer_id = (int) $args['customer_id']; + $primary = 'true' === $args['primary'] ? true : false; + $customer = new EDD_Customer( $customer_id ); + $customer_email_id = $customer->add_email( $email, $primary ); + + if ( false === $customer_email_id ) { + + if ( in_array( $email, $customer->emails, true ) ) { + $output = array( + 'success' => false, + 'message' => __( 'Email already associated with this customer.', 'easy-digital-downloads' ), + ); + + } else { + $output = array( + 'success' => false, + 'message' => __( 'Email address is already associated with another customer.', 'easy-digital-downloads' ), + ); + } + + } else { + $redirect = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => absint( $customer_id ), + 'edd-message' => 'email-added', + 'edd-email-id' => absint( $customer_email_id ), + ) + ); + $output = array( + 'success' => true, + 'message' => __( 'Email successfully added to customer.', 'easy-digital-downloads' ), + 'redirect' => $redirect . '#edd_general_emails', + ); + + $user = wp_get_current_user(); + $user_login = ! empty( $user->user_login ) ? $user->user_login : edd_get_bot_name(); + /* translators: 1. email address; 2. username */ + $customer_note = sprintf( __( 'Email address %1$s added by %2$s', 'easy-digital-downloads' ), $email, $user_login ); + $customer->add_note( $customer_note ); + + if ( $primary ) { + /* translators: 1. email address; 2. username */ + $customer_note = sprintf( __( 'Email address %1$s set as primary by %2$s', 'easy-digital-downloads' ), $email, $user_login ); + $customer->add_note( $customer_note ); + } + } + } + + if ( ! isset( $customer_id ) ) { + $customer_id = isset( $args['customer_id'] ) ? $args['customer_id'] : false; + } + + do_action( 'edd_post_add_customer_email', $customer_id, $args ); + + if ( edd_doing_ajax() ) { + wp_send_json( $output ); + } + + return $output; +} +add_action( 'edd_customer-add-email', 'edd_add_customer_email', 10, 1 ); + +/** + * Remove an email address to the customer from within the admin and log a customer note + * and redirect back to the customer interface for feedback + * + * @since 2.6 + * @return void + */ +function edd_remove_customer_email() { + if ( empty( $_GET['id'] ) || ! is_numeric( $_GET['id'] ) ) { + return false; + } + + if ( empty( $_GET['email'] ) || ! is_email( $_GET['email'] ) ) { + return false; + } + + if ( empty( $_GET['_wpnonce'] ) ) { + return false; + } + + $nonce = $_GET['_wpnonce']; + if ( ! wp_verify_nonce( $nonce, 'edd-remove-customer-email' ) ) { + wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $customer = new EDD_Customer( $_GET['id'] ); + if ( $customer->remove_email( $_GET['email'] ) ) { + $url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $customer->id ), + 'edd-message' => 'email-removed', + ) + ); + $user = wp_get_current_user(); + $user_login = ! empty( $user->user_login ) ? $user->user_login : edd_get_bot_name(); + /* translators: 1. email address; 2. username */ + $customer_note = sprintf( __( 'Email address %1$s removed by %2$s', 'easy-digital-downloads' ), sanitize_email( $_GET['email'] ), $user_login ); + $customer->add_note( $customer_note ); + + } else { + $url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $customer->id ), + 'edd-message' => 'email-remove-failed', + ) + ); + } + + edd_redirect( $url . '#edd_general_emails' ); +} +add_action( 'edd_customer-remove-email', 'edd_remove_customer_email', 10 ); + +/** + * Set an email address as the primary for a customer from within the admin and log a customer note + * and redirect back to the customer interface for feedback + * + * @since 2.6 + * @return void + */ +function edd_set_customer_primary_email() { + if ( empty( $_GET['id'] ) || ! is_numeric( $_GET['id'] ) ) { + return false; + } + + if ( empty( $_GET['email'] ) || ! is_email( $_GET['email'] ) ) { + return false; + } + + if ( empty( $_GET['_wpnonce'] ) ) { + return false; + } + + $nonce = $_GET['_wpnonce']; + if ( ! wp_verify_nonce( $nonce, 'edd-set-customer-primary-email' ) ) { + wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $customer = new EDD_Customer( $_GET['id'] ); + if ( $customer->set_primary_email( $_GET['email'] ) ) { + $url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $customer->id ), + 'edd-message' => 'primary-email-updated', + ) + ); + $user = wp_get_current_user(); + $user_login = ! empty( $user->user_login ) ? $user->user_login : edd_get_bot_name(); + $customer_note = sprintf( __( 'Email address %s set as primary by %s', 'easy-digital-downloads' ), sanitize_email( $_GET['email'] ), $user_login ); + $customer->add_note( $customer_note ); + + } else { + $url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $customer->id ), + 'edd-message' => 'primary-email-failed', + ) + ); + } + + edd_redirect( $url . '#edd_general_emails' ); +} +add_action( 'edd_customer-primary-email', 'edd_set_customer_primary_email', 10 ); + +/** + * Delete a customer + * + * @since 2.3 + * @param array $args The $_POST array being passed + * @return int Whether it was a successful deletion + */ +function edd_customer_delete( $args = array() ) { + $customer_edit_role = edd_get_edit_customers_role(); + + if ( ! is_admin() || ! current_user_can( $customer_edit_role ) ) { + wp_die( __( 'You do not have permission to delete this customer.', 'easy-digital-downloads' ) ); + } + + if ( empty( $args ) ) { + return; + } + + $customer_id = (int)$args['customer_id']; + $confirm = ! empty( $args['edd-customer-delete-confirm'] ); + $remove_data = ! empty( $args['edd-customer-delete-records'] ); + $nonce = $args['_wpnonce']; + + if ( ! wp_verify_nonce( $nonce, 'delete-customer' ) ) { + wp_die( __( 'Cheatin\' eh?!', 'easy-digital-downloads' ) ); + } + + if ( ! $confirm ) { + edd_set_error( 'customer-delete-no-confirm', __( 'Please confirm you want to delete this customer', 'easy-digital-downloads' ) ); + } + + if ( edd_get_errors() ) { + edd_redirect( + edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => absint( $customer_id ), + ) + ) + ); + } + + $customer = new EDD_Customer( $customer_id ); + + do_action( 'edd_pre_delete_customer', $customer_id, $confirm, $remove_data ); + + $success = false; + + if ( $customer->id > 0 ) { + + $payments_array = explode( ',', $customer->payment_ids ); + $success = edd_destroy_customer( $customer->id ); + + if ( $success ) { + + if ( $remove_data ) { + + // Remove all payments, logs, etc + foreach ( $payments_array as $payment_id ) { + edd_destroy_order( $payment_id ); + } + + } else { + + // Just set the payments to customer_id of 0 + foreach ( $payments_array as $payment_id ) { + edd_update_payment_meta( $payment_id, '_edd_payment_customer_id', 0 ); + } + } + + $redirect = edd_get_admin_url( array( 'page' => 'edd-customers', 'edd-message' => 'customer-deleted' ) ); + + } else { + edd_set_error( 'edd-customer-delete-failed', __( 'Error deleting customer', 'easy-digital-downloads' ) ); + $redirect = edd_get_admin_url( array( 'page' => 'edd-customers', 'view' => 'delete', 'id' => absint( $customer_id ) ) ); + } + + } else { + edd_set_error( 'edd-customer-delete-invalid-id', __( 'Invalid Customer ID', 'easy-digital-downloads' ) ); + $redirect = edd_get_admin_url( array( 'page' => 'edd-customers' ) ); + } + + edd_redirect( $redirect ); +} +add_action( 'edd_delete-customer', 'edd_customer_delete', 10, 1 ); + +/** + * Disconnect a user ID from a customer + * + * @since 2.3 + * @param array $args Array of arguments + * @return bool If the disconnect was successful + */ +function edd_disconnect_customer_user_id( $args = array() ) { + $customer_edit_role = edd_get_edit_customers_role(); + + if ( ! is_admin() || ! current_user_can( $customer_edit_role ) ) { + wp_die( __( 'You do not have permission to edit this customer.', 'easy-digital-downloads' ) ); + } + + if ( empty( $args ) ) { + return; + } + + $customer_id = (int)$args['customer_id']; + $nonce = $args['_wpnonce']; + + if ( ! wp_verify_nonce( $nonce, 'edit-customer' ) ) { + wp_die( __( 'Cheatin\' eh?!', 'easy-digital-downloads' ) ); + } + + $customer = new EDD_Customer( $customer_id ); + if ( empty( $customer->id ) ) { + return false; + } + + do_action( 'edd_pre_customer_disconnect_user_id', $customer_id, $customer->user_id ); + + $customer_args = array( 'user_id' => 0 ); + + if ( $customer->update( $customer_args ) ) { + + $output['success'] = true; + + } else { + + $output['success'] = false; + edd_set_error( 'edd-disconnect-user-fail', __( 'Failed to disconnect user from customer', 'easy-digital-downloads' ) ); + } + + do_action( 'edd_post_customer_disconnect_user_id', $customer_id ); + + if ( edd_doing_ajax() ) { + wp_send_json( $output ); + } + + return $output; +} +add_action( 'edd_disconnect-userid', 'edd_disconnect_customer_user_id', 10, 1 ); + +/** + * Process manual verification of customer account by admin + * + * @since 2.4.8 + * @return void + */ +function edd_process_admin_user_verification() { + + if ( empty( $_GET['id'] ) || ! is_numeric( $_GET['id'] ) ) { + return false; + } + + if ( empty( $_GET['_wpnonce'] ) ) { + return false; + } + + $nonce = $_GET['_wpnonce']; + if ( ! wp_verify_nonce( $nonce, 'edd-verify-user' ) ) { + wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $customer = new EDD_Customer( $_GET['id'] ); + edd_set_user_to_verified( $customer->user_id ); + + $url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => absint( $customer->id ), + 'edd-message' => 'user-verified', + ) + ); + + edd_redirect( $url ); +} +add_action( 'edd_verify_user_admin', 'edd_process_admin_user_verification' ); + +/** + * Register the reset single customer stats batch processor + * @since 2.5 + */ +function edd_register_batch_single_customer_recount_tool() { + add_action( 'edd_batch_export_class_include', 'edd_include_single_customer_recount_tool_batch_processer', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_batch_single_customer_recount_tool', 10 ); + +/** + * Loads the tools batch processing class for recounting stats for a single customer + * + * @since 2.5 + * @param string $class The class being requested to run for the batch export + * @return void + */ +function edd_include_single_customer_recount_tool_batch_processer( $class ) { + if ( 'EDD_Tools_Recount_Single_Customer_Stats' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/tools/class-edd-tools-recount-single-customer-stats.php'; + } +} + +/** + * Removes a customer address + * + * @since 3.0 + * @return void + */ +function edd_remove_customer_address() { + if ( ! is_admin() || ! current_user_can( edd_get_edit_customers_role() ) ) { + wp_die( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ); + } + + if ( empty( $_GET['id'] ) || ! is_numeric( $_GET['id'] ) || empty( $_GET['_wpnonce'] ) ) { + return; + } + + if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'edd-remove-customer-address' ) ) { + wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $address = edd_fetch_customer_address( absint( $_GET['id'] ) ); + $removed = $address instanceof EDD\Customers\Customer_Address ? edd_delete_customer_address( absint( $_GET['id'] ) ) : false; + + $url = edd_get_admin_url( array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $address->customer_id ), + 'edd-message' => 'address-removed' + ) ); + + if ( $removed ) { + $url = add_query_arg( 'edd-message', 'address-removed', $url ); + } else { + $url = add_query_arg( 'edd-message', 'address-remove-failed', $url ); + } + + edd_redirect( $url . '#edd_general_addresses' ); +} +add_action( 'edd_customer-remove-address', 'edd_remove_customer_address', 10 ); diff --git a/includes/admin/customers/customer-functions.php b/includes/admin/customers/customer-functions.php new file mode 100644 index 00000000000..883e13c86f2 --- /dev/null +++ b/includes/admin/customers/customer-functions.php @@ -0,0 +1,186 @@ + 'edd_customers_view', + 'emails' => 'edd_customers_emails_view', + 'addresses' => 'edd_customers_addresses_view', + 'delete' => 'edd_customers_delete_view', + 'notes' => 'edd_customer_notes_view', + 'tools' => 'edd_customer_tools_view', + ) ); +} +add_filter( 'edd_customer_views', 'edd_register_default_customer_views', 1, 1 ); + +/** + * Register a tab for the single customer view + * + * @since 2.3 + * @param array $tabs An array of existing tabs + * @return array The altered list of tabs + */ +function edd_register_default_customer_tabs( $tabs ) { + return array_merge( $tabs, array( + 'overview' => array( 'dashicon' => 'dashicons-admin-users', 'title' => _x( 'Profile', 'Customer Details tab title', 'easy-digital-downloads' ) ), + 'emails' => array( 'dashicon' => 'dashicons-email', 'title' => _x( 'Emails', 'Customer Emails tab title', 'easy-digital-downloads' ) ), + 'addresses' => array( 'dashicon' => 'dashicons-admin-home', 'title' => _x( 'Addresses', 'Customer Addresses tab title', 'easy-digital-downloads' ) ), + 'notes' => array( 'dashicon' => 'dashicons-admin-comments', 'title' => _x( 'Notes', 'Customer Notes tab title', 'easy-digital-downloads' ) ), + 'tools' => array( 'dashicon' => 'dashicons-admin-tools', 'title' => _x( 'Tools', 'Customer Tools tab title', 'easy-digital-downloads' ) ) + ) ); +} +add_filter( 'edd_customer_tabs', 'edd_register_default_customer_tabs', 1, 1 ); + +/** + * Register the Delete icon as late as possible so it's at the bottom + * + * @since 2.3.1 + * @param array $tabs An array of existing tabs + * @return array The altered list of tabs, with 'delete' at the bottom + */ +function edd_register_delete_customer_tab( $tabs ) { + + $tabs['delete'] = array( + 'dashicon' => 'dashicons-trash', + 'title' => _x( 'Delete', 'Delete Customer tab title', 'easy-digital-downloads' ) + ); + + return $tabs; +} +add_filter( 'edd_customer_tabs', 'edd_register_delete_customer_tab', PHP_INT_MAX, 1 ); + +/** + * Remove the admin bar edit profile link when the user is not verified + * + * @since 2.4.4 + * @return void + */ +function edd_maybe_remove_adminbar_profile_link() { + + if ( current_user_can( 'manage_shop_settings' ) ) { + return; + } + + if ( edd_user_pending_verification() ) { + global $wp_admin_bar; + $wp_admin_bar->remove_menu( 'edit-profile', 'user-actions' ); + } +} +add_action( 'wp_before_admin_bar_render', 'edd_maybe_remove_adminbar_profile_link' ); + +/** + * Remove the admin menus and disable profile access for non-verified users + * + * @since 2.4.4 + * @return void + */ +function edd_maybe_remove_menu_profile_links() { + + if ( edd_doing_ajax() ) { + return; + } + + if ( current_user_can( 'manage_shop_settings' ) ) { + return; + } + + if ( edd_user_pending_verification() ) { + + if ( defined( 'IS_PROFILE_PAGE' ) && true === IS_PROFILE_PAGE ) { + $url = esc_url( edd_get_user_verification_request_url() ); + /* translators: link to send an email */ + $message = sprintf( __( 'Your account is pending verification. Please click the link in your email to activate your account. No email? Click here to send a new activation code.', 'easy-digital-downloads' ), esc_url( $url ) ); + $title = __( 'Account Pending Verification', 'easy-digital-downloads' ); + $args = array( + 'response' => 403, + ); + wp_die( $message, $title, $args ); + } + + remove_menu_page( 'profile.php' ); + remove_submenu_page( 'users.php', 'profile.php' ); + } +} +add_action( 'admin_init', 'edd_maybe_remove_menu_profile_links' ); + +/** + * Add Customer column to Users list table. + * + * @since 3.0 + * + * @param array $columns Existing columns. + * + * @return array $columns Columns with `Customer` added. + */ +function edd_add_customer_column_to_users_table( $columns ) { + $columns['edd_customer'] = __( 'Customer', 'easy-digital-downloads' ); + return $columns; +} +add_filter( 'manage_users_columns', 'edd_add_customer_column_to_users_table' ); + +/** + * Display customer details on Users list table. + * + * @since 3.0 + * + * @param string $value Existing value of the custom column. + * @param string $column_name Column name. + * @param int $user_id User ID. + * + * @return string URL to Customer page, existing value otherwise. + */ +function edd_render_customer_column( $value, $column_name, $user_id ) { + if ( 'edd_customer' === $column_name ) { + $customer = new EDD_Customer( $user_id, true ); + + if ( $customer->id > 0 ) { + $name = '#' . $customer->id . ' '; + $name .= ! empty( $customer->name ) ? $customer->name : '' . __( 'Unnamed Customer', 'easy-digital-downloads' ) . ''; + $view_url = edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => absint( $customer->id ), + ) + ); + + return '' . $name . ''; + } + } + + return $value; +} +add_action( 'manage_users_custom_column', 'edd_render_customer_column', 10, 3 ); + +/** + * Renders the customer details header (gravatar/name). + * + * @since 3.0 + * @param \EDD_Customer $customer + * @return void + */ +function edd_render_customer_details_header( \EDD_Customer $customer ) { + ?> +
    + email, 30 ); ?> name ); ?> +
    + + + + + esc_html__( 'Customers', 'easy-digital-downloads' ), + 'emails' => esc_html__( 'Email Addresses', 'easy-digital-downloads' ), + 'physical' => esc_html__( 'Physical Addresses', 'easy-digital-downloads' ) + ) ); + } + + // Return + return $pages; +} + +/** + * Display customer sections + * + * Contains backwards compat code to shim tabs & views to EDD_Sections() + * + * @since 3.0 + * + * @param object $customer + */ +function edd_customers_sections( $customer ) { + + // Instantiate the Sections class and sections array + $sections = new EDD\Admin\Sections(); + $c_sections = array(); + + // Setup sections variables + $sections->item = $customer; + $sections->use_js = true; + $sections->current_section = ! empty( $_GET['view'] ) + ? sanitize_key( $_GET['view'] ) + : 'overview'; + $sections->base_url = edd_get_admin_url( array( + 'page' => 'edd-customers', + 'id' => absint( $customer->id ), + ) ); + + // Get all registered tabs & views + $tabs = edd_customer_tabs(); + $views = edd_customer_views(); + + // Do not display the addresses tab if there are none. + if ( empty( $customer->get_addresses() ) ) { + unset( $tabs['addresses'] ); + } + + // Loop through tabs & setup sections + if ( ! empty( $tabs ) ) { + foreach ( $tabs as $id => $tab ) { + + // Bail if no view + if ( ! isset( $views[ $id ] ) ) { + continue; + } + + // Add to sections array + $c_sections[] = array( + 'id' => $id, + 'label' => $tab['title'], + 'icon' => str_replace( 'dashicons-', '', $tab['dashicon'] ), + 'callback' => $views[ $id ] + ); + } + } + + // Set the customer sections + $sections->set_sections( $c_sections ); + + // Display the sections + $sections->display(); +} + +/** + * Customers Page + * + * Renders the customers page contents. + * + * @since 2.3 + * @return void + */ +function edd_customers_page() { + // Enqueue scripts. + wp_enqueue_script( 'edd-admin-customers' ); + wp_enqueue_script( 'edd-admin-notes' ); + + // Views + $default_views = edd_customer_views(); + $requested_view = isset( $_GET['view'] ) + ? sanitize_key( $_GET['view'] ) + : 'customers'; + + // Tabs + $active_tab = ! empty( $_GET['page_type'] ) + ? sanitize_key( $_GET['page_type'] ) + : 'customers'; + + // Single customer view + if ( array_key_exists( $requested_view, $default_views ) && is_callable( $default_views[ $requested_view ] ) ) { + edd_render_customer_view( $requested_view, $default_views ); + + // List table view + } else { + edd_customers_list( $active_tab ); + } +} + +/** + * Register the views for customer management + * + * @since 2.3 + * @return array Array of views and their callbacks + */ +function edd_customer_views() { + return apply_filters( 'edd_customer_views', array() ); +} + +/** + * Register the tabs for customer management + * + * @since 2.3 + * @return array Array of tabs for the customer + */ +function edd_customer_tabs() { + return apply_filters( 'edd_customer_tabs', array() ); +} + +/** + * List table of customers + * + * @since 2.3 + * @return void + */ +function edd_customers_list( $active_tab = 'customers' ) { + + // Get the possible pages + $pages = edd_get_customer_pages(); + + // Reset page if not a registered page + if ( ! in_array( $active_tab, array_keys( $pages ), true ) ) { + $active_tab = 'customers'; + } + + // Get the label/name from the active tab + $name = $pages[ $active_tab ]; + + // Get the action url from the active tab + $action_url = edd_get_admin_url( array( + 'page_type' => sanitize_key( $active_tab ), + 'page' => 'edd-' . sanitize_key( $active_tab ), + ) ); + + // Setup the list table class + switch ( $active_tab ) { + case 'customers' : + include_once dirname( __FILE__ ) . '/class-customer-table.php'; + $list_table_class = 'EDD_Customer_Reports_Table'; + break; + case 'emails' : + include_once dirname( __FILE__ ) . '/class-customer-email-addresses-table.php'; + $list_table_class = 'EDD_Customer_Email_Addresses_Table'; + break; + case 'physical' : + include_once dirname( __FILE__ ) . '/class-customer-addresses-table.php'; + $list_table_class = 'EDD_Customer_Addresses_Table'; + break; + } + + // Initialize the list table + $customers_table = new $list_table_class; + $customers_table->prepare_items(); ?> + +
    +

    +
    + + +
    + + + + + views(); + /* translators: the active screen, eg "Search Customers" or "Search Customer Email Addresses" */ + $customers_table->search_box( sprintf( __( 'Search %s', 'easy-digital-downloads' ), $name ), 'edd-customers' ); + $customers_table->display(); + ?> + + + + + + + + +
    + + id ) ) { + edd_set_error( 'edd-invalid_customer', __( 'Invalid Customer ID Provided.', 'easy-digital-downloads' ) ); + $render = false; + } ?> + +
    +

    + + +

    + +
    + + + +
    + + + +
    + +
    + + + +
    + get_meta( 'agree_to_terms_time', false ); + $show_terms = edd_get_option( 'show_agree_to_terms' ); + $privacy_timestamps = $customer->get_meta( 'agree_to_privacy_time', false ); + $show_privacy = edd_get_option( 'show_agree_to_privacy_policy' ); + $last_payment_date = ''; + + if ( ( empty( $agreement_timestamps ) && $show_terms ) || ( empty( $privacy_timestamps ) && $show_privacy ) ) { + $last_payment = edd_get_orders( + array( + 'customer_id' => $customer->id, + 'orderby' => 'date', + 'order' => 'DESC', + 'number' => 1, + ) + ); + if ( ! empty( $last_payment ) ) { + $last_payment = reset( $last_payment ); + $last_payment_date = strtotime( $last_payment->date_created ); + } + } + + if ( is_array( $agreement_timestamps ) ) { + $agreement_timestamp = array_pop( $agreement_timestamps ); + } + + if ( is_array( $privacy_timestamps ) ) { + $privacy_timestamp = array_pop( $privacy_timestamps ); + } + + $user_id = ( $customer->user_id > 0 ) + ? absint( $customer->user_id ) + : ''; + + $address_args = array( + 'address' => '', + 'address2' => '', + 'city' => '', + 'region' => '', + 'postal_code' => '', + 'country' => '', + ); + + $data_atts = array( + 'key' => 'user_login', + 'exclude' => $user_id + ); + + $user_args = array( + 'name' => 'customerinfo[user_login]', + 'class' => 'edd-user-dropdown', + 'data' => $data_atts + ); + + // Maybe get user data + if ( ! empty( $user_id ) ) { + $userdata = get_userdata( $user_id ); + + if ( ! empty( $userdata->user_login ) ) { + $user_login = $userdata->user_login; + $user_args['value'] = $user_login; + } else { + $user_login = false; + } + } + + // Address + $address = $customer->get_address(); + + if ( ! empty( $address ) ) { + $address = $address->to_array(); + $address = wp_parse_args( $address, $address_args ); + + } else { + $address = $address_args; + } + + do_action( 'edd_customer_card_top', $customer ); + + // Country + $selected_country = $address['country']; + $countries = edd_get_country_list(); + + // State + $selected_state = edd_get_shop_state(); + $states = edd_get_shop_states( $selected_country ); + $selected_state = isset( $address['region'] ) + ? $address['region'] + : $selected_state; + + // Orders + // Orders and refunds. + $orders = edd_get_orders( array( + 'customer_id' => $customer->id, + 'number' => 10, + 'type' => 'sale', + ) ); + + $refunds = edd_get_orders( array( + 'customer_id' => $customer->id, + 'number' => 10, + 'type' => 'refund', + ) ); + + // Downloads + $downloads = edd_get_users_purchased_products( $customer->email ); + + ?> + +
    +
    + + + + +
    +
    + email, 150 ); ?>
    + + + + + + + + + + + +
    + +
    + #id ); ?> +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + + + + + name ); ?> + + + + + + + email ); ?> + + + + + + date_created ) ) + ); + ?> + + + html->ajax_user_search( $user_args ); ?> + + + + + + + ' . esc_html( $user_id ) . ''); + endif; ?> + + + + + + + + + + + + 0 ) : ?> + + + + + + + +
    +
    +
    +
    +
    + + + +
    + +
    + + + +
    +

    +

    + + + + + + + + + +

    + +

    + + + + + + + + + +

    +
    + + + +
    + + + +

    + + + + + + + + + + + status ) { + $state = ' — ' . edd_get_payment_status_label( $order->status ); + } + + // View URL + $view_url = edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + 'id' => absint( $order->id ), + ) ); + ?> + + + + + + + + + + + +
    get_number() ); ?>gateway ) ); ?>total ), $order->currency ); ?>
    + +

    + + + + + + + + + + + 'edd-payment-history', + 'view' => 'view-refund-details', + 'id' => absint( $refund->id ), + ) ); + ?> + + + + + + + + + + + +
    order_number ); ?>gateway ) ); ?>total ), $refund->currency ); ?>
    + +

    + +

    + + + + + + + + + + + + + + + + + + + + + + + +
    post_title ); ?>
    + +
    + + + +
    + + + + $customer->id, + 'orderby' => 'type', // to put `primary` email first + 'order' => 'ASC' + ) ); + ?> +
    + +

    + + +

    + +
    + + + + + + + + + + $email ) : ?> + + + + + + + + + + + + + + + + +
    + email ); ?> + + type ) : ?> + + +
    + 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $customer->id ), + ) + ); + $actions = array( + 'promote' => array( + 'url' => wp_nonce_url( add_query_arg( array( 'email' => rawurlencode( $email->email ), 'edd_action' => 'customer-primary-email' ), $base_url ), 'edd-set-customer-primary-email' ), + 'label' => __( 'Make Primary', 'easy-digital-downloads' ), + ), + 'delete' => array( + 'url' => wp_nonce_url( add_query_arg( array( 'email' => rawurlencode( $email->email ), 'edd_action' => 'customer-remove-email' ), $base_url ), 'edd-remove-customer-email' ), + 'label' => __( 'Delete', 'easy-digital-downloads' ), + ), + ); + $action_links = array(); + foreach ( $actions as $action => $args ) { + $action_links[] = sprintf( + '%s', + esc_attr( $action ), + esc_url( $args['url'] ), + esc_html( $args['label'] ) + ); + } + echo wp_kses( implode( ' | ', $action_links ), edd_get_allowed_tags() ); + ?> +
    + +
    + +
    +
    + + +
    + +
    + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + get_addresses(); + // This has already been checked when setting the tabs. + if ( empty( $addresses ) ) { + return; + } + ?> +
    + +

    + +
    + + + + + + + + + + + + + + 'edd-customers', + 'view' => 'overview', + 'id' => urlencode( $address->id ), + 'edd_action' => 'customer-remove-address', + ) ), 'edd-remove-customer-address' ); + ?> + + + + + + + + + + + + +
    + address ) + ? esc_html( $address->address ) + : '—'; + + echo ! empty( $address->address2 ) + ? esc_html( $address->address2 ) + : ''; + ?> + + city ) + ? esc_html( $address->city ) + : '—'; + ?> + + region ) + ? esc_html( edd_get_state_name( $address->country, $address->region ) ) + : '—'; + ?> + + postal_code ) + ? esc_html( $address->postal_code ) + : '—'; + ?> + + country ) + ? esc_html( edd_get_country_name( $address->country ) ) + : '—'; + ?> + + + is_primary ) ) : ?> + + +
    + +
    +
    +
    + get_notes( $per_page, $paged ); + $note_count = $customer->get_notes_count(); + $args = array( + 'total' => $note_count, + 'add_fragment' => '#edd_general_notes' + ); ?> + +
    + +

    + + + +
    + + id, 'customer' ); ?> +
    + + +
    + + + +
    + +
    + + +

    + +
    + +

    + html->checkbox( array( 'name' => 'edd-customer-delete-confirm' ) ); ?> + +

    + +

    + html->checkbox( array( 'name' => 'edd-customer-delete-records', 'options' => array( 'disabled' => true ) ) ); ?> + +

    + + +
    + + + + + + + +
    +
    +
    + + + +
    + +

    + +
    +

    +

    +
    + + + + + + + +
    +
    +
    + + user_id ) ) { + return; + } + + $url = wp_nonce_url( + edd_get_admin_url( + array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'edd_action' => 'verify_user_admin', + 'id' => absint( $customer->id ), + ) + ), + 'edd-verify-user' + ); + + echo '

    '; + esc_html_e( 'This customer\'s user account is pending verification.', 'easy-digital-downloads' ); + echo ' '; + echo '' . esc_html__( 'Verify account.', 'easy-digital-downloads' ) . ''; + echo "\n\n"; + + echo '

    '; +} +add_action( 'edd_customer_card_top', 'edd_verify_customer_notice', 10, 1 ); diff --git a/includes/admin/dashboard-widgets.php b/includes/admin/dashboard-widgets.php new file mode 100755 index 00000000000..744b980dc06 --- /dev/null +++ b/includes/admin/dashboard-widgets.php @@ -0,0 +1,315 @@ +%1$s %2$s

    ', + esc_html__( 'Easy Digital Downloads is performing a database migration via WP-CLI.', 'easy-digital-downloads' ), + esc_html__( 'This summary will be available when that has completed.', 'easy-digital-downloads' ) + ); + return; + } + global $wpdb; + $orders = $wpdb->get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'edd_payment' LIMIT 1" ); + if ( ! empty( $orders ) ) { + $url = add_query_arg( + array( + 'page' => 'edd-upgrades', + 'edd-upgrade' => 'v30_migration', + ), + admin_url( 'index.php' ) + ); + printf( + '

    %1$s %2$s%4$s

    ', + esc_html__( 'Easy Digital Downloads needs to upgrade the database.', 'easy-digital-downloads' ), + esc_html__( 'This summary will be available when that has completed.', 'easy-digital-downloads' ), + esc_url( $url ), + esc_html__( 'Begin the upgrade.', 'easy-digital-downloads' ) + ); + return; + } + } + wp_enqueue_script( 'edd-admin-dashboard' ); + + /** + * Action hook to add content to the dashboard widget. + * This content will not be replaced by the AJAX function: + * only the "edd-loading" content will. + * + * @since 2.11.4 + */ + do_action( 'edd_dashboard_sales_widget' ); + ?> +

    + $range, + 'output' => 'formatted', + 'revenue_type' => 'net', + ); + if ( 'total' === $range ) { + unset( $args['range'] ); + } + // Remove filters so that deprecation notices are not unnecessarily logged outside of reports. + remove_all_filters( 'edd_report_views' ); + $stats = new EDD\Stats( $args ); + $data[ $range ] = array( + 'earnings' => $stats->get_order_earnings(), + 'count' => $stats->get_order_count(), + ); + } + + return $data; +} + +/** + * Loads the dashboard sales widget via ajax + * + * @since 2.1 + * @return void + */ +function edd_load_dashboard_sales_widget( ) { + + if ( ! current_user_can( apply_filters( 'edd_dashboard_stats_cap', 'view_shop_reports' ) ) ) { + die(); + } + + $stats = new EDD_Payment_Stats(); + $data = edd_get_dashboard_sales_widget_data(); ?> +
    +
    + + + + + + + + + + + + + + + +
    —
    + + + + + + + + + + + + + + + + +
    —
    +
    +
    + + + + + + + + + + + + + + + + +
    + — +
    + +
    +
    +
    + + + + + + + + + + + + + + + + +
    + — +
    +
    +
    + + 5, + 'status' => edd_get_net_order_statuses(), + 'type' => 'sale', + ) + ); + + if ( $orders ) { ?> + + + + 5, 'status' => 'complete' ) ) ); + } + ?> +
    + publish ) { + $text = _n( '%s ' . edd_get_label_singular(), '%s ' . edd_get_label_plural(), $num_posts->publish, 'easy-digital-downloads' ); + + $text = sprintf( $text, number_format_i18n( $num_posts->publish ) ); + + if ( current_user_can( 'edit_products' ) ) { + $text = sprintf( '%1$s', $text ); + } else { + $text = sprintf( '%1$s', $text ); + } + + $items[] = $text; + } + + return $items; +} +add_filter( 'dashboard_glance_items', 'edd_dashboard_at_a_glance_widget', 1 ); diff --git a/includes/admin/discounts/add-discount.php b/includes/admin/discounts/add-discount.php index b6529de38e9..0c1673e4e2f 100755 --- a/includes/admin/discounts/add-discount.php +++ b/includes/admin/discounts/add-discount.php @@ -1,98 +1,236 @@ -

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    -

    - - - -

    -
    \ No newline at end of file +
    +

    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +

    +
    + + + +

    +
    + + + + + + + +

    +
    + + + html->product_dropdown( array( + 'name' => 'product_reqs[]', + 'id' => 'edd_products', + 'selected' => array(), + 'multiple' => true, + 'chosen' => true, + 'placeholder' => sprintf( esc_html__( 'Select %s', 'easy-digital-downloads' ), esc_html( edd_get_label_plural() ) ), + ) ); // WPCS: XSS ok. ?> + +

    +
    + + + html->product_dropdown( array( + 'name' => 'excluded_products[]', + 'id' => 'excluded_products', + 'selected' => array(), + 'multiple' => true, + 'chosen' => true, + 'placeholder' => sprintf( esc_html__( 'Select %s', 'easy-digital-downloads' ), esc_html( edd_get_label_plural() ) ), + ) ); // WPCS: XSS ok. ?> +

    +
    + + + + + + + : + + + + + +

    +
    + + + + + + + : + + + + + +

    +
    + + + +

    +
    + + + +

    +
    + + + + +
    + + + +

    + + + + + +

    +
    +
    diff --git a/includes/admin/discounts/class-discount-codes-table.php b/includes/admin/discounts/class-discount-codes-table.php new file mode 100755 index 00000000000..73751d8cebd --- /dev/null +++ b/includes/admin/discounts/class-discount-codes-table.php @@ -0,0 +1,459 @@ + 'discount', + 'plural' => 'discounts', + 'ajax' => false, + ) ); + + $this->process_bulk_action(); + $this->get_counts(); + } + + /** + * Get the base URL for the discount list table + * + * @since 3.0 + * + * @return string + */ + public function get_base_url() { + + // Remove some query arguments + $base = remove_query_arg( edd_admin_removable_query_args(), edd_get_admin_base_url() ); + + // Add base query args + return add_query_arg( array( + 'page' => 'edd-discounts', + ), $base ); + } + + /** + * Retrieve the table columns + * + * @since 1.4 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return apply_filters( 'edd_discounts_table_columns', array( + 'cb' => '', + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'code' => __( 'Code', 'easy-digital-downloads' ), + 'amount' => __( 'Amount', 'easy-digital-downloads' ), + 'use_count' => __( 'Uses', 'easy-digital-downloads' ), + 'start_date' => __( 'Start Date', 'easy-digital-downloads' ), + 'end_date' => __( 'End Date', 'easy-digital-downloads' ) + ) ); + } + + /** + * Retrieve the sortable columns + * + * @since 1.4 + * + * @return array Array of all the sortable columns + */ + public function get_sortable_columns() { + return apply_filters( 'edd_discounts_table_sortable_columns', array( + 'name' => array( 'name', false ), + 'code' => array( 'code', false ), + 'use_count' => array( 'use_count', false ), + 'start_date' => array( 'start_date', false ), + 'end_date' => array( 'end_date', false ) + ) ); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 1.4 + * + * @param EDD_Discount $discount Discount object. + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $discount, $column_name ) { + $value = property_exists( $discount, $column_name ) ? $discount->$column_name : ''; + + return apply_filters( 'edd_discounts_table_column', $value, $discount, $column_name ); + } + + /** + * This function renders the amount column. + * + * @since 3.0 + * + * @param EDD_Discount $discount Data for the discount code. + * @return string Formatted amount. + */ + public function column_amount( $discount ) { + return edd_format_discount_rate( $discount->type, $discount->amount ); + } + + /** + * This function renders the start column. + * + * @since 3.0 + * + * @param EDD_Discount $discount Discount object. + * @return string Start date + */ + public function column_start_date( $discount ) { + $start_date = $discount->start_date; + $timezone_abbreviation = edd_get_timezone_abbr(); + + if ( $start_date ) { + $display = edd_date_i18n( $start_date, 'M. d, Y' ) . '
    ' . edd_date_i18n( $start_date, 'H:i' ) . ' ' . $timezone_abbreviation; + } else { + $display = '—'; + } + + return $display; + } + + /** + * Render the Expiration column. + * + * @since 3.0 + * + * @param EDD_Discount $discount Discount object. + * @return string Expiration date. + */ + public function column_end_date( $discount ) { + $expiration = $discount->end_date; + $timezone_abbreviation = edd_get_timezone_abbr(); + + if ( $expiration ) { + $display = edd_date_i18n( $expiration, 'M. d, Y' ) . '
    ' . edd_date_i18n( $expiration, 'H:i' ) . ' ' . $timezone_abbreviation; + } else { + $display = '—'; + } + + return $display; + } + + /** + * Render the Name column. + * + * @since 1.4 + * + * @param EDD_Discount $discount Discount object. + * @return string Data shown in the Name column + */ + public function column_name( $discount ) { + $base = $this->get_base_url(); + $state = ''; + $row_actions = array(); + $status = $this->get_status(); + + // Bail if current user cannot manage discounts + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + return; + } + + // State + if ( ( ! empty( $status ) && ( $status !== $discount->status ) ) || ( 'active' !== $discount->status ) ) { + $state = ' — ' . edd_get_discount_status_label( $discount->id ); + } + + // Edit + $row_actions['edit'] = '' . __( 'Edit', 'easy-digital-downloads' ) . ''; + + // Active, so add "deactivate" action + if ( 'active' === strtolower( $discount->status ) ) { + $row_actions['cancel'] = '' . __( 'Deactivate', 'easy-digital-downloads' ) . ''; + + // Inactive, so add "activate" action + } elseif ( 'inactive' === strtolower( $discount->status ) ) { + $row_actions['activate'] = '' . __( 'Activate', 'easy-digital-downloads' ) . ''; + } + + // Delete + if ( 0 === (int) $discount->use_count ) { + $row_actions['delete'] = '' . __( 'Delete', 'easy-digital-downloads' ) . ''; + } else { + $row_actions['orders'] = '' . __( 'View Orders', 'easy-digital-downloads' ) . ''; + } + + // Filter all discount row actions + $row_actions = apply_filters( 'edd_discount_row_actions', $row_actions, $discount ); + + // Wrap discount title in strong anchor + $discount_title = '' . stripslashes( $discount->name ) . '' . esc_html( $state ) . ''; + + /** + * Filter to allow additional content to be appended to the discount title. + * + * @since 3.0 + * + * @param EDD_Discount $discount Discount object. + * @param string $base The base URL for the discount list table. + * @param string $status The queried discount status. + * @return string Additional data shown in the Name column + */ + $additional_content = apply_filters( 'edd_discount_row_after_title', '', $discount, $base, $status ); + + // Return discount title & row actions + return $discount_title . $additional_content . $this->row_actions( $row_actions ); + } + + /** + * Render the checkbox column. + * + * @since 1.4 + * + * @param EDD_Discount $discount Discount object. + * @return string Checkbox HTML. + */ + public function column_cb( $discount ) { + return sprintf( + '', + /*$1%s*/ 'discount', + /*$2%s*/ absint( $discount->id ), + /* translators: discount name */ + esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $discount->name ) ) + ); + } + + /** + * Return discount code wrapped in a `` tag. + * + * @since 3.0 + * + * @param EDD_Discount $discount Discount object. + * @return string Discount code HTML. + */ + public function column_code( $discount ) { + return '' . $discount->code . ''; + } + + /** + * Message to be displayed when there are no items. + * + * @since 1.7.2 + */ + public function no_items() { + esc_html_e( 'No discounts found.', 'easy-digital-downloads' ); + } + + /** + * Retrieve the bulk actions + * + * @since 1.4 + * @return array $actions Array of the bulk actions + */ + public function get_bulk_actions() { + return array( + 'activate' => __( 'Activate', 'easy-digital-downloads' ), + 'deactivate' => __( 'Deactivate', 'easy-digital-downloads' ), + 'delete' => __( 'Delete', 'easy-digital-downloads' ) + ); + } + + /** + * Process bulk actions. + * + * @since 1.4 + */ + public function process_bulk_action() { + + // Bail if a nonce was not supplied. + if ( ! isset( $_REQUEST['_wpnonce'] ) ) { + return; + } + + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-discounts' ) ) { + return; + } + + check_admin_referer( 'bulk-discounts' ); + + $ids = wp_parse_id_list( (array) $this->get_request_var( 'discount', false ) ); + + // Bail if no IDs + if ( empty( $ids ) ) { + return; + } + + foreach ( $ids as $id ) { + switch ( $this->current_action() ) { + case 'delete': + edd_delete_discount( $id ); + break; + + case 'cancel': + edd_update_discount_status( $id, 'cancelled' ); + break; + + case 'activate': + edd_update_discount_status( $id, 'active' ); + break; + + case 'deactivate': + edd_update_discount_status( $id, 'inactive' ); + break; + } + } + } + + /** + * Retrieve the discount code counts. + * + * @since 1.4 + */ + public function get_counts() { + $this->counts = edd_get_discount_counts(); + } + + /** + * Retrieves all the data for all the discount codes. + * + * @since 1.4 + * @deprecated 3.0 Use get_data() + * + * @return array Discount codes. + */ + public function discount_codes_data() { + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Discount_Codes_Table::get_data()' ); + + return $this->get_data(); + } + + /** + * Retrieves all of the table data for the discount codes. + * + * @since 3.0 + * + * @return array Discount codes table data. + */ + public function get_data() { + + // Parse pagination + $this->args = $this->parse_pagination_args( array( + 'status' => $this->get_status(), + 'search' => $this->get_search(), + ) ); + + // Return data + return edd_get_discounts( $this->args ); + } + + /** + * Setup the final data for the table + * + * @since 1.4 + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); + $sortable = $this->get_sortable_columns(); + + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + + $status = $this->get_status( 'total' ); + + // Setup pagination + $this->set_pagination_args( array( + 'total_pages' => ceil( $this->counts[ $status ] / $this->per_page ), + 'total_items' => $this->counts[ $status ], + 'per_page' => $this->per_page, + ) ); + } + + /** + * Generate the table navigation above or below the table. + * We're overriding this to turn off the referer param in `wp_nonce_field()`. + * + * @param string $which + * @since 3.1.0.4 + */ + protected function display_tablenav( $which ) { + if ( 'top' === $which ) { + wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false ); + } + ?> +
    + + has_items() ) : ?> +
    + bulk_actions( $which ); ?> +
    + extra_tablenav( $which ); + $this->pagination( $which ); + ?> + +
    +
    + isFree() ) { + $docs_url = edd_link_helper( + 'https://easydigitaldownloads.com/docs/', + array( + 'utm_medium' => 'discounts-contextual-help', + 'utm_content' => 'documentation', + ) + ); + + $upgrade_url = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => 'discounts-contextual-help', + 'utm_content' => 'lite-upgrade', + ) + ); + $screen->set_help_sidebar( + '

    ' . __( 'For more information:', 'easy-digital-downloads' ) . '

    ' . + '

    ' . sprintf( __( 'Visit the documentation on the Easy Digital Downloads website.', 'easy-digital-downloads' ), $docs_url ) . '

    ' . + '

    ' . sprintf( + __( 'Need more from your Easy Digital Downloads store? Upgrade Now!', 'easy-digital-downloads' ), + $upgrade_url + ) . '

    ' + ); + } + + $screen->add_help_tab( array( + 'id' => 'edd-discount-general', + 'title' => __( 'General', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'Discount codes allow you to offer buyers special discounts by having them enter predefined codes during checkout.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Discount codes that are set to "inactive" cannot be redeemed.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Discount codes can be setup to only be used only one time by each customer. If a customer attempts to use a code a second time, they will be given an error.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Discount codes that have already been used cannot be deleted for data integrity and reporting purposes.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-discount-add', + 'title' => __( 'Adding Discounts', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'You can create any number of discount codes easily from this page.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Discount codes have several options:', 'easy-digital-downloads' ) . '

    ' . + '
      '. + '
    • ' . __( 'Name - this is the name given to the discount. Used primarily for administrative purposes.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Code - this is the unique code that customers will enter during checkout to redeem the code.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Type - this is the type of discount this code awards.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Amount - this is the discount amount provided by this code. For percentage based discounts, enter a number such as 70 for 70%. Do not enter a percent sign.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Requirements - this allows you to select the product(s) that are required to be purchased in order for a discount to be applied.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Condition - this lets you set whether all selected products must be in the cart, or just a minimum of one.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Apply discount only to selected Downloads? - If this box is checked, only the prices of the required products will be discounted. If left unchecked, the discount will apply to all products in the cart.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Start Date - this is the date that this code becomes available. If a customer attempts to redeem the code prior to this date, they will be given an error. This is optional.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Expiration Date - this is the end date for the discount. After this date, the code will no longer be able to be used. This is optional.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Minimum Amount - this is the minimum purchase amount required to use this code. If a customer has less than this amount in their cart, they will be given an error. This is optional.', 'easy-digital-downloads' ) . '
    • ' . + '
    • ' . __( 'Max Uses - this is the maximum number of times this discount can be redeemed. Once this number is reached, no more customers will be allowed to use it.', 'easy-digital-downloads' ) . '
    • ' . + '
    ' + ) ); + + do_action( 'edd_discounts_contextual_help', $screen ); +} +add_action( 'load-download_page_edd-discounts', 'edd_discounts_contextual_help' ); diff --git a/includes/admin/discounts/discount-actions.php b/includes/admin/discounts/discount-actions.php old mode 100644 new mode 100755 index 5421e212ae9..b30ec279df9 --- a/includes/admin/discounts/discount-actions.php +++ b/includes/admin/discounts/discount-actions.php @@ -2,114 +2,421 @@ /** * Discount Actions * - * @package Easy Digital Downloads - * @subpackage Discount Actions - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Admin/Discounts + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License * @since 1.0.8.1 -*/ - + */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** - * Add Discount + * Sets up and stores a new discount code. * - * Setups and stores a new discount code. + * @since 1.0 + * @since 3.0 Added backwards compatibility for pre-3.0 discount data. Added discount start/end time. * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_add_discount( $data ) { - if (wp_verify_nonce( $data['edd-discount-nonce'], 'edd_discount_nonce' ) ) { - // setup the discount code details - $posted = array(); - foreach( $data as $key => $value ) { - if( $key != 'edd-discount-nonce' && $key != 'edd-action' ) - $posted[$key] = strip_tags(addslashes( $value ) ); + * @param array $data Discount code data. + */ +function edd_admin_add_discount( $data = array() ) { + + // Bail if no nonce or nonce fails. + if ( ! isset( $data['edd-discount-nonce'] ) || ! wp_verify_nonce( $data['edd-discount-nonce'], 'edd_discount_nonce' ) ) { + return; + } + + // Bail if current user cannot manage shop discounts. + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to create discount codes', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Bail if discount does not exist. + if ( edd_get_discount_by( 'code', $data['code'] ) ) { + edd_redirect( add_query_arg( 'edd-message', 'discount_exists', $data['edd-redirect'] ) ); + } + + // Bail if missing important data. + if ( empty( $data['name'] ) || empty( $data['code'] ) || empty( $data['amount_type'] ) || ( empty( $data['amount'] ) && 0 !== absint( $data['amount'] ) ) ) { + edd_redirect( add_query_arg( 'edd-message', 'discount_validation_failed' ) ); + } + + // Verify only accepted characters. + $sanitized = preg_replace( '/[^a-zA-Z0-9-_]+/', '', $data['code'] ); + if ( strtoupper( $data['code'] ) !== strtoupper( $sanitized ) ) { + edd_redirect( add_query_arg( 'edd-message', 'discount_invalid_code' ) ); + } + + $sanitized_amount = (float) edd_sanitize_amount( $data['amount'] ); + if ( empty( $data['amount'] ) || 0.00 === $sanitized_amount ) { + edd_redirect( add_query_arg( 'edd-message', 'discount_invalid_amount' ) ); + } + + // Setup default discount values. + $to_add = array(); + $to_add['status'] = 'active'; + $current_timestamp = current_time( 'timestamp' ); + + $data = array_filter( $data ); + + foreach ( $data as $column => $value ) { + switch ( $column ) { + + // We skip these here as they are handled below. + case 'start_date': + case 'start': + case 'end_date': + case 'expiration': + break; + + case 'product_reqs': + $to_add[ $column ] = $value; + break; + + case 'amount': + $to_add['amount'] = edd_sanitize_amount( $value ); + break; + + default: + $to_add[ $column ] = is_array( $value ) + ? array_map( 'sanitize_text_field', $value ) + : sanitize_text_field( $value ); + break; } - // set the discount code's default status to active - $posted['status'] = 'active'; - $save = edd_store_discount( $posted ); } -} -add_action( 'edd_add_discount', 'edd_add_discount' ); + // Start date. + if ( ! empty( $data['start_date'] ) ) { + $start_date = sanitize_text_field( $data['start_date'] ); + $start_date_hour = isset( $data['start_date_hour'] ) && (int) $data['start_date_hour'] >= 0 && (int) $data['start_date_hour'] <= 23 + ? intval( $data['start_date_hour'] ) + : '00'; + $start_date_minute = isset( $data['start_date_minute'] ) && (int) $data['start_date_minute'] >= 0 && (int) $data['start_date_minute'] <= 59 + ? intval( $data['start_date_minute'] ) + : '00'; + + $start_date_string = EDD()->utils->get_date_string( + $start_date, + $start_date_hour, + $start_date_minute + ); + // The start date is entered in the user's WP timezone. We need to convert it to UTC prior to saving now. + $to_add['start_date'] = edd_get_utc_date_string( $start_date_string ); + } + + // End date. + if ( ! empty( $data['end_date'] ) ) { + $end_date = sanitize_text_field( $data['end_date'] ); + $end_date_hour = isset( $data['end_date_hour'] ) && (int) $data['end_date_hour'] >= 0 && (int) $data['end_date_hour'] <= 23 + ? intval( $data['end_date_hour'] ) + : '23'; + $end_date_minute = isset( $data['end_date_minute'] ) && (int) $data['end_date_minute'] >= 0 && (int) $data['end_date_minute'] <= 59 + ? intval( $data['end_date_minute'] ) + : '59'; + + $end_date_string = EDD()->utils->get_date_string( + $end_date, + $end_date_hour, + $end_date_minute + ); + // The end date is entered in the user's WP timezone. We need to convert it to UTC prior to saving now. + $to_add['end_date'] = edd_get_utc_date_string( $end_date_string ); + } + + // Meta values. + $to_add['product_reqs'] = isset( $data['product_reqs'] ) ? wp_parse_id_list( $data['product_reqs'] ) : ''; + $to_add['excluded_products'] = isset( $data['excluded_products'] ) ? wp_parse_id_list( $data['excluded_products'] ) : ''; + + $to_add = array_filter( $to_add ); + + // Strip out data that should not be sent to the query methods. + $to_strip = array( + 'discount-id', + 'edd-redirect', + 'edd-action', + 'edd-discount-nonce', + 'start_date_minute', + 'start_date_hour', + 'end_date_minute', + 'end_date_hour', + ); + + // Loop through fields to update, and unset known bad keys. + foreach ( $to_add as $key => $value ) { + if ( in_array( $key, $to_strip, true ) ) { + unset( $to_add[ $key ] ); + } + } + + // Attempt to add. + $created = edd_add_discount( $to_add ); + $arg = ! empty( $created ) + ? 'discount_added' + : 'discount_add_failed'; + + // Redirect. + edd_redirect( add_query_arg( 'edd-message', sanitize_key( $arg ), $data['edd-redirect'] ) ); +} +add_action( 'edd_add_discount', 'edd_admin_add_discount' ); /** - * Edit Discount - * - * Saves an edited discount. + * Saves an edited discount * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_edit_discount( $data ) { - if( isset( $data['edd-discount-nonce'] ) && wp_verify_nonce( $data['edd-discount-nonce'], 'edd_discount_nonce' ) ) { - // setup the discount code details - $discount = array(); - foreach( $data as $key => $value ) { - if( $key != 'edd-discount-nonce' && $key != 'edd-action' && $key != 'discount-id' && $key != 'edd-redirect' ) - $discount[$key] = strip_tags( addslashes( $value ) ); + * @since 3.0 + * @param array $data Discount code data + * @return void + */ +function edd_admin_edit_discount( $data = array() ) { + + // Bail if no nonce or nonce fails + if ( ! isset( $data['edd-discount-nonce'] ) || ! wp_verify_nonce( $data['edd-discount-nonce'], 'edd_discount_nonce' ) ) { + return; + } + + // Bail if current user cannot manage shop discounts + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to edit discount codes', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Bail if discount does not exist + if ( empty( $data['discount-id'] ) ) { + wp_die( __( 'No discount ID supplied', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Setup default discount values + $discount_id = absint( $data['discount-id'] ); + $discount = edd_get_discount( $discount_id ); + + // Bail if no discount + if ( empty( $discount ) || ( $discount->id <= 0 ) ) { + wp_die( __( 'Invalid discount', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $sanitized_amount = (float) edd_sanitize_amount( $data['amount'] ); + if ( empty( $data['amount'] ) || 0.00 === $sanitized_amount ) { + edd_redirect( add_query_arg( 'edd-message', 'discount_invalid_amount' ) ); + } + + // Prepare update + $to_update = array(); + $current_time = current_time( 'timestamp' ); + + $data = array_filter( $data ); + + foreach ( $data as $column => $value ) { + switch ( $column ) { + // We skip these here as they are handled below. + case 'start_date': + case 'start': + case 'end_date': + case 'expiration': + break; + + case 'discount-id': + $to_update['id'] = $value; + break; + + case 'amount': + $to_update['amount'] = edd_sanitize_amount( $value ); + break; + + default: + $to_update[ $column ] = sanitize_text_field( $value ); + break; } - $old_discount = edd_get_discount_by_code( $data['code'] ); - $discount['uses'] = $old_discount['uses']; - if( edd_store_discount( $discount, $data['discount-id'] ) ) { - wp_redirect( add_query_arg( 'edd-message', 'discount_updated', $data['edd-redirect'] ) ); exit; - } else { - wp_redirect( add_query_arg( 'edd-message', 'discount_update_failed', $data['edd-redirect'] ) ); exit; + } + + // Start date. + if ( ! empty( $data['start_date'] ) ) { + $start_date = sanitize_text_field( $data['start_date'] ); + $start_date_hour = isset( $data['start_date_hour'] ) && (int) $data['start_date_hour'] >= 0 && (int) $data['start_date_hour'] <= 23 + ? intval( $data['start_date_hour'] ) + : '00'; + $start_date_minute = isset( $data['start_date_minute'] ) && (int) $data['start_date_minute'] >= 0 && (int) $data['start_date_minute'] <= 59 + ? intval( $data['start_date_minute'] ) + : '00'; + + $start_date_string = EDD()->utils->get_date_string( + $start_date, + $start_date_hour, + $start_date_minute + ); + + // The start date is entered in the user's WP timezone. We need to convert it to UTC prior to saving now. + $to_update['start_date'] = edd_get_utc_date_string( $start_date_string ); + } else { + $to_update['start_date'] = null; + } + + // End date. + if ( ! empty( $data['end_date'] ) ) { + $end_date = sanitize_text_field( $data['end_date'] ); + $end_date_hour = isset( $data['end_date_hour'] ) && (int) $data['end_date_hour'] >= 0 && (int) $data['end_date_hour'] <= 23 + ? intval( $data['end_date_hour'] ) + : '23'; + $end_date_minute = isset( $data['end_date_minute'] ) && (int) $data['end_date_minute'] >= 0 && (int) $data['end_date_minute'] <= 59 + ? intval( $data['end_date_minute'] ) + : '59'; + + $end_date_string = EDD()->utils->get_date_string( + $end_date, + $end_date_hour, + $end_date_minute + ); + + // The end date is entered in the user's WP timezone. We need to convert it to UTC prior to saving now. + $to_update['end_date'] = edd_get_utc_date_string( $end_date_string ); + } else { + $to_update['end_date'] = null; + } + + // Known & accepted core discount meta + $to_update['product_reqs'] = isset( $data['product_reqs'] ) ? wp_parse_id_list( $data['product_reqs'] ) : ''; + $to_update['excluded_products'] = isset( $data['excluded_products'] ) ? wp_parse_id_list( $data['excluded_products'] ) : ''; + + // "Once per customer" checkbox. + $to_update['once_per_customer'] = isset( $data['once_per_customer'] ) + ? 1 + : 0; + + // Strip out known non-columns + $to_strip = array( + + // Legacy + 'discount-id', + + // Redirect + 'edd-redirect', + 'edd-action', + 'edd-discount-nonce', + '_wp_http_referer', + + // Time + 'start_date_minute', + 'start_date_hour', + 'end_date_minute', + 'end_date_hour' + ); + + // Loop through fields to update, and unset known bad keys + foreach ( $to_update as $key => $value ) { + if ( in_array( $key, $to_strip, true ) ) { + unset( $to_update[ $key ] ); } } + + // Attempt to update + $updated = edd_update_discount( $discount_id, $to_update ); + $arg = ! empty( $updated ) + ? 'discount_updated' + : 'discount_not_changed'; + + // Redirect + edd_redirect( add_query_arg( 'edd-message', sanitize_key( $arg ), $data['edd-redirect'] ) ); } -add_action( 'edd_edit_discount', 'edd_edit_discount' ); +add_action( 'edd_edit_discount', 'edd_admin_edit_discount' ); /** - * Delete Discount - * - * Listens for when a discount delete button is clicked. + * Listens for when a discount delete button is clicked and deletes the + * discount code * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_delete_discount( $data ) { - $discount_id = $data['discount']; - edd_remove_discount( $discount_id ); -} -add_action( 'edd_delete_discount', 'edd_delete_discount' ); + * @since 3.0 + * @param array $data Discount code data + * @uses edd_delete_discount() + * @return void + */ +function edd_admin_delete_discount( $data = array() ) { + + // Bail if no nonce or nonce fails + if ( ! isset( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd_discount_nonce' ) ) { + wp_die( __( 'Trying to cheat or something?', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Bail if current user cannot manage shop + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to delete discount codes', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Bail if discount does not exist + if ( empty( $data['discount'] ) ) { + wp_die( __( 'No discount ID supplied', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Setup default discount values + $discount_id = absint( $data['discount'] ); + $deleted = edd_delete_discount( $discount_id ); + $arg = ! empty( $deleted ) + ? 'discount_deleted' + : 'discount_deleted_failed'; + // Redirect + edd_redirect( remove_query_arg( 'edd-action', add_query_arg( 'edd-message', sanitize_key( $arg ), $_SERVER['REQUEST_URI'] ) ) ); +} +add_action( 'edd_delete_discount', 'edd_admin_delete_discount' ); /** - * Activate Discount + * Activates Discount Code * - * Sets a discount code to active. + * Sets a discount status to active * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_activate_discount( $data ) { - $id = $data['discount']; - edd_update_discount_status( $id, 'active' ); + * @since 1.0 + * @param array $data Discount code data + * @uses edd_update_discount_status() + * @return void + */ +function edd_activate_discount( $data = array() ) { + + // Bail if no nonce or nonce fails + if ( ! isset( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd_discount_nonce' ) ) { + wp_die( __( 'Trying to cheat or something?', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Bail if current user cannot manage shop + if( ! current_user_can( 'manage_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to edit discount codes', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $discount_id = absint( $data['discount'] ); + $activated = edd_update_discount_status( $discount_id, 'active' ); + $arg = ! empty( $activated ) + ? 'discount_activated' + : 'discount_activation_failed'; + + // Redirect + edd_redirect( remove_query_arg( 'edd-action', add_query_arg( 'edd-message', sanitize_key( $arg ), $_SERVER['REQUEST_URI'] ) ) ); } add_action( 'edd_activate_discount', 'edd_activate_discount' ); - /** * Deactivate Discount * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_deactivate_discount( $data) { - $id = $data['discount']; - edd_update_discount_status( $id, 'inactive' ); + * Sets a discount status to deactivate + * + * @since 1.0 + * @param array $data Discount code data + * @uses edd_update_discount_status() + * @return void + */ +function edd_deactivate_discount( $data = array() ) { + + // Bail if no nonce or nonce fails + if ( ! isset( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd_discount_nonce' ) ) { + wp_die( __( 'Trying to cheat or something?', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Bail if current user cannot manage shop + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + wp_die( __( 'You do not have permission to create discount codes', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $discount_id = absint( $data['discount'] ); + $activated = edd_update_discount_status( $discount_id, 'inactive' ); + $arg = ! empty( $activated ) + ? 'discount_deactivated' + : 'discount_deactivation_failed'; + + // Redirect + edd_redirect( remove_query_arg( 'edd-action', add_query_arg( 'edd-message', sanitize_key( $arg ), $_SERVER['REQUEST_URI'] ) ) ); } -add_action( 'edd_deactivate_discount', 'edd_deactivate_discount' ); \ No newline at end of file +add_action( 'edd_deactivate_discount', 'edd_deactivate_discount' ); diff --git a/includes/admin/discounts/discount-codes.php b/includes/admin/discounts/discount-codes.php index 7a180c39c02..e243d3dfd51 100755 --- a/includes/admin/discounts/discount-codes.php +++ b/includes/admin/discounts/discount-codes.php @@ -2,130 +2,68 @@ /** * Discount Codes * - * @package Easy Digital Downloads - * @subpackage Discount Codes - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Admin/Discounts + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** - * Discounts Page + * Renders the Discounts admin page. * - * Renders the discount page contents. + * Here only for backwards compatibility * - * @access private - * @since 1.0 - * @return void + * @since 1.4 + * @since 3.0 Nomenclature updated for consistency. */ - function edd_discounts_page() { - global $edd_options; - $current_page = get_bloginfo( 'wpurl' ) . '/wp-admin/admin.php?edit.php?post_type=download&page=edd-discounts'; - ?> -
    - - + // Enqueue scripts. + wp_enqueue_script( 'edd-admin-discounts' ); + + // Edit + if ( ! empty( $_GET['edd-action'] ) && ( 'edit_discount' === $_GET['edd-action'] ) ) { + wp_enqueue_script( 'edd-admin-notes' ); + require_once EDD_PLUGIN_DIR . 'includes/admin/discounts/edit-discount.php'; + + // Add + } elseif ( ! empty( $_GET['edd-action'] ) && ( 'add_discount' === $_GET['edd-action'] ) ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/discounts/add-discount.php'; + + // List tables + } else { + edd_adjustments_page(); + } +} + +/** + * Output the discounts page content, in the adjustments page action. + * + * @since 3.0 + */ +function edd_discounts_page_content() { + require_once EDD_PLUGIN_DIR . 'includes/admin/discounts/class-discount-codes-table.php'; + + $discount_codes_table = new EDD_Discount_Codes_Table(); + $discount_codes_table->prepare_items(); + + do_action( 'edd_discounts_page_top' ); ?> + +
    + search_box( __( 'Search Discounts', 'easy-digital-downloads' ), 'edd-discounts' ); ?> - + + - -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $discount) : ?> - - - - - - - - - - - - - - - - -
    - - - - - - - - - | - - | - - | - - -
    - + views(); + $discount_codes_table->display(); + ?> +
    - - - - -
    - 400 ) ); +} + +// Load discount +$discount_id = absint( $_GET['discount'] ); + +/** @var EDD_Discount */ +$discount = edd_get_discount( $discount_id ); + +// Bail if discount does not exist +if ( empty( $discount ) ) { + wp_die( __( 'Something went wrong.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 400 ) ); } -$discount = edd_get_discount( $_GET['discount']); + +// Setup discount vars +$product_requirements = $discount->get_product_reqs(); +$excluded_products = $discount->get_excluded_products(); +$condition = $discount->get_product_condition(); +$single_use = $discount->get_once_per_customer(); +$type = $discount->get_type(); +$notes = edd_get_discount_notes( $discount->id ); + +// Show/Hide +$flat_display = ( 'flat' === $type ) ? '' : ' style="display:none;"'; +$percent_display = ( 'percent' === $type ) ? '' : ' style="display:none;"'; +$no_notes_display = empty( $notes ) ? '' : ' style="display:none;"'; +$condition_display = ! empty( $product_requirements ) ? '' : ' style="display:none;"'; + +// Dates & times +$discount_start_date = edd_get_edd_timezone_equivalent_date_from_utc( EDD()->utils->date( $discount->start_date, 'utc' ) ); +$discount_end_date = edd_get_edd_timezone_equivalent_date_from_utc( EDD()->utils->date( $discount->end_date, 'utc' ) ); +$start_date = $discount_start_date->format( 'Y-m-d' ); +$start_hour = $discount_start_date->format( 'H' ); +$start_minute = $discount_start_date->format( 'i' ); +$end_date = $discount_end_date->format( 'Y-m-d' ); +$end_hour = $discount_end_date->format( 'H' ); +$end_minute = $discount_end_date->format( 'i' ); +$hours = edd_get_hour_values(); +$minutes = edd_get_minute_values(); ?> -

    -

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    - - - -

    -
    -

    - - - - - -

    -
    \ No newline at end of file +
    +

    + +
    + +
    + id, $discount ); ?> + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + + id, $discount ); ?> + + + + + + +
    + + + +

    +
    + + + +

    +
    + + + + + + + +

    +
    + + + html->product_dropdown( array( + 'name' => 'product_reqs[]', + 'id' => 'edd_products', + 'selected' => $product_requirements, + 'multiple' => true, + 'chosen' => true, + 'placeholder' => sprintf( __( 'Select %s', 'easy-digital-downloads' ), edd_get_label_plural() ) + ) ); ?> +
    > +

    + +

    +

    +
    + +

    +
    +

    +
    + + + html->product_dropdown( array( + 'name' => 'excluded_products[]', + 'id' => 'excluded_products', + 'selected' => $excluded_products, + 'multiple' => true, + 'chosen' => true, + 'placeholder' => sprintf( __( 'Select %s', 'easy-digital-downloads' ), edd_get_label_plural() ) + ) ); ?> +

    +
    + + + + + + + : + + + + + +

    +
    + + + + + + + : + + + + + +

    +
    + + + +

    +
    + + + +

    +
    + + + /> + +
    + + + +

    +
    + + +
    + + id, 'discount' ); ?> +
    +
    + + id, $discount ); ?> + +

    + + + + + + +

    +
    +
    diff --git a/includes/admin/downloads/contextual-help.php b/includes/admin/downloads/contextual-help.php new file mode 100755 index 00000000000..ca5f8b60679 --- /dev/null +++ b/includes/admin/downloads/contextual-help.php @@ -0,0 +1,128 @@ +id != 'download' ) { + return; + } + + $pass_manager = new Pass_Manager(); + if ( $pass_manager->isFree() ) { + $docs_url = edd_link_helper( + 'https://easydigitaldownloads.com/docs/', + array( + 'utm_medium' => 'downloads-contextual-help', + 'utm_content' => 'documentation', + ) + ); + + $upgrade_url = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => 'downloads-contextual-help', + 'utm_content' => 'lite-upgrade', + ) + ); + + $screen->set_help_sidebar( + '

    ' . __( 'For more information:', 'easy-digital-downloads' ) . '

    ' . + '

    ' . sprintf( __( 'Visit the documentation on the Easy Digital Downloads website.', 'easy-digital-downloads' ), $docs_url ) . '

    ' . + '

    ' . sprintf( + __( 'Need more from your Easy Digital Downloads store? Upgrade Now!', 'easy-digital-downloads' ), + $upgrade_url + ) . '

    ' + ); + } + + $screen->add_help_tab( array( + 'id' => 'edd-download-configuration', + 'title' => sprintf( __( '%s Settings', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'content' => + '

    ' . __( 'File Download Limit - Define how many times customers are allowed to download their purchased files. Leave at 0 for unlimited. Resending the purchase receipt will permit the customer one additional download if their limit has already been reached.', 'easy-digital-downloads' ) . '

    ' . + + '

    ' . __( 'Accounting Options - If enabled, define an individual SKU or product number for this download.', 'easy-digital-downloads' ) . '

    ' . + + '

    ' . __( 'Button Options - Disable the automatic output of the purchase button. If disabled, no button will be added to the download page unless the [purchase_link] shortcode is used.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-download-prices', + 'title' => sprintf( __( '%s Prices', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'content' => + '

    ' . __( 'Enable variable pricing - By enabling variable pricing, multiple download options and prices can be configured.', 'easy-digital-downloads' ) . '

    ' . + + '

    ' . __( 'Enable multi-option purchases - By enabling multi-option purchases customers can add multiple variable price items to their cart at once.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-download-files', + 'title' => sprintf( __( '%s Files', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'content' => + '

    ' . __( 'Product Type Options - Choose a default product type or a bundle. Bundled products automatically include access to other download's files when purchased.', 'easy-digital-downloads' ) . '

    ' . + + '

    ' . __( 'File Downloads - Define download file names and their respective file URL. Multiple files can be assigned to a single price, or variable prices.', 'easy-digital-downloads' ) . '

    ' + ) ); + + + $screen->add_help_tab( array( + 'id' => 'edd-product-notes', + 'title' => sprintf( __( '%s Instructions', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'content' => '

    ' . sprintf( __( 'Special instructions for this %s. These will be added to the sales receipt, and may be used by some extensions or themes.', 'easy-digital-downloads' ), strtolower( edd_get_label_singular() ) ) . '

    ' + ) ); + + $colors = array( + 'gray', 'pink', 'blue', 'green', 'teal', 'black', 'dark gray', 'orange', 'purple', 'slate' + ); + + $screen->add_help_tab( array( + 'id' => 'edd-purchase-shortcode', + 'title' => __( 'Purchase Shortcode', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'Purchase Shortcode - If the automatic output of the purchase button has been disabled via the Download Configuration box, a shortcode can be used to output the button or link.', 'easy-digital-downloads' ) . '

    ' . + '

    [purchase_link id="#" price="1" text="Add to Cart" color="blue"]

    ' . + '
      +
    • id - ' . __( 'The ID of a specific download to purchase.', 'easy-digital-downloads' ) . '
    • +
    • price - ' . __( 'Whether to show the price on the purchase button. 1 to show the price, 0 to disable it.', 'easy-digital-downloads' ) . '
    • +
    • text - ' . __( 'The text to be displayed on the button or link.', 'easy-digital-downloads' ) . '
    • +
    • style - ' . __( 'button | text - The style of the purchase link.', 'easy-digital-downloads' ) . '
    • +
    • color - ' . implode( ' | ', $colors ) . '
    • +
    • class - ' . __( 'One or more custom CSS classes you want applied to the button.', 'easy-digital-downloads' ) . '
    • +
    ' . + '

    ' . sprintf( + __( 'For more information, see using Shortcodes on the WordPress.org Codex or Easy Digital Downloads Documentation', 'easy-digital-downloads' ), + 'https://codex.wordpress.org/Shortcode', + 'https://easydigitaldownloads.com/docs/purchase_link-shortcode/' + ) . '

    ' + ) ); + + /** + * Fires off in the EDD Downloads Contextual Help Screen + * + * @since 1.2.3 + * @param object $screen The current admin screen + */ + do_action( 'edd_downloads_contextual_help', $screen ); +} +add_action( 'load-post.php', 'edd_downloads_contextual_help' ); +add_action( 'load-post-new.php', 'edd_downloads_contextual_help' ); diff --git a/includes/admin/downloads/dashboard-columns.php b/includes/admin/downloads/dashboard-columns.php index e23232e48b5..21d8fd87874 100755 --- a/includes/admin/downloads/dashboard-columns.php +++ b/includes/admin/downloads/dashboard-columns.php @@ -2,272 +2,366 @@ /** * Dashboard Columns * - * @package Easy Digital Downloads - * @subpackage Dashboard Columns - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Admin/Downloads + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** - * Donwload Columns + * Download Columns * - * Defines the custom columns and their order. + * Defines the custom columns and their order * - * @access private - * @since 1.0 - * @return array -*/ + * @since 1.0 + * @param array $download_columns Array of download columns + * @return array $download_columns Updated array of download columns for Downloads + * Post Type List Table + */ +function edd_download_columns( $download_columns ) { + $category_labels = edd_get_taxonomy_labels( 'download_category' ); + $tag_labels = edd_get_taxonomy_labels( 'download_tag' ); -function edd_download_columns( $download_columns ){ - $download_columns = array( + return apply_filters( 'edd_download_columns', array( 'cb' => '', - 'title' => __( 'Name', 'edd' ), - 'download_category' => __( 'Categories', 'edd' ), - 'download_tag' => __( 'Tags', 'edd' ), - 'price' => __( 'Price', 'edd' ), - 'sales' => __( 'Sales', 'edd' ), - 'earnings' => __( 'Earnings', 'edd' ), - 'shortcode' => __( 'Short Code', 'edd' ), - 'date' => __( 'Date', 'edd' ) - ); - return $download_columns; + 'title' => __( 'Name', 'easy-digital-downloads' ), + 'download_category' => $category_labels['menu_name'], + 'download_tag' => $tag_labels['menu_name'], + 'price' => __( 'Price', 'easy-digital-downloads' ), + 'sales' => __( 'Net Sales', 'easy-digital-downloads' ), + 'earnings' => __( 'Net Revenue', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ) + ) ); } add_filter( 'manage_edit-download_columns', 'edd_download_columns' ); - /** - * Render Donwload Columns - * - * Render the custom columns content. + * Render Download Columns * - * @access private - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @param string $column_name Column name + * @param int $post_id Download (Post) ID + * @return void + */ function edd_render_download_columns( $column_name, $post_id ) { - if(get_post_type( $post_id) == 'download') { - global $edd_options; - $sales = edd_get_download_sales_stats( $post_id ); - $earnings = edd_get_download_earnings_stats( $post_id ); - $style = isset( $edd_options['button_style'] ) ? $edd_options['button_style'] : 'button'; - $color = isset( $edd_options['checkout_color'] ) ? $edd_options['checkout_color'] : 'blue'; - $purchase_text = isset( $edd_options['add_to_cart_text'] ) ? $edd_options['add_to_cart_text'] : __( 'Purchase', 'edd' ); - - switch ( $column_name) { - case 'download_category': - echo get_the_term_list( $post_id, 'download_category', '', ', ', ''); - break; - case 'download_tag': - echo get_the_term_list( $post_id, 'download_tag', '', ', ', ''); - break; - case 'price': - echo edd_price( $post_id, false); - if ( !edd_has_variable_prices( $post_id) ) { - echo ''; - } - break; - case 'sales': - echo $sales; - break; - case 'earnings': - echo edd_currency_filter( $earnings); - break; - case 'shortcode': - echo '[purchase_link id="' . absint( $post_id ) . '" text="' . esc_html( $purchase_text ) . '" style="' . $style . '" color="' . esc_attr( $color ) . '"]'; - break; - } + + // Bail if not a download + if ( get_post_type( $post_id ) !== 'download' ) { + return; + } + + switch ( $column_name ) { + case 'download_category': + $terms = get_the_term_list( $post_id, 'download_category', '', ', ', ''); + echo ! empty( $terms ) + ? $terms + : '—'; + break; + case 'download_tag': + $terms = get_the_term_list( $post_id, 'download_tag', '', ', ', ''); + echo ! empty( $terms ) + ? $terms + : '—'; + break; + case 'price': + if ( edd_has_variable_prices( $post_id ) ) { + echo edd_price_range( $post_id ); + } else { + echo edd_price( $post_id, false ); + echo ''; + } + break; + case 'sales': + if ( current_user_can( 'view_product_stats', $post_id ) ) { + $sales_url = add_query_arg( array( + 'page' => 'edd-payment-history', + 'product-id' => urlencode( $post_id ) + ), edd_get_admin_base_url() ); + + echo ''; + echo edd_get_download_sales_stats( $post_id ); + echo ''; + } else { + echo '-'; + } + break; + case 'earnings': + if ( current_user_can( 'view_product_stats', $post_id ) ) { + $report_url = edd_get_admin_url( array( + 'page' => 'edd-reports', + 'view' => 'downloads', + 'products' => absint( $post_id ), + ) ); + + echo ''; + echo edd_currency_filter( edd_format_amount( edd_get_download_earnings_stats( $post_id ) ) ); + echo ''; + } else { + echo '-'; + } + break; } } add_action( 'manage_posts_custom_column', 'edd_render_download_columns', 10, 2 ); - /** - * Sortable Donwload Columns - * - * Set the sortable columns content. + * Registers the sortable columns in the list table * - * @access private - * @since 1.0 - * @return array -*/ - + * @since 1.0 + * @param array $columns Array of the columns + * @return array $columns Array of sortable columns + */ function edd_sortable_download_columns( $columns ) { - - $columns['price'] = 'price'; - $columns['sales'] = 'sales'; + $columns['price'] = 'price'; + $columns['sales'] = 'sales'; $columns['earnings'] = 'earnings'; return $columns; } add_filter( 'manage_edit-download_sortable_columns', 'edd_sortable_download_columns' ); - /** - * Sorts Downloads - * - * Sorts the downloads. + * Sorts Columns in the Downloads List Table * - * @access private - * @since 1.0 - * @return array -*/ - + * @since 1.0 + * @param array $vars Array of all the sort variables + * @return array $vars Array of all the sort variables + */ function edd_sort_downloads( $vars ) { - - // check if we're viewing the "download" post type + // Check if we're viewing the "download" post type if ( isset( $vars['post_type'] ) && 'download' == $vars['post_type'] ) { - - // check if 'orderby' is set to "sales" + // Check if 'orderby' is set to "sales" if ( isset( $vars['orderby'] ) && 'sales' == $vars['orderby'] ) { - - // merge the query vars with our custom variables $vars = array_merge( $vars, array( 'meta_key' => '_edd_download_sales', - 'orderby' => 'meta_value_num' + 'orderby' => 'meta_value_num' ) ); } - - // check if "orderby" is set to "earnings" + + // Check if "orderby" is set to "earnings" if ( isset( $vars['orderby'] ) && 'earnings' == $vars['orderby'] ) { - // merge the query vars with our custom variables $vars = array_merge( $vars, array( 'meta_key' => '_edd_download_earnings', - 'orderby' => 'meta_value_num' + 'orderby' => 'meta_value_num' ) ); } - // check if "orderby" is set to "earnings" + // Check if "orderby" is set to "earnings" if ( isset( $vars['orderby'] ) && 'price' == $vars['orderby'] ) { - // merge the query vars with our custom variables $vars = array_merge( $vars, array( 'meta_key' => 'edd_price', - 'orderby' => 'meta_value_num' + 'orderby' => 'meta_value_num' ) ); } } - + return $vars; } +/** + * Sets restrictions on author of Downloads List Table + * + * @since 2.2 + * @param array $vars Array of all sort variables + * @return array Array of all sort variables + */ +function edd_filter_downloads( $vars ) { + if ( isset( $vars['post_type'] ) && 'download' == $vars['post_type'] ) { + + // If an author ID was passed, use it + if ( isset( $_REQUEST['author'] ) && ! current_user_can( 'view_shop_reports' ) ) { + + $author_id = $_REQUEST['author']; + if ( (int) $author_id !== get_current_user_id() ) { + // Tried to view the products of another person, sorry + wp_die( __( 'You do not have permission to view this data.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + $vars = array_merge( + $vars, + array( + 'author' => get_current_user_id() + ) + ); + } + } + + return $vars; +} /** * Download Load * * Sorts the downloads. * - * @access private - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @return void + */ function edd_download_load() { - add_filter( 'request', 'edd_sort_downloads' ); + add_filter( 'request', 'edd_sort_downloads' ); + add_filter( 'request', 'edd_filter_downloads' ); } add_action( 'load-edit.php', 'edd_download_load', 9999 ); - /** * Add Download Filters * - * Add taxonomy drop down filters for downloads. + * Adds taxonomy drop down filters for downloads. * - * @access private - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @return void + */ function edd_add_download_filters() { global $typenow; - - // the current post type - if( $typenow == 'download') { - - $terms = get_terms( 'download_category' ); - if(count( $terms) > 0) { - echo ""; + + // Checks if the current post type is 'download'. + if ( 'download' !== $typenow ) { + return; + } + + $category_args = array( + 'taxonomy' => 'download_category', + 'number' => 30, + ); + + $categories = get_terms( $category_args ); + if ( ! empty( $categories ) ) { + $category_labels = edd_get_taxonomy_labels( 'download_category' ); + + $options = array(); + $options[''] = sprintf( _x( 'All %s', 'plural: Example: "All Categories"', 'easy-digital-downloads' ), $category_labels['name'] ); + + // Ensure we include the selected value in the pre-populated list. + $selected = ! empty( $_GET['download_category'] ) ? $_GET['download_category'] : ''; + if ( ! empty( $selected ) ) { + $selected_term = get_term_by( 'slug', $selected, 'download_category' ); + + $options[ $selected_term->slug ] = $selected_term->name . ' (' . $selected_term->count . ')'; } - - $terms = get_terms('download_tag'); - if(count( $terms) > 0) { - echo ""; + + foreach ( $categories as $category ) { + $options[ $category->slug ] = $category->name . ' (' . $category->count . ')'; } - + + echo EDD()->html->select( + array( + 'name' => 'download_category', + 'id' => 'download_category', + 'class' => 'postform', + 'chosen' => true, + 'show_option_all' => false, + 'show_option_none' => false, + 'options' => $options, + 'selected' => $selected, + 'data' => array( + 'placeholder' => sprintf( _x( 'Search %s', 'plural: Example: "Search Download Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + 'search-type' => 'download_category', + 'search-placeholder' => sprintf( _x( 'Search %s', 'plural: Example: "Search Download Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + ), + ) + ); } + if ( isset( $_REQUEST['all_posts'] ) && '1' === $_REQUEST['all_posts'] ) { + echo ''; + } else if ( ! current_user_can( 'view_shop_reports' ) ) { + $author_id = get_current_user_id(); + echo ''; + } } add_action( 'restrict_manage_posts', 'edd_add_download_filters', 100 ); +/** + * Remove Download Month Filter + * + * Removes the drop down filter for downloads by date. + * + * @author Daniel J Griffiths + * @since 2.1 + * @param array $dates The preset array of dates + * @global $typenow The post type we are viewing + * @return array Empty array disables the dropdown + */ +function edd_remove_month_filter( $dates ) { + global $typenow; + + if ( 'download' === $typenow ) { + $dates = array(); + } + + return $dates; +} +add_filter( 'months_dropdown_results', 'edd_remove_month_filter', 99 ); /** * Adds price field to Quick Edit options * - * @access public - * @since 1.1.3.4 - * @return void -*/ - + * @since 1.1.3.4 + * @param string $column_name Name of the column + * @param string $post_type Current Post Type (i.e. download) + * @return void + */ function edd_price_field_quick_edit( $column_name, $post_type ) { - if ( $column_name != 'price' || $post_type != 'download' ) return; - ?> -
    -
    -

    + // Bail if not price or download + if ( $column_name !== 'price' || $post_type !== 'download' ) { + return; + } ?> +
    +
    +


    -
    + post_type ) && $post->post_type == 'revision' ) - return $post_id; - - // check permissions - if ( ! current_user_can( 'edit_pages', $post_id ) ) { - return $post_id; +function edd_download_meta_box_save( $post_id, $post ) { + if ( ! isset( $_POST['edd_download_meta_box_nonce'] ) || ! wp_verify_nonce( $_POST['edd_download_meta_box_nonce'], basename( __FILE__ ) ) ) { + return; } - - // these are the default fields that get saved - $fields = apply_filters( 'edd_metabox_fields_save', array( - 'edd_price', - '_variable_pricing', - 'edd_variable_prices', - 'edd_download_files', - '_edd_purchase_text', - '_edd_purchase_style', - '_edd_purchase_color', - '_edd_hide_purchase_link', - 'edd_product_notes' - ) - ); - foreach( $fields as $field ) { - if( isset( $_POST[ $field ] ) ) { - if ( is_string( $_POST[$field] ) ) { - $new = esc_attr( $_POST[$field] ); + if ( edd_doing_autosave() || edd_doing_ajax() || isset( $_REQUEST['bulk_edit'] ) ) { + return; + } + + if ( isset( $post->post_type ) && 'revision' == $post->post_type ) { + return; + } + + if ( ! current_user_can( 'edit_product', $post_id ) ) { + return; + } + + edd_download_meta_box_fields_save( $post_id, $post ); +} + +add_action( 'save_post', 'edd_download_meta_box_save', 10, 2 ); + + +/** + * Save post meta when the save_post action is called + * + * @since 3.2 + * @param int $post_id Download (Post) ID. + * @global array $post All the data of the the current post. + * @return void + */ +function edd_download_meta_box_fields_save( $post_id, $post ) { + if ( ! current_user_can( 'edit_product', $post_id ) ) { + return; + } + + // The default fields that get saved + $fields = edd_download_metabox_fields(); + foreach ( $fields as $field ) { + if ( '_edd_default_price_id' == $field && edd_has_variable_prices( $post_id ) ) { + + if ( isset( $_POST[ $field ] ) ) { + $new_default_price_id = ( ! empty( $_POST[ $field ] ) && is_numeric( $_POST[ $field ] ) ) || ( 0 === (int) $_POST[ $field ] ) ? (int) $_POST[ $field ] : 1; + } else { + $new_default_price_id = 1; + } + + update_post_meta( $post_id, $field, $new_default_price_id ); + } elseif ( '_edd_product_type' === $field && '0' === $_POST[ $field ] ) { + // No value stored when product type is "default" ("0") for backwards compatibility. + delete_post_meta( $post_id, '_edd_product_type' ); + } else { + + $new = false; + if ( ! empty( $_POST[ $field ] ) ) { + $new = apply_filters( 'edd_metabox_save_' . $field, $_POST[ $field ] ); + } + if ( ! empty( $new ) ) { + update_post_meta( $post_id, $field, $new ); } else { - $new = $_POST[ $field ]; + delete_post_meta( $post_id, $field ); } - - $new = apply_filters( 'edd_metabox_save_' . $field, $new ); + } + } + + if ( edd_has_variable_prices( $post_id ) ) { + $lowest = edd_get_lowest_price_option( $post_id ); + update_post_meta( $post_id, 'edd_price', $lowest ); + } + + do_action( 'edd_save_download', $post_id, $post ); +} + + +/** + * Sanitize bundled products on save + * + * Ensures a user doesn't try and include a product's ID in the products bundled with that product + * + * @since 1.6 + * + * @param array $products + * @return array + */ +function edd_sanitize_bundled_products_save( $products = array() ) { - update_post_meta( $post_id, $field, $new ); + $products = array_map( function( $value ) { + return preg_replace( '/[^0-9_]/', '', $value ); + }, (array) $products ); + + foreach ( $products as $key => $value ) { + $underscore_pos = strpos( $value, '_' ); + if ( is_numeric( $underscore_pos ) ) { + $product_id = substr( $value, 0, $underscore_pos ); } else { - delete_post_meta( $post_id, $field ); + $product_id = $value; + } + + if ( in_array( $product_id, array( 0, get_the_ID() ) ) ) { + unset( $products[ $key ] ); } } + + $products = array_unique( $products ); + + return ! empty( $products ) ? array_combine( + range( 1, count( $products ) ), + array_values( $products ) + ) : false; +} +add_filter( 'edd_metabox_save__edd_bundled_products', 'edd_sanitize_bundled_products_save' ); + +/** + * Sanitize bundled products conditions on save + * + * @since 3.1 + * + * @param array $bundled_products_conditions + * @return array + */ +function edd_sanitize_bundled_products_conditions_save( $bundled_products_conditions = array() ) { + return ! empty( $bundled_products_conditions ) ? array_combine( + range( 1, count( $bundled_products_conditions ) ), + array_values( $bundled_products_conditions ) + ) : false; +} +add_filter( 'edd_metabox_save__edd_bundled_products_conditions', 'edd_sanitize_bundled_products_conditions_save' ); + +/** + * Don't save blank rows. + * + * When saving, check the price and file table for blank rows. + * If the name of the price or file is empty, that row should not + * be saved. + * + * @since 1.2.2 + * @param array $new Array of all the meta values + * @return array $new New meta value with empty keys removed + */ +function edd_metabox_save_check_blank_rows( $new ) { + foreach ( $new as $key => $value ) { + if ( empty( $value['name'] ) && empty( $value['amount'] ) && empty( $value['file'] ) ) + unset( $new[ $key ] ); + } + + return $new; } -add_action( 'save_post', 'edd_download_meta_box_save' ); -/** Download Configuration *****************************************************************/ +/** Download Configuration ****************************************************/ /** * Download Metabox * * Extensions (as well as the core plugin) can add items to the main download - * configuration metabox via the `edd_meta_box_fields` action. + * configuration metabox via the `edd_meta_box_fields` action. * - * @access private - * @since 1.0 - * @return void + * @since 1.0 + * @return void */ function edd_render_download_meta_box() { - global $post, $edd_options; - - do_action( 'edd_meta_box_fields', $post->ID ); + $post_id = get_the_ID(); + + /* + * Output the price fields + * @since 1.9 + */ + do_action( 'edd_meta_box_price_fields', $post_id ); + + /* + * Output the price fields + * + * Left for backwards compatibility + * + */ + do_action( 'edd_meta_box_fields', $post_id ); + wp_nonce_field( basename( __FILE__ ), 'edd_download_meta_box_nonce' ); } /** - * Price section. + * Download Files Metabox + * + * @since 1.9 + * @return void + */ +function edd_render_files_meta_box() { + /* + * Output the files fields + * @since 1.9 + */ + do_action( 'edd_meta_box_files_fields', get_the_ID() ); +} + +/** + * Download Settings Metabox + * + * @since 1.9 + * @return void + */ +function edd_render_settings_meta_box() { + /* + * Output the files fields + * @since 1.9 + */ + do_action( 'edd_meta_box_settings_fields', get_the_ID() ); +} + +/** + * Price Section * * If variable pricing is not enabled, simply output a single input box. * - * If variable pricing is enabled, outputs a table of all current prices. - * Extensions can add column heads to the table via the `edd_download_file_table_head` + * If variable pricing is enabled, outputs a table of all current prices. + * Extensions can add column heads to the table via the `edd_download_file_table_head` * hook, and actual columns via `edd_download_file_table_row` * - * @see edd_render_price_row() + * @since 1.0 * - * @access private - * @since 1.0 - * @return void + * @see edd_render_price_row() + * + * @param $post_id */ -function edd_render_price_field( $post_id) { - global $edd_options; - - $price = edd_get_download_price( $post_id ); - $variable_pricing = edd_has_variable_prices( $post_id ); - $prices = edd_get_variable_prices( $post_id ); - - $price_display = $variable_pricing ? ' style="display:none;"' : ''; - $variable_display = $variable_pricing ? '' : ' style="display:none;"'; -?> +function edd_render_price_field( $post_id ) { + $price = edd_get_download_price( $post_id ); + $variable_pricing = edd_has_variable_prices( $post_id ); + $prices = edd_get_variable_prices( $post_id ); + $single_option_mode = edd_single_price_option_mode( $post_id ); + $price_display = $variable_pricing ? ' style="display:none;"' : ''; + $variable_display = $variable_pricing ? '' : ' style="display:none;"'; + $currency_position = edd_get_option( 'currency_position', 'before' ); + ?>

    - -

    - -

    - +

    -
    > - - - - - +
    +
    + /> + +
    +
    + +
    > + +
    + 'edd_price', + 'id' => 'edd_price', + 'value' => isset( $price ) ? esc_attr( edd_format_amount( $price ) ) : '', + 'class' => 'edd-form-group__input edd-price-field', + ); + if ( 'before' === $currency_position ) { + ?> + + html->text( $price_args ); + } else { + echo EDD()->html->text( $price_args ); + ?> + + +
    + +
    > - +
    +
    + html->checkbox( array( 'name' => '_edd_price_options_mode', 'current' => $single_option_mode, 'class' => 'edd-form-group__input', ) ); ?> + +
    +
    - - - - - - - - - - - $value ) : - $name = isset( $prices[ $key ]['name'] ) ? $prices[ $key ]['name'] : ''; - $amount = isset( $prices[ $key ]['amount'] ) ? $prices[ $key ]['amount'] : ''; +
    - $args = apply_filters( 'edd_price_row_args', compact( 'name', 'amount' ) ); - ?> -
    - - +
    $value ) : + $name = ( isset( $value['name'] ) && ! empty( $value['name'] ) ) ? $value['name'] : ''; + $index = ( isset( $value['index'] ) && $value['index'] !== '' ) ? $value['index'] : $key; + $amount = isset( $value['amount'] ) ? $value['amount'] : ''; + $args = apply_filters( 'edd_price_row_args', compact( 'name', 'amount' ), $value ); + ?> +
    + +
    + -
    - - +
    + +
    - - - - -
    - -
    +
    + +
    + +
    +
    -
    + null, @@ -227,100 +434,373 @@ function edd_render_price_row( $key, $args = array(), $post_id ) { ); $args = wp_parse_args( $args, $defaults ); - extract( $args, EXTR_SKIP ); -?> - - - - - - - - - - - - - - - - × - - +
    + + ' . esc_html( $key ) . '' ); ?> + + + ' . __( 'Show advanced settings', 'easy-digital-downloads' ) . ''; + } + + $actions['remove'] = '' . sprintf( __( 'Remove', 'easy-digital-downloads' ), esc_html( $key ) ) . '' . sprintf( __( 'Remove price option %s', 'easy-digital-downloads' ), esc_html( $key ) ) . ''; + ?> + + + +
    + +
    + +
    + +
    + html->text( array( + 'name' => 'edd_variable_prices[' . $key . '][name]', + 'id' => 'edd_variable_prices-' . $key . '-name', + 'value' => esc_attr( $args['name'] ), + 'placeholder' => __( 'Option Name', 'easy-digital-downloads' ), + 'class' => 'edd_variable_prices_name large-text' + ) ); ?> +
    +
    + +
    + + 'edd_variable_prices[' . $key . '][amount]', + 'id' => 'edd_variable_prices-' . $key . '-amount', + 'value' => $args['amount'], + 'placeholder' => edd_format_amount( 9.99 ), + 'class' => 'edd-form-group__input edd-price-field', + ); + ?> + +
    + + + html->text( $price_args ); + } else { + echo EDD()->html->text( $price_args ); + ?> + + +
    +
    + +
    +
    + + class="edd_repeatable_default_input" name="_edd_default_price_id" id="edd_default_price_id_" value="" /> + +
    +
    + +
    + + + +
    +
    + +
    +
    + + +
    + +
    + html->select( + array( + 'options' => $types, + 'name' => '_edd_product_type', + 'id' => '_edd_product_type', + 'selected' => $type, + 'show_option_all' => false, + 'show_option_none' => false, + 'class' => 'edd-form-group__input', + ) + ); + ?> + + +
    +
    + get_type(); + $display = $type == 'bundle' ? '' : ' style="display:none;"'; + $products = $download->get_bundled_downloads(); + $variable_pricing = $download->has_variable_prices(); + $variable_display = $variable_pricing ? '' : 'display:none;'; + $variable_class = $variable_pricing ? ' has-variable-pricing' : ''; + $prices = $download->get_prices(); + $bundle_options = EDD()->html->get_products( + array( + 'bundles' => false, + ) + ); + ?> + +
    > +
    +
    + + + +
    + + + +
    + +
    + + + $product ) : ?> +
    +
    +
    + + +
    +
    + +
    + html->product_dropdown( + array( + 'name' => '_edd_bundled_products[' . $index . ']', + 'id' => 'edd_bundled_products_' . esc_attr( $index ), + 'selected' => $product, + 'multiple' => false, + 'chosen' => true, + 'products' => $bundle_options, + 'variations' => true, + 'show_variations_only' => false, + 'class' => 'edd-form-group__input', + ) + ); + ?> +
    +
    +
    + +
    + $price ) { + $options[ $price_key ] = $prices[ $price_key ]['name']; + } + } + + $price_assignments = edd_get_bundle_pricing_variations( $post_id ); + if ( ! empty( $price_assignments[0] ) ) { + $price_assignments = $price_assignments[0]; + } + + $selected = isset( $price_assignments[ $index ] ) ? $price_assignments[ $index ] : null; + + echo EDD()->html->select( array( + 'name' => '_edd_bundled_products_conditions[' . $index . ']', + 'id' => 'edd_bundled_products_conditions_'. esc_attr( $index ), + 'class' => 'edd_repeatable_condition_field', + 'options' => $options, + 'show_option_none' => false, + 'selected' => $selected + ) ); + ?> +
    +
    +
    + +
    + +
    +
    + + + + + +
    + +
    +
    +
    + +
    + + +
    +
    + +
    + html->product_dropdown( array( + 'name' => '_edd_bundled_products[1]', + 'id' => 'edd_bundled_products_1', + 'multiple' => false, + 'chosen' => true, + 'products' => $bundle_options, + 'variations' => true, + 'show_variations_only' => false, + ) ); + ?> +
    +
    +
    + +
    + $price ) { + $options[ $price_key ] = $prices[ $price_key ]['name']; + } + } + + $price_assignments = edd_get_bundle_pricing_variations( $post_id ); + + echo EDD()->html->select( array( + 'name' => '_edd_bundled_products_conditions[1]', + 'id' => 'edd_bundled_products_conditions_1', + 'class' => 'edd-form-group__input edd_repeatable_condition_field', + 'options' => $options, + 'show_option_none' => false, + 'selected' => null, + ) ); + ?> +
    +
    +
    + +
    + +
    +
    + + + +
    +
    + +
    +
    +
    +
    + -
    +function edd_render_files_field( $post_id = 0 ) { + $type = edd_get_download_type( $post_id ); + $files = edd_get_download_files( $post_id ); + $display = $type == 'bundle' ? ' style="display:none;"' : ''; ?> -

    - -

    +
    > +
    +
    - - -
    - - - - - - - - - - - - $value ) : - $name = isset( $files[ $key ]['name'] ) ? $files[ $key ]['name'] : ''; - $file = isset( $files[ $key ]['file'] ) ? $files[ $key ]['file'] : ''; - $condition = isset( $files[ $key ]['condition'] ) ? $files[ $key ]['condition'] : false; - - $args = apply_filters( 'edd_file_row_args', compact( 'name', 'file', 'condition' ) ); - ?> - - - - + $value ) : + $index = isset( $value['index'] ) ? $value['index'] : $key; + $name = isset( $value['name'] ) ? $value['name'] : ''; + $file = isset( $value['file'] ) ? $value['file'] : ''; + $condition = isset( $value['condition'] ) ? $value['condition'] : false; + $thumbnail_size = isset( $value['thumbnail_size'] ) ? $value['thumbnail_size'] : ''; + $attachment_id = isset( $value['attachment_id'] ) ? absint( $value['attachment_id'] ) : false; + + $args = apply_filters( 'edd_file_row_args', compact( 'name', 'file', 'condition', 'attachment_id', 'thumbnail_size' ), $value ); ?> + +
    + +
    + + - - - - - - - - -
    - -
    + else : ?> + +
    + +
    + + + +
    + +
    + +
    +
    null, - 'file' => null, - 'condition' => null - ); - - $args = wp_parse_args( $args, $defaults ); - extract( $args, EXTR_SKIP ); +function edd_render_file_row( $key, $args, $post_id, $index ) { - $prices = edd_get_variable_prices( $post_id ); + $args = wp_parse_args( $args, array( + 'name' => null, + 'file' => null, + 'condition' => null, + 'attachment_id' => null, + 'thumbnail_size' => null, + ) ); + $prices = edd_get_variable_prices( $post_id ); $variable_pricing = edd_has_variable_prices( $post_id ); $variable_display = $variable_pricing ? '' : ' style="display:none;"'; -?> - - - - - - - - - + $variable_class = $variable_pricing ? ' has-variable-pricing' : ''; ?> + +
    + + ' . esc_html( $key ) . '' ); ?> + + + + + + - - - > - - - - - - - × - +
    + +
    +
    + +
    + + + html->text( array( + 'name' => 'edd_download_files[' . $key . '][name]', + 'id' => 'edd_download_files-' . $key . '-name', + 'value' => $args['name'], + 'placeholder' => __( 'My Neat File', 'easy-digital-downloads' ), + 'class' => 'edd-form-group__input edd_repeatable_name_field large-text', + ) ); ?> +
    +
    + +
    + +
    + html->text( array( + 'name' => 'edd_download_files[' . $key . '][file]', + 'id' => 'edd_download_files-' . $key . '-file', + 'value' => $args['file'], + 'placeholder' => __( 'Enter, upload, choose from Media Library', 'easy-digital-downloads' ), + 'class' => 'edd-form-group__input edd_repeatable_upload_field edd_upload_field large-text', + ) ); ?> + + + + +
    +
    + +
    > + + +
    + $price ) { + $options[ $price_key ] = $prices[ $price_key ]['name']; + } + } + + echo EDD()->html->select( array( + 'name' => 'edd_download_files[' . $key . '][condition]', + 'id' => 'edd_download_files-' . $key . '-condition', + 'class' => 'edd-form-group__input edd_repeatable_condition_field', + 'options' => $options, + 'selected' => $args['condition'], + 'show_option_none' => false, + ) ); + ?> +
    +
    + + + +
    -

    - -

    +function edd_download_media_strings( $strings ) { + global $post; -

    - -

    -post_type !== 'download' ) ) { + return $strings; + } + + $downloads_object = get_post_type_object( 'download' ); + $labels = $downloads_object->labels; + + $strings['insertIntoPost'] = sprintf( __( 'Insert into %s', 'easy-digital-downloads' ), strtolower( $labels->singular_name ) ); + + return $strings; } -add_action( 'edd_meta_box_fields', 'edd_render_disable_button', 30 ); +add_filter( 'media_view_strings', 'edd_download_media_strings', 10, 1 ); +/** + * Refund Window + * + * The refund window is the maximum number of days each + * can be downloaded by the buyer + * + * @since 3.0 + * @param int $post_id Download (Post) ID + * @return void + */ +function edd_render_refund_row( $post_id ) { + + // Bail if user cannot manage shop settings + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + $types = edd_get_refundability_types(); + $global_ability = edd_get_option( 'refundability', 'refundable' ); + $refundability = edd_get_download_refundability( $post_id ); + $global_window = edd_get_option( 'refund_window', 30 ); + $edd_refund_window = edd_get_download_refund_window( $post_id ); ?> + +
    +
    + + +
    + +
    + + html->select( array( + 'name' => '_edd_refundability', + 'id' => 'edd_refundability', + 'class' => 'edd-form-group__input', + 'options' => array_merge( + // Manually define a "none" option to set a blank value, vs. -1. + array( + '' => sprintf( + /* translators: Default refund status */ + esc_html_x( 'Default (%1$s)', 'Download refund status', 'easy-digital-downloads' ), + ucwords( $refundability ) + ), + ), + $types + ), + // Use the direct meta value to avoid falling back to default. + 'selected' => get_post_meta( $post_id, '_edd_refundability', true ), + 'show_option_all' => '', + 'show_option_none' => false, + ) ); ?> +
    + +
    + + + +
    +

    + 0 for unlimited', 'easy-digital-downloads' ); ?> +

    +
    + $value ) { - if ( $value['name'] == '' ) - unset( $new[ $key ] ); +function edd_render_download_limit_row( $post_id ) { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; } - return $new; + $edd_download_limit = edd_get_file_download_limit( $post_id ); + $display = 'bundle' == edd_get_download_type( $post_id ) ? ' style="display: none;"' : ''; +?> +
    > +
    + + +
    +

    + 0 for unlimited', 'easy-digital-downloads' ); ?> +

    +
    +ID ); +function edd_render_down_tax_options( $post_id = 0 ) { + + // Bail if current user cannot view shop reports, or taxes are disabled, + if ( ! current_user_can( 'view_shop_reports' ) || ! edd_use_taxes() ) { + return; + } + + $exclusive = edd_download_is_tax_exclusive( $post_id ); ?> + +
    +
    +
    + html->checkbox( + array( + 'name' => '_edd_download_tax_exclusive', + 'id' => '_edd_download_tax_exclusive', + 'current' => $exclusive, + 'class' => 'edd-form-group__input', + ) + ); ?> + +
    +
    + + +
    +
    +
    + html->checkbox( + array( + 'name' => '_edd_quantities_disabled', + 'id' => '_edd_quantities_disabled', + 'current' => $disabled, + 'class' => 'edd-form-group__input', + ) + ); + ?> + +
    +
    - $product_notes = edd_get_product_notes( $post_id ); -?> - -

    + +
    +
    + + +
    +
    + ID ); - $sales = edd_get_download_sales_stats( $post->ID ); - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - do_action('edd_stats_meta_box'); - echo '
    ' . __( 'Sales:', 'edd' ) . ''; - echo $sales; - echo '
    ' . __( 'Earnings:', 'edd' ) . ''; - echo edd_currency_filter( $earnings ); - echo '
    '; +function edd_render_accounting_options( $post_id ) { + if ( ! edd_use_skus() ) { + return; + } + + $edd_sku = get_post_meta( $post_id, 'edd_sku', true ); ?> + +
    +
    +
    + + html->text( + array( + 'name' => 'edd_sku', + 'id' => 'edd_sku', + 'value' => $edd_sku, + 'class' => 'edd-form-group__input small-text', + ) + ); + ?> +
    +
    +ID); - - $per_page = 10; - - if( isset( $_GET['edd_sales_log_page'] ) ) { - $page = intval( $_GET['edd_sales_log_page'] ); - $offset = $per_page * ( $page - 1 ); - $sales_log = edd_get_download_sales_log( $post->ID, true, $per_page, $offset ); - } else { - $page = 1; - $sales_log = edd_get_download_sales_log( $post->ID, false ); - } - - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - if( $sales_log['sales']) { - foreach( $sales_log['sales'] as $sale ) { - if( $sale['user_info']['id'] != 0) { - $user_data = get_userdata( $sale['user_info']['id'] ); - $name = $user_data->display_name; - } else { - $name = $sale['user_info']['first_name'] . ' ' . $sale['user_info']['last_name']; - } - echo ''; - - echo ''; - - echo ''; - - echo ''; - echo ''; - } // endforeach - do_action('edd_purchase_log_meta_box'); - } else { - echo ''; - echo ''; - echo ''; +function edd_render_disable_button( $post_id ) { + $supports_buy_now = edd_shop_supports_buy_now(); + $hide_button = get_post_meta( $post_id, '_edd_hide_purchase_link', true ) ? 1 : 0; + $behavior = get_post_meta( $post_id, '_edd_button_behavior', true ); + $buy_now_support_tooltip = __( 'Purchase button behavior: Add to Cart buttons follow a traditional eCommerce flow. A Buy Now button bypasses most of the process, taking the customer directly from button click to payment, greatly speeding up the process of buying the product.', 'easy-digital-downloads' ); + $no_buy_now_support_tooltip = __( 'Purchase button behavior: Add to Cart buttons follow a traditional eCommerce flow. Buy Now buttons are only available for stores that have a single supported gateway active and that do not use taxes.', 'easy-digital-downloads' ); + ?> + +
    +
    +
    + html->checkbox( + array( + 'name' => '_edd_hide_purchase_link', + 'id' => '_edd_hide_purchase_link', + 'current' => $hide_button, + 'class' => 'edd-form-group__input', + ) + ); + ?> + +
    + +
    + + '_edd_button_behavior', + 'id' => 'edd_button_behavior', + 'selected' => $behavior, + 'options' => array( + 'add_to_cart' => __( 'Add to Cart', 'easy-digital-downloads' ), + 'direct' => __( 'Buy Now', 'easy-digital-downloads' ), + ), + 'show_option_all' => null, + 'show_option_none' => null, + 'class' => 'edd-form-group__input', + ); + echo EDD()->html->select( $args ); + ?> +
    + '; - - $total_log_entries = $sales_log['number']; - $total_pages = ceil( $total_log_entries / $per_page ); - - if ( $total_pages > 1) : - echo '
    '; - echo '
    '; - $base = 'post.php?post=' . $post->ID . '&action=edit%_%'; - echo paginate_links( array( - 'base' => $base, - 'format' => '&edd_sales_log_page=%#%', - 'prev_text' => '« ' . __( 'Previous', 'edd' ), - 'next_text' => __( 'Next', 'edd' ) . ' »', - 'total' => $total_pages, - 'current' => $page, - 'end_size' => 1, - 'mid_size' => 5, - 'add_fragment' => '#edd_purchase_log' - )); - echo '
    '; - echo '
    '; - endif; - + ?> +
    + +ID, true, $per_page, $offset ); - } else { - $page = 1; - $download_log = edd_get_file_download_log( $post->ID, true ); +function edd_render_product_notes_meta_box() { + do_action( 'edd_product_notes_meta_box_fields', get_the_ID() ); +} + +/** + * Render Product Notes Field + * + * @since 1.2.1 + * @param int $post_id Download (Post) ID + * @return void + */ +function edd_render_product_notes_field( $post_id ) { + $product_notes = edd_get_product_notes( $post_id ); + ?> +
    +
    + + +
    +

    +
    + ID ); - - echo '
    ' . __( 'Sales Log', 'edd' ) . ''; - _e('Each sale for this download is listed below.', 'edd' ); - echo '
    '; - echo '' . __( 'Date:', 'edd' ) . ' ' . $sale['date']; - echo ''; - echo '' . __( 'Buyer:', 'edd' ) . ' ' . $name; - echo ''; - echo '' . __( 'Purchase ID:', 'edd' ) . ' ' . $sale['payment_id'] . ''; - echo '
    '; - echo __( 'No sales yet', 'edd' ); - echo '
    '; - echo ''; - echo ''; - echo ''; - echo ''; - if( $download_log) { - foreach( $download_log['downloads'] as $file_download ) { - $user_id = isset( $file_download['user_info']['id']) ? $file_download['user_info']['id'] : 0; - $user_data = get_userdata( $user_id ); - if( $user_data ) { - $name = $user_data->display_name; - } else { - $name = $file_download['user_info']['email']; - } - $file_name = $files[$file_download['file_id']]['name']; - - echo ''; - - echo ''; - - echo ''; - - echo ''; - - echo ''; - - echo ''; - do_action('edd_download_log__meta_box'); - } // endforeach - } else { - echo ''; - echo ''; - echo ''; - } - echo '
    ' . __( 'Download Log', 'edd' ) . ''; - _e('Each time a file is downloaded, it is recorded below.', 'edd' ); - echo '
    '; - echo '' . __( 'Date:', 'edd' ) . ' ' . $file_download['date']; - echo ''; - echo '' . __( 'Downloaded by:', 'edd' ) . ' ' . $name; - echo ''; - echo '' . __( 'IP Address:', 'edd' ) . ' ' . $file_download['ip']; - echo ''; - echo '' . __( 'File: ', 'edd' ) . ' ' . $file_name; - echo '
    '; - echo __( 'No file downloads yet yet', 'edd' ); - echo '
    '; - - $total_log_entries = $download_log['number']; - $total_pages = ceil( $total_log_entries / $per_page ); - - if ( $total_pages > 1) : - echo '
    '; - echo '
    '; - $base = 'post.php?post=' . $post->ID . '&action=edit%_%'; - echo paginate_links( array( - 'base' => $base, - 'format' => '&edd_log_page=%#%', - 'prev_text' => '« ' . __( 'Previous', 'edd' ), - 'next_text' => __( 'Next', 'edd' ) . ' »', - 'total' => $total_pages, - 'current' => $page, - 'end_size' => 1, - 'mid_size' => 5, - 'add_fragment' => '#edd_file_download_log' - )); - echo '
    '; - echo '
    '; - endif; -} \ No newline at end of file + + $earnings = edd_get_download_earnings_stats( $post_id ); + $sales = edd_get_download_sales_stats( $post_id ); + + $sales_url = add_query_arg( array( + 'page' => 'edd-payment-history', + 'product-id' => urlencode( $post_id ) + ), edd_get_admin_base_url() ); + + $earnings_report_url = edd_get_admin_url( array( + 'page' => 'edd-reports', + 'view' => 'downloads', + 'products' => absint( $post_id ), + ) ); + ?> + +

    + + +

    + +

    + + +

    + +
    + +

    + 'edd-tools', + 'view' => 'file_downloads', + 'tab' => 'logs', + 'download' => absint( $post_id ), + ) + ); + ?> +
    +

    + 'error', + 'message' => __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), + ) + ); + } + + add_action( 'wp_mail_failed', array( $this, 'mail_failed' ) ); + + $output = array( + 'status' => 'success', + 'message' => __( 'The test Email Summary was sent successfully!', 'easy-digital-downloads' ), + ); + $email = new EDD_Email_Summary( true ); + $email_status = $email->send_email(); + if ( ! $email_status ) { + $output['status'] = 'error'; + + // Generic error. + $output['message'] = __( 'There was an unknown problem while sending test Email Summary!', 'easy-digital-downloads' ); + + // SMTP error. + if ( $this->mail_smtp_error ) { + $output['message'] = $this->mail_smtp_error; + } + } + + echo wp_json_encode( $output ); + edd_die(); + } + + /** + * Get error message from failed SMTP. + * + * @since 3.1 + * + * @param \WP_Error $error The WP Error thrown in WP core: `wp_mail_failed` hook. + */ + public function mail_failed( $error ) { + if ( ! is_wp_error( $error ) ) { + return; + } + + $this->mail_smtp_error = $error->get_error_message(); + } + +} diff --git a/includes/admin/export-functions.php b/includes/admin/export-functions.php deleted file mode 100644 index 77209a7b302..00000000000 --- a/includes/admin/export-functions.php +++ /dev/null @@ -1,172 +0,0 @@ - 0, - 'number' => -1, - 'mode' => $mode, - 'orderby' => $orderby, - 'order' => $order, - 'user' => $user, - 'status' => $status - ) ); - if($payments){ - $i = 0; - echo '"' . __('ID', 'edd') . '",'; - echo '"' . __('Email', 'edd') . '",'; - echo '"' . __('First Name', 'edd') . '",'; - echo '"' . __('Last Name', 'edd') . '",'; - echo '"' . __('Products', 'edd') . '",'; - echo '"' . __('Discounts,', 'edd') . '",'; - echo '"' . __('Amount paid', 'edd') . '",'; - echo '"' . __('Payment method', 'edd') . '",'; - echo '"' . __('Key', 'edd') . '",'; - echo '"' . __('Date', 'edd') . '",'; - echo '"' . __('User', 'edd') . '",'; - echo '"' . __('Status', 'edd') . '"'; - echo "\r\n"; - foreach($payments as $payment){ - - $payment_meta = edd_get_payment_meta( $payment->ID ); - $user_info = maybe_unserialize($payment_meta['user_info']); - - echo '"' . $payment->ID . '",'; - echo '"' . $payment_meta['email'] . '",'; - echo '"' . $user_info['first_name'] . '",'; - echo '"' . $user_info['last_name']. '",'; - $downloads = isset($payment_meta['cart_details']) ? maybe_unserialize($payment_meta['cart_details']) : false; - if (empty($downloads ) || !$downloads ) { - $downloads = maybe_unserialize($payment_meta['downloads']); - } - - if ($downloads) { - - foreach($downloads as $key => $download) { - - // retrieve the ID of the download - $id = isset($payment_meta['cart_details']) ? $download['id'] : $download; - - // if download has variable prices, override the default price - $price_override = isset($payment_meta['cart_details']) ? $download['price'] : null; - - $user_info = unserialize($payment_meta['user_info']); - - // calculate the final price - $price = edd_get_download_final_price($id, $user_info, $price_override); - - // show name of download - echo get_the_title($id); - - echo ' - '; - - if (isset($downloads[$key]['item_number'])) { - - $price_options = $downloads[$key]['item_number']['options']; - - if (isset($price_options['price_id']) ) { - echo edd_get_price_option_name($id, $price_options['price_id']); - echo ' - '; - } - } - echo html_entity_decode( edd_currency_filter($price) ); - - if( $key != ( count( $downloads ) -1 ) ) { - echo ' / '; - } - - } - - echo ','; - - } - - if (isset($user_info['discount']) && $user_info['discount'] != 'none') { - echo '"' . $user_info['discount'] . '",'; - } else { - - echo '"' . __('none', 'edd') . '",'; - } - echo '"' . html_entity_decode( edd_currency_filter($payment_meta['amount']) ) . '",'; - - $gateway = get_post_meta($payment->ID, '_edd_payment_gateway', true); - if ($gateway) { - echo '"' . edd_get_gateway_admin_label($gateway) . '",'; - } else { - echo '"' . __('none', 'edd') . '",'; - } - echo '"' . $payment_meta['key'] . '",'; - echo '"' . date(get_option('date_format'), strtotime($payment->post_date)) . '",'; - - $user_id = isset($user_info['id']) && $user_info['id'] != -1 ? $user_info['id'] : $user_info['email']; - - echo '"'; - echo is_numeric($user_id) ? get_user_by('id', $user_id)->display_name : __('guest', 'edd'); - echo '",'; - echo '"' . edd_get_payment_status($payment, true) . '"'; - echo "\r\n"; - - $i++; - } - } else { - echo __('No payments recorded yet', 'edd'); - } - } - die(); -} -add_action('admin_init', 'edd_export_payment_history'); - - -/** - * Export all customers to CSV - * - * Using wpdb directly for performance reasons (workaround of calling all posts and fetch data respectively) - * - * @access private - * @since 1.2 - * @return void -*/ -function edd_export_all_customers() { - if( current_user_can( 'administrator' ) ) { - global $wpdb; - - $emails = $wpdb->get_col("SELECT DISTINCT meta_value FROM $wpdb->postmeta WHERE meta_key = '_edd_payment_user_email' "); - - if( ! empty( $emails ) ) { - header("Content-type: text/csv"); - $today = date("Y-m-d"); - header("Content-Disposition: attachment; filename=user_emails-$today.csv"); - header("Pragma: no-cache"); - header("Expires: 0"); - - echo implode( "\n", $emails ); - exit; - } - } else { - wp_die(__( 'Export not allowed for non-administrators.', 'edd' ) ); - } -} -add_action( 'edd_email_export', 'edd_export_all_customers' ); diff --git a/includes/admin/import/class-batch-import-downloads.php b/includes/admin/import/class-batch-import-downloads.php new file mode 100644 index 00000000000..c6281a416d5 --- /dev/null +++ b/includes/admin/import/class-batch-import-downloads.php @@ -0,0 +1,567 @@ +field_mapping = array( + 'post_title' => '', + 'post_name' => '', + 'post_status' => 'draft', + 'post_author' => '', + 'post_date' => '', + 'post_content' => '', + 'post_excerpt' => '', + 'price' => '', + 'files' => '', + 'categories' => '', + 'tags' => '', + 'sku' => '', + 'earnings' => '', + 'sales' => '', + 'featured_image' => '', + 'download_limit' => '', + 'notes' => '' + ); + } + + /** + * Process a step + * + * @since 2.6 + * @return bool + */ + public function process_step() { + + $more = false; + + if ( ! $this->can_import() ) { + wp_die( __( 'You do not have permission to import data.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $i = 1; + $offset = $this->step > 1 ? ( $this->per_step * ( $this->step - 1 ) ) : 0; + + if( $offset > $this->total ) { + $this->done = true; + + // Delete the uploaded CSV file. + unlink( $this->file ); + } + + if( ! $this->done && $this->csv ) { + + $more = true; + + foreach( $this->csv as $key => $row ) { + + // Skip all rows until we pass our offset + if( $key + 1 <= $offset ) { + continue; + } + + // Done with this batch + if( $i > $this->per_step ) { + break; + } + + // Import Download + $args = array( + 'post_type' => 'download', + 'post_title' => '', + 'post_name' => '', + 'post_status' => '', + 'post_author' => '', + 'post_date' => '', + 'post_content' => '', + 'post_excerpt' => '' + ); + + foreach ( $args as $key => $field ) { + if ( ! empty( $this->field_mapping[ $key ] ) && ! empty( $row[ $this->field_mapping[ $key ] ] ) ) { + $args[ $key ] = $row[ $this->field_mapping[ $key ] ]; + } + } + + if ( empty( $args['post_author'] ) ) { + $user = wp_get_current_user(); + $args['post_author'] = $user->ID; + } else { + + // Check all forms of possible user inputs, email, ID, login. + if ( is_email( $args['post_author'] ) ) { + $user = get_user_by( 'email', $args['post_author'] ); + } elseif ( is_numeric( $args['post_author'] ) ) { + $user = get_user_by( 'ID', $args['post_author'] ); + } else { + $user = get_user_by( 'login', $args['post_author'] ); + } + + // If we don't find one, resort to the logged in user. + if ( false === $user ) { + $user = wp_get_current_user(); + } + + $args['post_author'] = $user->ID; + } + + // Format the date properly + if ( ! empty( $args['post_date'] ) ) { + + $timestamp = strtotime( $args['post_date'], current_time( 'timestamp' ) ); + $date = date( 'Y-m-d H:i:s', $timestamp ); + + // If the date provided results in a date string, use it, or just default to today so it imports + if ( ! empty( $date ) ) { + $args['post_date'] = $date; + } else { + $date = ''; + } + + } + + + // Detect any status that could map to `publish` + if ( ! empty( $args['post_status'] ) ) { + + $published_statuses = array( + 'live', + 'published', + ); + + $current_status = strtolower( $args['post_status'] ); + + if ( in_array( $current_status, $published_statuses ) ) { + $args['post_status'] = 'publish'; + } + + } + + $download_id = wp_insert_post( $args ); + + // setup categories + if( ! empty( $this->field_mapping['categories'] ) && ! empty( $row[ $this->field_mapping['categories'] ] ) ) { + + $categories = $this->str_to_array( $row[ $this->field_mapping['categories'] ] ); + + $this->set_taxonomy_terms( $download_id, $categories, 'download_category' ); + + } + + // setup tags + if( ! empty( $this->field_mapping['tags'] ) && ! empty( $row[ $this->field_mapping['tags'] ] ) ) { + + $tags = $this->str_to_array( $row[ $this->field_mapping['tags'] ] ); + + $this->set_taxonomy_terms( $download_id, $tags, 'download_tag' ); + + } + + // setup price(s) + if( ! empty( $this->field_mapping['price'] ) && ! empty( $row[ $this->field_mapping['price'] ] ) ) { + + $price = $row[ $this->field_mapping['price'] ]; + + $this->set_price( $download_id, $price ); + + } + + // setup files + if( ! empty( $this->field_mapping['files'] ) && ! empty( $row[ $this->field_mapping['files'] ] ) ) { + + $files = $this->convert_file_string_to_array( $row[ $this->field_mapping['files'] ] ); + + $this->set_files( $download_id, $files ); + + } + + // Product Image + if( ! empty( $this->field_mapping['featured_image'] ) && ! empty( $row[ $this->field_mapping['featured_image'] ] ) ) { + + $image = sanitize_text_field( $row[ $this->field_mapping['featured_image'] ] ); + + $this->set_image( $download_id, $image, $args['post_author'] ); + + } + + // File download limit + if( ! empty( $this->field_mapping['download_limit'] ) && ! empty( $row[ $this->field_mapping['download_limit'] ] ) ) { + + update_post_meta( $download_id, '_edd_download_limit', absint( $row[ $this->field_mapping['download_limit'] ] ) ); + } + + // Sale count + if( ! empty( $this->field_mapping['sales'] ) && ! empty( $row[ $this->field_mapping['sales'] ] ) ) { + + update_post_meta( $download_id, '_edd_download_sales', absint( $row[ $this->field_mapping['sales'] ] ) ); + } + + // Earnings + if( ! empty( $this->field_mapping['earnings'] ) && ! empty( $row[ $this->field_mapping['earnings'] ] ) ) { + + update_post_meta( $download_id, '_edd_download_earnings', edd_sanitize_amount( $row[ $this->field_mapping['earnings'] ] ) ); + } + + // Notes + if( ! empty( $this->field_mapping['notes'] ) && ! empty( $row[ $this->field_mapping['notes'] ] ) ) { + + update_post_meta( $download_id, 'edd_product_notes', sanitize_text_field( $row[ $this->field_mapping['notes'] ] ) ); + } + + // SKU + if( ! empty( $this->field_mapping[ 'sku' ] ) && ! empty( $row[ $this->field_mapping[ 'sku' ] ] ) ) { + + update_post_meta( $download_id, 'edd_sku', sanitize_text_field( $row[ $this->field_mapping['sku'] ] ) ); + } + + // Custom fields + + + $i++; + } + + } + + return $more; + } + + /** + * Return the calculated completion percentage + * + * @since 2.6 + * @return int + */ + public function get_percentage_complete() { + + $percentage = 0; + if ( $this->total > 0 ) { + $percentage = ( $this->step * $this->per_step / $this->total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set up and store the price for the download + * + * @since 2.6 + * @return void + */ + private function set_price( $download_id = 0, $price = '' ) { + + if( is_numeric( $price ) ) { + + update_post_meta( $download_id, 'edd_price', edd_sanitize_amount( $price ) ); + + } else { + + $prices = $this->str_to_array( $price ); + + if( ! empty( $prices ) ) { + + $variable_prices = array(); + $price_id = 1; + foreach( $prices as $price ) { + + // See if this matches the EDD Download export for variable prices + if( false !== strpos( $price, ':' ) ) { + + $price = array_map( 'trim', explode( ':', $price ) ); + + $variable_prices[ $price_id ] = array( 'name' => $price[ 0 ], 'amount' => $price[ 1 ] ); + $price_id++; + + } + + } + + update_post_meta( $download_id, '_variable_pricing', 1 ); + update_post_meta( $download_id, 'edd_variable_prices', $variable_prices ); + + } + + } + + } + + /** + * Set up and store the file downloads + * + * @since 2.6 + * @return void + */ + private function set_files( $download_id = 0, $files = array() ) { + + if( ! empty( $files ) ) { + + $download_files = array(); + $file_id = 1; + foreach( $files as $file ) { + + $condition = ''; + + if ( false !== strpos( $file, ';' ) ) { + + $split_on = strpos( $file, ';' ); + $file_url = substr( $file, 0, $split_on ); + $condition = substr( $file, $split_on + 1 ); + + } else { + + $file_url = $file; + + } + + $download_file_args = array( + 'index' => $file_id, + 'file' => $file_url, + 'name' => basename( $file_url ), + 'condition' => empty( $condition ) ? 'all' : $condition + ); + + $download_files[ $file_id ] = $download_file_args; + $file_id++; + + } + + update_post_meta( $download_id, 'edd_download_files', $download_files ); + + } + + } + + /** + * Set up and store the Featured Image + * + * @since 2.6 + * @return void + */ + private function set_image( $download_id = 0, $image = '', $post_author = 0 ) { + + $is_url = false !== filter_var( $image, FILTER_VALIDATE_URL ); + $is_local = $is_url && false !== strpos( $image, site_url() ); + $ext = edd_get_file_extension( $image ); + + if( $is_url && $is_local ) { + + // Image given by URL, see if we have an attachment already + $attachment_id = attachment_url_to_postid( $image ); + + } elseif( $is_url ) { + + if( ! function_exists( 'media_sideload_image' ) ) { + + require_once( ABSPATH . 'wp-admin/includes/file.php' ); + + } + + // Image given by external URL + $url = media_sideload_image( $image, $download_id, '', 'src' ); + + if( ! is_wp_error( $url ) ) { + + $attachment_id = attachment_url_to_postid( $url ); + + } + + + } elseif( false === strpos( $image, '/' ) && edd_get_file_extension( $image ) ) { + + // Image given by name only + + $upload_dir = wp_upload_dir(); + + if( file_exists( trailingslashit( $upload_dir['path'] ) . $image ) ) { + + // Look in current upload directory first + $file = trailingslashit( $upload_dir['path'] ) . $image; + + } else { + + // Now look through year/month sub folders of upload directory for files with our image's same extension + $files = glob( $upload_dir['basedir'] . '/*/*/*' . $ext ); + foreach( $files as $file ) { + + if( basename( $file ) == $image ) { + + // Found our file + break; + + } + + // Make sure $file is unset so our empty check below does not return a false positive + unset( $file ); + + } + + } + + if( ! empty( $file ) ) { + + // We found the file, let's see if it already exists in the media library + + $guid = str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $file ); + $attachment_id = attachment_url_to_postid( $guid ); + + + if( empty( $attachment_id ) ) { + + // Doesn't exist in the media library, let's add it + + $filetype = wp_check_filetype( basename( $file ), null ); + + // Prepare an array of post data for the attachment. + $attachment = array( + 'guid' => $guid, + 'post_mime_type' => $filetype['type'], + 'post_title' => preg_replace( '/\.[^.]+$/', '', $image ), + 'post_content' => '', + 'post_status' => 'inherit', + 'post_author' => $post_author + ); + + // Insert the attachment. + $attachment_id = wp_insert_attachment( $attachment, $file, $download_id ); + + // Make sure that this file is included, as wp_generate_attachment_metadata() depends on it. + require_once( ABSPATH . 'wp-admin/includes/image.php' ); + + // Generate the metadata for the attachment, and update the database record. + $attach_data = wp_generate_attachment_metadata( $attachment_id, $file ); + wp_update_attachment_metadata( $attachment_id, $attach_data ); + + } + + } + + } + + if( ! empty( $attachment_id ) ) { + + return set_post_thumbnail( $download_id, $attachment_id ); + + } + + return false; + + } + + /** + * Set up and taxonomy terms + * + * @since 2.6 + * @return void + */ + private function set_taxonomy_terms( $download_id = 0, $terms = array(), $taxonomy = 'download_category' ) { + + $terms = $this->maybe_create_terms( $terms, $taxonomy ); + + if( ! empty( $terms ) ) { + + wp_set_object_terms( $download_id, $terms, $taxonomy ); + + } + + } + + /** + * Locate term IDs or create terms if none are found + * + * @since 2.6 + * @return array + */ + private function maybe_create_terms( $terms = array(), $taxonomy = 'download_category' ) { + + // Return of term IDs + $term_ids = array(); + + foreach( $terms as $term ) { + + if( is_numeric( $term ) && 0 === (int) $term ) { + + $t = get_term( $term, $taxonomy ); + + } else { + + $t = get_term_by( 'name', $term, $taxonomy ); + + if( ! $t ) { + + $t = get_term_by( 'slug', $term, $taxonomy ); + + } + + } + + if( ! empty( $t ) ) { + + $term_ids[] = $t->term_id; + + } else { + + $term_data = wp_insert_term( $term, $taxonomy, array( 'slug' => sanitize_title( $term ) ) ); + + if( ! is_wp_error( $term_data ) ) { + + $term_ids[] = $term_data['term_id']; + + } + + } + + } + + return array_map( 'absint', $term_ids ); + } + + /** + * Retrieve URL to Downloads list table + * + * @since 2.6 + * @return string + */ + public function get_list_table_url() { + return edd_get_admin_base_url(); + } + + /** + * Retrieve Download label + * + * @since 2.6 + * @return void + */ + public function get_import_type_label() { + return edd_get_label_plural( true ); + } + +} diff --git a/includes/admin/import/class-batch-import-payments.php b/includes/admin/import/class-batch-import-payments.php new file mode 100644 index 00000000000..0ce839bf4f6 --- /dev/null +++ b/includes/admin/import/class-batch-import-payments.php @@ -0,0 +1,644 @@ +per_step = 5; + + // Set up default field map values + $this->field_mapping = array( + 'total' => '', + 'subtotal' => '', + 'tax' => 'draft', + 'number' => '', + 'mode' => '', + 'gateway' => '', + 'date' => '', + 'status' => '', + 'email' => '', + 'name' => '', + 'first_name' => '', + 'last_name' => '', + 'edd_customer_id' => '', + 'user_id' => '', + 'discounts' => '', + 'key' => '', + 'transaction_id' => '', + 'ip' => '', + 'currency' => '', + 'parent_payment_id' => '', + 'downloads' => '', + 'line1' => '', + 'line2' => '', + 'city' => '', + 'state' => '', + 'zip' => '', + 'country' => '', + ); + } + + /** + * Process a step + * + * @since 2.6 + * @return bool + */ + public function process_step() { + + $more = false; + + if ( ! $this->can_import() ) { + wp_die( __( 'You do not have permission to import data.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Remove certain actions to ensure they don't fire when creating the payments + remove_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999 ); + remove_action( 'edd_admin_sale_notice', 'edd_admin_email_notice', 10 ); + + $i = 1; + $offset = $this->step > 1 ? ( $this->per_step * ( $this->step - 1 ) ) : 0; + + if( $offset > $this->total ) { + $this->done = true; + + // Clean up the temporary records in the payment import process + global $wpdb; + $sql = "DELETE FROM {$wpdb->prefix}edd_customermeta WHERE meta_key = '_canonical_import_id'"; + $wpdb->query( $sql ); + + // Delete the uploaded CSV file. + unlink( $this->file ); + } + + if( ! $this->done && $this->csv ) { + + $more = true; + + foreach( $this->csv as $key => $row ) { + + // Skip all rows until we pass our offset + if( $key + 1 <= $offset ) { + continue; + } + + // Done with this batch + if( $i > $this->per_step ) { + break; + } + + // Import payment + $this->create_payment( $row ); + + $i++; + } + + } + + return $more; + } + + /** + * Set up and store a payment record from a CSV row + * + * @since 2.6 + * @return void + */ + public function create_payment( $row = array() ) { + + $payment = new EDD_Payment; + $payment->status = 'pending'; + + if( ! empty( $this->field_mapping['number'] ) && ! empty( $row[ $this->field_mapping['number'] ] ) ) { + + $payment->number = sanitize_text_field( $row[ $this->field_mapping['number'] ] ); + + } + + if( ! empty( $this->field_mapping['mode'] ) && ! empty( $row[ $this->field_mapping['mode'] ] ) ) { + + $mode = strtolower( sanitize_text_field( $row[ $this->field_mapping['mode'] ] ) ); + $mode = 'test' != $mode && 'live' != $mode ? false : $mode; + if( ! $mode ) { + $mode = edd_is_test_mode() ? 'test' : 'live'; + } + + $payment->mode = $mode; + + } + + if( ! empty( $this->field_mapping['date'] ) && ! empty( $row[ $this->field_mapping['date'] ] ) ) { + + $date = sanitize_text_field( $row[ $this->field_mapping['date'] ] ); + + if( ! strtotime( $date ) ) { + + $date = date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ); + + } else { + + $date = date( 'Y-m-d H:i:s', strtotime( $date ) ); + + } + + $payment->date = $date; + + } + + $payment->customer_id = $this->set_customer( $row ); + + if( ! empty( $this->field_mapping['email'] ) && ! empty( $row[ $this->field_mapping['email'] ] ) ) { + + $payment->email = sanitize_text_field( $row[ $this->field_mapping['email'] ] ); + + } + + if ( ! empty( $this->field_mapping['name'] ) && ! empty( $row[ $this->field_mapping['name'] ] ) ) { + + $payment->name = sanitize_text_field( $row[ $this->field_mapping['name'] ] ); + + } else { + + if ( ! empty( $this->field_mapping['first_name'] ) && ! empty( $row[ $this->field_mapping['first_name'] ] ) ) { + + $payment->first_name = sanitize_text_field( $row[ $this->field_mapping['first_name'] ] ); + + } + + if ( ! empty( $this->field_mapping['last_name'] ) && ! empty( $row[ $this->field_mapping['last_name'] ] ) ) { + + $payment->last_name = sanitize_text_field( $row[ $this->field_mapping['last_name'] ] ); + + } + } + + if( ! empty( $this->field_mapping['user_id'] ) && ! empty( $row[ $this->field_mapping['user_id'] ] ) ) { + + $user_id = sanitize_text_field( $row[ $this->field_mapping['user_id'] ] ); + + if( is_numeric( $user_id ) ) { + + $user_id = absint( $row[ $this->field_mapping['user_id'] ] ); + $user = get_userdata( $user_id ); + + } elseif( is_email( $user_id ) ) { + + $user = get_user_by( 'email', $user_id ); + + } else { + + $user = get_user_by( 'login', $user_id ); + + } + + if( $user ) { + + $payment->user_id = $user->ID; + + $customer = new EDD_Customer( $payment->customer_id ); + + if( empty( $customer->user_id ) ) { + $customer->update( array( 'user_id' => $user->ID ) ); + } + + } + + } + + if( ! empty( $this->field_mapping['discounts'] ) && ! empty( $row[ $this->field_mapping['discounts'] ] ) ) { + + $payment->discounts = sanitize_text_field( $row[ $this->field_mapping['discounts'] ] ); + + } + + if( ! empty( $this->field_mapping['transaction_id'] ) && ! empty( $row[ $this->field_mapping['transaction_id'] ] ) ) { + + $payment->transaction_id = sanitize_text_field( $row[ $this->field_mapping['transaction_id'] ] ); + + } + + if( ! empty( $this->field_mapping['ip'] ) && ! empty( $row[ $this->field_mapping['ip'] ] ) ) { + + $payment->ip = sanitize_text_field( $row[ $this->field_mapping['ip'] ] ); + + } + + if( ! empty( $this->field_mapping['gateway'] ) && ! empty( $row[ $this->field_mapping['gateway'] ] ) ) { + + $gateways = edd_get_payment_gateways(); + $gateway = strtolower( sanitize_text_field( $row[ $this->field_mapping['gateway'] ] ) ); + + if( ! array_key_exists( $gateway, $gateways ) ) { + + foreach( $gateways as $key => $enabled_gateway ) { + + if( $enabled_gateway['checkout_label'] == $gateway ) { + + $gateway = $key; + break; + + } + + } + + } + + $payment->gateway = $gateway; + + } + + if( ! empty( $this->field_mapping['currency'] ) && ! empty( $row[ $this->field_mapping['currency'] ] ) ) { + + $payment->currency = strtoupper( sanitize_text_field( $row[ $this->field_mapping['currency'] ] ) ); + + } + + if( ! empty( $this->field_mapping['key'] ) && ! empty( $row[ $this->field_mapping['key'] ] ) ) { + + $payment->key = sanitize_text_field( $row[ $this->field_mapping['key'] ] ); + + } + + if( ! empty( $this->field_mapping['parent_payment_id'] ) && ! empty( $row[ $this->field_mapping['parent_payment_id'] ] ) ) { + + $payment->parent_payment_id = absint( $row[ $this->field_mapping['parent_payment_id'] ] ); + + } + + if( ! empty( $this->field_mapping['downloads'] ) && ! empty( $row[ $this->field_mapping['downloads'] ] ) ) { + + if( __( 'Products (Raw)', 'easy-digital-downloads' ) == $this->field_mapping['downloads'] ) { + + // This is an EDD export so we can extract prices + $downloads = $this->get_downloads_from_edd( $row[ $this->field_mapping['downloads'] ] ); + + } else { + + $downloads = $this->str_to_array( $row[ $this->field_mapping['downloads'] ] ); + + } + + if( is_array( $downloads ) ) { + + $download_count = count( $downloads ); + + foreach( $downloads as $download ) { + + if( is_array( $download ) ) { + $download_name = $download['download']; + $price = $download['price']; + $tax = $download['tax']; + $price_id = $download['price_id']; + } else { + $download_name = $download; + } + + $download_id = $this->maybe_create_download( $download_name ); + + if( ! $download_id ) { + continue; + } + + $item_price = ! isset( $price ) ? edd_get_download_price( $download_id ) : $price; + $item_tax = ! isset( $tax ) ? ( $download_count > 1 ? 0.00 : $payment->tax ) : $tax; + $price_id = ! isset( $price_id ) ? false : $price_id; + + $args = array( + 'item_price' => $item_price, + 'tax' => $item_tax, + 'price_id' => $price_id, + ); + + $payment->add_download( $download_id, $args ); + + } + + } + + } + + if( ! empty( $this->field_mapping['total'] ) && ! empty( $row[ $this->field_mapping['total'] ] ) ) { + + $payment->total = edd_sanitize_amount( $row[ $this->field_mapping['total'] ] ); + + } + + if( ! empty( $this->field_mapping['tax'] ) && ! empty( $row[ $this->field_mapping['tax'] ] ) ) { + + $payment->tax = edd_sanitize_amount( $row[ $this->field_mapping['tax'] ] ); + + } + + if( ! empty( $this->field_mapping['subtotal'] ) && ! empty( $row[ $this->field_mapping['subtotal'] ] ) ) { + + $payment->subtotal = edd_sanitize_amount( $row[ $this->field_mapping['subtotal'] ] ); + + } else { + + $payment->subtotal = $payment->total - $payment->tax; + + } + + $address = array( 'line1' => '', 'line2' => '', 'city' => '', 'state' => '', 'zip' => '', 'country' => '' ); + + foreach( $address as $key => $address_field ) { + + if( ! empty( $this->field_mapping[ $key ] ) && ! empty( $row[ $this->field_mapping[ $key ] ] ) ) { + + $address[ $key ] = sanitize_text_field( $row[ $this->field_mapping[ $key ] ] ); + + } + + } + + $payment->address = $address; + + $payment->save(); + + + // The status has to be set after payment is created to ensure status update properly + if( ! empty( $this->field_mapping['status'] ) && ! empty( $row[ $this->field_mapping['status'] ] ) ) { + + $payment->status = strtolower( sanitize_text_field( $row[ $this->field_mapping['status'] ] ) ); + + } else { + + $payment->status = 'complete'; + + } + + // Save a second time to update stats + $payment->save(); + + } + + private function set_customer( $row ) { + + global $wpdb; + $customer = false; + + $customer = false; + $email = ''; + + if( ! empty( $this->field_mapping['email'] ) && ! empty( $row[ $this->field_mapping['email'] ] ) ) { + + $email = sanitize_text_field( $row[ $this->field_mapping['email'] ] ); + + } + + // Look for a customer from the canonical source, if any + if( ! empty( $this->field_mapping['edd_customer_id'] ) && ! empty( $row[ $this->field_mapping['edd_customer_id'] ] ) ) { + + $canonical_id = absint( $row[ $this->field_mapping['edd_customer_id'] ] ); + $mapped_id = $wpdb->get_var( $wpdb->prepare( "SELECT edd_customer_id FROM $wpdb->edd_customermeta WHERE meta_key = '_canonical_import_id' AND meta_value = %d LIMIT 1", $canonical_id ) ); + + } + + if( ! empty( $mapped_id ) ) { + + $customer = new EDD_Customer( $mapped_id ); + + } + + if( empty( $mapped_id ) || ! $customer->id > 0 ) { + + // Look for a customer based on provided ID, if any + if ( ! empty( $this->field_mapping['edd_customer_id'] ) && ! empty( $row[ $this->field_mapping['edd_customer_id'] ] ) ) { + + $customer_id = absint( $row[ $this->field_mapping['edd_customer_id'] ] ); + + $customer_by_id = new EDD_Customer( $customer_id ); + + } + + // Now look for a customer based on provided email + + if( ! empty( $email ) ) { + + $customer_by_email = new EDD_Customer( $email ); + + } + + // Now compare customer records. If they don't match, customer_id will be stored in meta and we will use the customer that matches the email + + if ( ! empty( $customer_by_email ) && ( empty( $customer_by_id ) || $customer_by_id->id !== $customer_by_email->id ) ) { + + $customer = $customer_by_email; + + } elseif ( ! empty( $customer_by_id ) ) { + + $customer = $customer_by_id; + + if( ! empty( $email ) ) { + $customer->add_email( $email ); + } + + } + + // Make sure we found a customer. Create one if not. + if ( empty( $customer->id ) ) { + + if ( ! $customer instanceof EDD_Customer ) { + $customer = new EDD_Customer(); + } + } + + if ( ! empty( $this->field_mapping['name'] ) && ! empty( $row[ $this->field_mapping['name'] ] ) ) { + + $name = $row[ $this->field_mapping['name'] ]; + + } else { + $first_name = ''; + $last_name = ''; + if ( ! empty( $this->field_mapping['first_name'] ) && ! empty( $row[ $this->field_mapping['first_name'] ] ) ) { + + $first_name = $row[ $this->field_mapping['first_name'] ]; + + } + + if ( ! empty( $this->field_mapping['last_name'] ) && ! empty( $row[ $this->field_mapping['last_name'] ] ) ) { + + $last_name = $row[ $this->field_mapping['last_name'] ]; + + } + $name = $first_name . ' ' . $last_name; + } + + $customer->create( + array( + 'name' => sanitize_text_field( $name ), + 'email' => empty( $email ) ? '' : $email, + ) + ); + + if( ! empty( $canonical_id ) && (int) $canonical_id !== (int) $customer->id ) { + $customer->update_meta( '_canonical_import_id', $canonical_id ); + } + } + + if ( ! empty( $email ) && $email !== $customer->email ) { + $customer->add_email( $email ); + } + + return $customer->id; + + } + + /** + * Look up Download by title and create one if none is found + * + * @since 2.6 + * @return int|false Download ID or false if the download could not be created. + */ + private function maybe_create_download( $title = '' ) { + + if ( ! is_string( $title ) ) { + return false; + } + + $download = new WP_Query( + array( + 'post_type' => 'download', + 'title' => $title, + 'post_status' => 'all', + 'posts_per_page' => 1, + 'no_found_rows' => true, + 'ignore_sticky_posts' => true, + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + 'orderby' => 'post_date ID', + 'order' => 'ASC', + ) + ); + + if ( ! empty( $download->post ) ) { + return $download->post->ID; + } + + $args = array( + 'post_type' => 'download', + 'post_title' => $title, + 'post_author' => get_current_user_id(), + ); + + return wp_insert_post( $args ); + } + + /** + * Return the calculated completion percentage + * + * @since 2.6 + * @return int + */ + public function get_downloads_from_edd( $data_str ) { + + // Break string into separate products + + $d_array = array(); + $downloads = (array) explode( '/', $data_str ); + + if( $downloads ) { + + foreach( $downloads as $key => $download ) { + + $d = (array) explode( '|', $download ); + if ( ! array_key_exists( 1, $d ) ) { + continue; + } + preg_match_all( '/\{(\d|(\d+(\.\d+|\d+)))\}/', $d[1], $matches ); + + if( false !== strpos( $d[1], '{' ) ) { + + $price = trim( substr( $d[1], 0, strpos( $d[1], '{' ) ) ); + + } else { + + $price = trim( $d[1] ); + } + + $price = floatval( $price ); + $tax = isset( $matches[1][0] ) ? floatval( trim( $matches[1][0] ) ) : 0; + $price_id = isset( $matches[1][1] ) ? trim( $matches[1][1] ) : false; + + $d_array[] = array( + 'download' => trim( $d[0] ), + 'price' => $price - $tax, + 'tax' => $tax, + 'price_id' => $price_id, + ); + + } + + } + + return $d_array; + + } + + /** + * Return the calculated completion percentage + * + * @since 2.6 + * @return int + */ + public function get_percentage_complete() { + + $percentage = 0; + $total = count( $this->csv ); + + if ( $total > 0 ) { + $percentage = ( $this->step * $this->per_step / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Retrieve the URL to the payments list table + * + * @since 2.6 + * @return string + */ + public function get_list_table_url() { + return admin_url( 'edit.php?post_type=download&page=edd-payment-history' ); + } + + /** + * Retrieve the payments labels + * + * @since 2.6 + * @return string + */ + public function get_import_type_label() { + return __( 'payments', 'easy-digital-downloads' ); + } +} diff --git a/includes/admin/import/class-batch-import.php b/includes/admin/import/class-batch-import.php new file mode 100644 index 00000000000..2f61acbc067 --- /dev/null +++ b/includes/admin/import/class-batch-import.php @@ -0,0 +1,355 @@ + database fields + * + * @since 2.6 + */ + public $field_mapping = array(); + + /** + * Get things started + * + * @param $_step int The step to process + * @since 2.6 + */ + public function __construct( $_file = '', $_step = 1 ) { + + $this->step = $_step; + $this->done = false; + if ( ! empty( $_file ) ) { + $this->set_up_csv( $_file ); + } + } + + /** + * Sets up the CSV file for importing. + * + * @param [type] $file + * @return void + */ + public function set_up_csv( $file ) { + $this->csv = $this->get_csv_file( $file ); + $this->total = count( $this->csv ); + $this->init(); + } + + /** + * Initialize the updater. Runs after import file is loaded but before any processing is done. + * + * @since 2.6 + * @return void + */ + public function init() {} + + /** + * Can we import? + * + * @since 2.6 + * @return bool Whether we can iport or not + */ + public function can_import() { + return (bool) apply_filters( 'edd_import_capability', current_user_can( $this->capability_type ) ); + } + + /** + * Parses the CSV from the file and returns the data as an array. + * + * @since 2.11.5 + * @param string $file + * + * @return array + */ + public function get_csv_file( $file ) { + $this->file = $file; + $csv = array_map( 'str_getcsv', file( $file ) ); + array_walk( + $csv, + function ( &$a ) use ( $csv ) { + /* + * Make sure the two arrays have the same lengths. + * If not, we trim the larger array to match the smaller one. + */ + $min = min( count( $csv[0] ), count( $a ) ); + $headers = array_slice( $csv[0], 0, $min ); + $values = array_slice( $a, 0, $min ); + $a = array_combine( $headers, $values ); + } + ); + array_shift( $csv ); + + return $csv; + } + + /** + * Get the CSV columns + * + * @since 2.6 + * @return array The columns in the CSV + */ + public function get_columns() { + + $columns = array(); + + if ( isset( $this->csv[0] ) && is_array( $this->csv[0] ) ) { + $columns = array_keys( $this->csv[0] ); + } + + return $columns; + } + + /** + * Get the first row of the CSV + * + * This is used for showing an example of what the import will look like + * + * @since 2.6 + * @return array The first row after the header of the CSV + */ + public function get_first_row() { + + if ( ! is_array( $this->csv ) ) { + return array(); + } + + return array_map( array( $this, 'trim_preview' ), current( $this->csv ) ); + } + + /** + * Process a step + * + * @since 2.6 + * @return bool + */ + public function process_step() { + + $more = false; + + if ( ! $this->can_import() ) { + wp_die( __( 'You do not have permission to import data.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + return $more; + } + + /** + * Return the calculated completion percentage + * + * @since 2.6 + * @return int + */ + public function get_percentage_complete() { + return 100; + } + + /** + * Map CSV columns to import fields + * + * @since 2.6 + * @return void + */ + public function map_fields( $import_fields = array() ) { + + $this->field_mapping = array_map( 'sanitize_text_field', $import_fields ); + + } + + /** + * Retrieve the URL to the list table for the import data type + * + * @since 2.6 + * @return string + */ + public function get_list_table_url() {} + + /** + * Retrieve the label for the import type. Example: Payments + * + * @since 2.6 + * @return string + */ + public function get_import_type_label() {} + + /** + * Convert a string containing delimiters to an array + * + * @since 2.6 + * @param $str Input string to convert to an array + * @return array + */ + public function str_to_array( $str = '' ) { + + $array = array(); + + if( is_array( $str ) ) { + return array_map( 'trim', $str ); + } + + // Look for standard delimiters + if( false !== strpos( $str, '|' ) ) { + + $delimiter = '|'; + + } elseif( false !== strpos( $str, ',' ) ) { + + $delimiter = ','; + + } elseif( false !== strpos( $str, ';' ) ) { + + $delimiter = ';'; + + } elseif( false !== strpos( $str, '/' ) && ! filter_var( str_replace( ' ', '%20', $str ), FILTER_VALIDATE_URL ) && '/' !== substr( $str, 0, 1 ) ) { + + $delimiter = '/'; + + } + + if( ! empty( $delimiter ) ) { + + $array = (array) explode( $delimiter, $str ); + + } else { + + $array[] = $str; + } + + return array_map( 'trim', $array ); + + } + + /** + * Convert a files string containing delimiters to an array. + * + * This is identical to str_to_array() except it ignores all / characters. + * + * @since 2.9.20 + * @param $str Input string to convert to an array + * @return array + */ + public function convert_file_string_to_array( $str = '' ) { + + $array = array(); + + if( is_array( $str ) ) { + return array_map( 'trim', $str ); + } + + // Look for standard delimiters + if( false !== strpos( $str, '|' ) ) { + + $delimiter = '|'; + + } elseif( false !== strpos( $str, ',' ) ) { + + $delimiter = ','; + + } elseif( false !== strpos( $str, ';' ) ) { + + $delimiter = ';'; + + } + + if( ! empty( $delimiter ) ) { + + $array = (array) explode( $delimiter, $str ); + + } else { + + $array[] = $str; + } + + return array_map( 'trim', $array ); + + } + + /** + * Trims a column value for preview + * + * @since 2.6 + * @param $str Input string to trim down + * @return string + */ + public function trim_preview( $str = '' ) { + + if( ! is_numeric( $str ) ) { + + $long = strlen( $str ) >= 30; + $str = substr( $str, 0, 30 ); + $str = $long ? $str . '...' : $str; + + } + + return $str; + + } +} diff --git a/includes/admin/import/import-actions.php b/includes/admin/import/import-actions.php new file mode 100644 index 00000000000..f42d990a310 --- /dev/null +++ b/includes/admin/import/import-actions.php @@ -0,0 +1,75 @@ + __( 'Nonce verification failed', 'easy-digital-downloads' ) ) ); + } + + if ( empty( $_POST['edd-import-class'] ) ) { + wp_send_json_error( array( 'error' => __( 'Missing import parameters. Import class must be specified.', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + if ( ! function_exists( 'wp_handle_upload' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + require_once EDD_PLUGIN_DIR . 'includes/admin/import/class-batch-import.php'; + + $importer_class = sanitize_text_field( $_POST['edd-import-class'] ); + $is_class_allowed = edd_importer_is_class_allowed( $importer_class ); + if ( false === $is_class_allowed ) { + wp_send_json_error( array( 'error' => __( 'Invalid importer class supplied', 'easy-digital-downloads' ) ) ); + } + + do_action( 'edd_batch_import_class_include', $importer_class ); + + $import = new $importer_class(); + + // The import class checks for the user's capability. + if ( ! $import->can_import() ) { + wp_send_json_error( array( 'error' => __( 'You do not have permission to import data', 'easy-digital-downloads' ) ) ); + } + + if ( empty( $_FILES['edd-import-file'] ) ) { + wp_send_json_error( array( 'error' => __( 'Missing import file. Please provide an import file.', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + if ( empty( $_FILES['edd-import-file']['type'] ) || ! in_array( strtolower( $_FILES['edd-import-file']['type'] ), edd_importer_accepted_mime_types(), true ) ) { + wp_send_json_error( array( 'error' => __( 'The file you uploaded does not appear to be a CSV file.', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + if ( ! file_exists( $_FILES['edd-import-file']['tmp_name'] ) ) { + wp_send_json_error( array( 'error' => __( 'Something went wrong during the upload process, please try again.', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + // Let WordPress import the file. We will remove it after import is complete + $import_file = wp_handle_upload( $_FILES['edd-import-file'], array( 'test_form' => false ) ); + + if ( $import_file && empty( $import_file['error'] ) ) { + + $import->set_up_csv( $import_file['file'] ); + wp_send_json_success( array( + 'form' => $_POST, + 'class' => $importer_class, + 'upload' => $import_file, + 'first_row' => $import->get_first_row(), + 'columns' => $import->get_columns(), + 'nonce' => wp_create_nonce( 'edd_ajax_import', 'edd_ajax_import' ) + ) ); + + } else { + + /** + * Error generated by _wp_handle_upload() + * @see _wp_handle_upload() in wp-admin/includes/file.php + */ + + wp_send_json_error( array( 'error' => $import_file['error'] ) ); + } + + exit; + +} +add_action( 'edd_upload_import_file', 'edd_do_ajax_import_file_upload' ); + +/** + * Process batch imports via ajax + * + * @since 2.6 + * @return void + */ +function edd_do_ajax_import() { + + require_once EDD_PLUGIN_DIR . 'includes/admin/import/class-batch-import.php'; + + if( ! wp_verify_nonce( $_REQUEST['nonce'], 'edd_ajax_import' ) ) { + wp_send_json_error( array( 'error' => __( 'Nonce verification failed', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + if ( empty( $_REQUEST['class'] ) ) { + wp_send_json_error( array( 'error' => __( 'Missing import parameters. Import class must be specified.', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + if ( ! file_exists( $_REQUEST['upload']['file'] ) ) { + wp_send_json_error( array( 'error' => __( 'Something went wrong during the upload process, please try again.', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + $file = sanitize_text_field( $_REQUEST['upload']['file'] ); + + $mime_type_allowed = false; + if ( is_callable( 'mime_content_type' ) ) { + if ( in_array( mime_content_type( $file ), edd_importer_accepted_mime_types(), true ) ) { + $mime_type_allowed = true; + } + } else { + if ( wp_check_filetype( $file, edd_importer_accepted_mime_types() ) ) { + $mime_type_allowed = true; + } + } + + if ( false === $mime_type_allowed ) { + wp_send_json_error( array( 'error' => __( 'The file you uploaded does not appear to be a CSV file.', 'easy-digital-downloads' ), 'request' => $_REQUEST ) ); + } + + $importer_class = sanitize_text_field( $_REQUEST['class'] ); + $is_class_allowed = edd_importer_is_class_allowed( $importer_class ); + + if ( ! $is_class_allowed ) { + wp_send_json_error( array( 'error' => __( 'Invalid importer class supplied', 'easy-digital-downloads' ) ) ); + } + + do_action( 'edd_batch_import_class_include', $importer_class ); + + $step = absint( $_REQUEST['step'] ); + $class = $importer_class; + $import = new $class( '', $step ); + + if ( ! $import->can_import() ) { + wp_send_json_error( array( 'error' => __( 'You do not have permission to import data', 'easy-digital-downloads' ) ) ); + } + $import->set_up_csv( $file ); + + parse_str( $_REQUEST['mapping'], $map ); + + $import->map_fields( $map['edd-import-field'] ); + + $ret = $import->process_step( $step ); + + $percentage = $import->get_percentage_complete(); + + if( $ret ) { + + $step += 1; + wp_send_json_success( array( + 'step' => $step, + 'percentage' => $percentage, + 'columns' => $import->get_columns(), + 'mapping' => $import->field_mapping, + 'total' => $import->total + ) ); + + } elseif ( true === $import->is_empty ) { + + wp_send_json_error( array( + 'error' => __( 'No data found for import parameters', 'easy-digital-downloads' ) + ) ); + + } else { + + wp_send_json_success( array( + 'step' => 'done', + 'message' => sprintf( + __( 'Import complete! View imported %s.', 'easy-digital-downloads' ), + esc_url( $import->get_list_table_url() ), + esc_html( $import->get_import_type_label() ) + ) + ) ); + + } +} +add_action( 'wp_ajax_edd_do_ajax_import', 'edd_do_ajax_import' ); + +/** + * Returns the array of accepted mime types for the importer. + * + * @since 3.0 + * @return array + */ +function edd_importer_accepted_mime_types() { + return array( + 'text/csv', + 'text/comma-separated-values', + 'text/plain', + 'text/anytext', + 'text/*', + 'text/plain', + 'text/anytext', + 'text/*', + 'application/csv', + 'application/excel', + 'application/vnd.ms-excel', + 'application/vnd.msexcel', + ); +} + +/** + * Given an importer class name, is it allowed to process as an importer. + * + * @since 3.0.2 + * + * @param string $class The class name to check. + * + * @return bool If the class is allowed to be used as an importer. + */ +function edd_importer_is_class_allowed( $class = '' ) { + $allowed_importer_classes = edd_get_importer_accepted_classes(); + return in_array( $class, $allowed_importer_classes, true ); +} + +/** + * Returns a list of allowed importer classes. + * + * @since 3.0.2 + * + * @return array An array of class names to allow during imports. + */ +function edd_get_importer_accepted_classes() { + return apply_filters( + 'edd_accepted_importer_classes', + array( + 'EDD_Batch_Downloads_Import', + 'EDD_Batch_Payments_Import', + ) + ); +} diff --git a/includes/admin/notes/note-actions.php b/includes/admin/notes/note-actions.php new file mode 100644 index 00000000000..2e1a072a15a --- /dev/null +++ b/includes/admin/notes/note-actions.php @@ -0,0 +1,132 @@ + $object_id, + 'object_type' => $object_type, + 'content' => $note, + 'user_id' => get_current_user_id() + ) ); + + $x = new WP_Ajax_Response(); + $x->add( + array( + 'what' => 'edd_note_html', + 'data' => edd_admin_get_note_html( $note_id, $object_id ), + ) + ); + $x->send(); +} +add_action( 'wp_ajax_edd_add_note', 'edd_admin_ajax_add_note' ); + +/** + * Delete a note. + * + * @since 3.0 + * + * @param array $data Data from $_GET. + */ +function edd_admin_delete_note( $data = array() ) { + + // Bail if missing any data + if ( empty( $data['_wpnonce'] ) || empty( $data['note_id'] ) ) { + return; + } + + // Bail if nonce fails + if ( ! wp_verify_nonce( $data['_wpnonce'], 'edd_delete_note_' . $data['note_id'] ) ) { + return; + } + + // Try to delete + edd_delete_note( $data['note_id'] ); + + edd_redirect( edd_get_note_delete_redirect_url() ); +} +add_action( 'edd_delete_note', 'edd_admin_delete_note' ); + +/** + * Delete a discount note via AJAX. + * + * @since 3.0 + */ +function edd_admin_ajax_delete_note() { + + // Check AJAX referrer + check_ajax_referer( 'edd_note', 'nonce' ); + + // Bail if user cannot delete notes + if ( ! current_user_can( 'manage_shop_settings' ) ) { + wp_die( -1 ); + } + + // Get note ID + $note_id = ! empty( $_POST['note_id'] ) + ? absint( $_POST['note_id'] ) + : 0; + + // Bail if no note + if ( empty( $note_id ) ) { + wp_die( -1 ); + } + + // Delete note + if ( edd_delete_note( $note_id ) ) { + wp_die( 1 ); + } + + wp_die( 0 ); +} +add_action( 'wp_ajax_edd_delete_note', 'edd_admin_ajax_delete_note' ); diff --git a/includes/admin/notes/note-functions.php b/includes/admin/notes/note-functions.php new file mode 100644 index 00000000000..9b2b2c82938 --- /dev/null +++ b/includes/admin/notes/note-functions.php @@ -0,0 +1,223 @@ + + +
    + + +

    > + +

    +
    + + user_id; + $author = edd_get_bot_name(); + if ( ! empty( $user_id ) ) { + /* translators: user ID */ + $author = sprintf( __( 'User ID #%s', 'easy-digital-downloads' ), $user_id ); + $user_object = get_userdata( $user_id ); + if ( $user_object ) { + $author = ! empty( $user_object->display_name ) ? $user_object->display_name : $user_object->user_login; + } + } + + // URL to delete note + $delete_note_url = wp_nonce_url( add_query_arg( array( + 'edd-action' => 'delete_note', + 'note_id' => absint( $note->id ), + ) ), 'edd_delete_note_' . absint( $note->id ) ); + + // Start a buffer + ob_start(); + ?> + +
    +
    + + + + + +
    + + content ) ); ?> +
    + + + +
    +
    + + +
    + +
    +
    + +
    + + +
    + + +
    + + 0, + 'pag_arg' => 'paged', + 'base' => '%_%', + 'show_all' => true, + 'prev_text' => is_rtl() ? '→' : '←', + 'next_text' => is_rtl() ? '←' : '→', + 'add_fragment' => '' + ) ); + + // Maximum notes per page + $per_page = apply_filters( 'edd_notes_per_page', 20 ); + $r['total'] = ceil( $r['total'] / $per_page ); + $r['format'] = "?{$r['pag_arg']}=%#%"; + + // Don't allow pagination beyond the boundaries + $r['current'] = ! empty( $_GET[ $r['pag_arg'] ] ) && is_numeric( $_GET[ $r['pag_arg'] ] ) + ? absint( $_GET[ $r['pag_arg'] ] ) + : 1; + + // Start a buffer + ob_start(); ?> + +
    + +
    + + 'edd-payment-history', + 'view' => 'view-order-details', + 'id' => absint( $request['id'] ) + ) ) ); + } +} +add_action( 'edd_handle_order_item_change', 'edd_handle_order_item_change' ); + +/** + * Process the changes from the `View Order Details` page. + * + * @since 1.9 + * @since 3.0 Refactored to use new core objects and query methods. + * + * @param array $data Order data. +*/ +function edd_update_payment_details( $data = array() ) { + + // Bail if an empty array is passed. + if ( empty( $data ) ) { + wp_die( esc_html__( 'You do not have permission to edit this payment record', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Bail if the user does not have the correct permissions. + if ( ! current_user_can( 'edit_shop_payments', $data['edd_payment_id'] ) ) { + wp_die( esc_html__( 'You do not have permission to edit this payment record', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + check_admin_referer( 'edd_update_payment_details_nonce' ); + + // Retrieve the order ID and set up the order. + $order_id = absint( $data['edd_payment_id'] ); + $order = edd_get_order( $order_id ); + + $order_update_args = array(); + + $unlimited = isset( $data['edd-unlimited-downloads'] ) ? '1' : null; + $new_status = sanitize_key( $data['edd-payment-status'] ); + $date_string = EDD()->utils->get_date_string( + sanitize_text_field( $data['edd-payment-date'] ), + sanitize_text_field( $data['edd-payment-time-hour'] ), + sanitize_text_field( $data['edd-payment-time-min'] ) + ); + + // The date is entered in the WP timezone. We need to convert it to UTC prior to saving now. + $order_update_args['date_created'] = edd_get_utc_date_string( $date_string ); + + // Customer + $curr_customer_id = sanitize_text_field( $data['current-customer-id'] ); + $new_customer_id = sanitize_text_field( $data['customer-id'] ); + + // Create a new customer. + if ( isset( $data['edd-new-customer'] ) && 1 === (int) $data['edd-new-customer'] ) { + $email = isset( $data['edd-new-customer-email'] ) + ? sanitize_text_field( $data['edd-new-customer-email'] ) + : ''; + + // Sanitize first name + $first_name = isset( $data['edd-new-customer-first-name'] ) + ? sanitize_text_field( $data['edd-new-customer-first-name'] ) + : ''; + + // Sanitize last name + $last_name = isset( $data['edd-new-customer-last-name'] ) + ? sanitize_text_field( $data['edd-new-customer-last-name'] ) + : ''; + + // Combine + $name = $first_name . ' ' . $last_name; + + if ( empty( $email ) || empty( $name ) ) { + wp_die( esc_html__( 'New Customers require a name and email address', 'easy-digital-downloads' ) ); + } + + $customer = new EDD_Customer( $email ); + + if ( empty( $customer->id ) ) { + $customer_data = array( + 'name' => $name, + 'email' => $email, + ); + + $user_id = email_exists( $email ); + + if ( false !== $user_id ) { + $customer_data['user_id'] = $user_id; + } + + if ( ! $customer->create( $customer_data ) ) { + $customer = new EDD_Customer( $curr_customer_id ); + edd_set_error( 'edd-payment-new-customer-fail', __( 'Error creating new customer', 'easy-digital-downloads' ) ); + } + } else { + wp_die( sprintf( __( 'A customer with the email address %s already exists. Please go back and assign this payment to them.', 'easy-digital-downloads' ), $email ) ); + } + + $previous_customer = new EDD_Customer( $curr_customer_id ); + } elseif ( $curr_customer_id !== $new_customer_id ) { + $customer = new EDD_Customer( $new_customer_id ); + + $previous_customer = new EDD_Customer( $curr_customer_id ); + } else { + $customer = new EDD_Customer( $curr_customer_id ); + } + + // Remove the stats and payment from the previous customer and attach it to the new customer + if ( isset( $previous_customer ) ) { + $previous_customer->remove_payment( $order_id, false ); + $customer->attach_payment( $order_id, false ); + + $order_update_args['customer_id'] = $customer->id; + } + + $order_update_args['user_id'] = $customer->user_id; + $order_update_args['email'] = $customer->email; + + // Address. + $address = $data['edd_order_address']; + + $order_address_id = absint( $address['address_id'] ); + $order_address_details = array( + 'name' => $customer->name, + 'address' => $address['address'], + 'address2' => $address['address2'], + 'city' => $address['city'], + 'region' => $address['region'], + 'postal_code' => $address['postal_code'], + 'country' => $address['country'], + ); + + if ( empty( $order_address_id ) ) { + + // Unset the address_id which is 0. + unset( $address['address_id'] ); + + // Add the $order_id to the arguments to create this order address. + $order_address_details['order_id'] = $order_id; + + edd_add_order_address( $order_address_details ); + } else { + edd_update_order_address( $order_address_id, $order_address_details ); + } + + // Unlimited downloads. + if ( 1 === (int) $unlimited ) { + edd_update_order_meta( $order_id, 'unlimited_downloads', $unlimited ); + } else { + edd_delete_order_meta( $order_id, 'unlimited_downloads' ); + } + + // Don't set the status in the update call (for back compat) + unset( $order_update_args['status'] ); + + // Attempt to update the order + $updated = edd_update_order( $order_id, $order_update_args ); + + // Bail if an error occurred + if ( false === $updated ) { + wp_die( __( 'Error updating order.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 400 ) ); + } + + // Check if the status has changed, if so, we need to invoke the pertinent + // status processing method (for back compat) + if ( $new_status !== $order->status ) { + edd_update_order_status( $order_id, $new_status ); + } + + do_action( 'edd_updated_edited_purchase', $order_id ); + + edd_redirect( edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + 'edd-message' => 'payment-updated', + 'id' => absint( $order_id ), + ) ) ); +} +add_action( 'edd_update_payment_details', 'edd_update_payment_details' ); + +/** + * New in 3.0, permanently destroys an order, and all its data, and related data. + * + * @since 3.0 + * + * @param array $data Arguments passed. + */ +function edd_trigger_destroy_order( $data ) { + + if ( wp_verify_nonce( $data['_wpnonce'], 'edd_payment_nonce' ) ) { + + $payment_id = absint( $data['purchase_id'] ); + + if ( ! current_user_can( 'delete_shop_payments', $payment_id ) ) { + wp_die( esc_html__( 'You do not have permission to edit this order.', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + edd_destroy_order( $payment_id ); + + $redirect_link = add_query_arg( + array( + 'page' => 'edd-payment-history', + 'edd-message' => 'payment_deleted', + 'status' => 'trash', + ), + edd_get_admin_base_url() + ); + edd_redirect( $redirect_link ); + } +} +add_action( 'edd_delete_order', 'edd_trigger_destroy_order' ); + +/** + * Trigger the action of moving an order to the 'trash' status + * + * @since 3.0 + * + * @param $data + * @return void + */ +function edd_trigger_trash_order( $data ) { + if ( wp_verify_nonce( $data['_wpnonce'], 'edd_payment_nonce' ) ) { + + $payment_id = absint( $data['purchase_id'] ); + + if ( ! current_user_can( 'delete_shop_payments', $payment_id ) ) { + wp_die( __( 'You do not have permission to edit this payment record', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + edd_trash_order( $payment_id ); + + $redirect = edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'edd-message' => 'order_trashed', + 'order_type' => esc_attr( $data['order_type'] ), + ) ); + + edd_redirect( $redirect ); + } +} +add_action( 'edd_trash_order', 'edd_trigger_trash_order' ); + +/** + * Trigger the action of restoring an order from the 'trash' status + * + * @since 3.0 + * + * @param $data + * @return void + */ +function edd_trigger_restore_order( $data ) { + if ( wp_verify_nonce( $data['_wpnonce'], 'edd_payment_nonce' ) ) { + + $payment_id = absint( $data['purchase_id'] ); + + if ( ! current_user_can( 'delete_shop_payments', $payment_id ) ) { + wp_die( __( 'You do not have permission to edit this payment record', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + edd_restore_order( $payment_id ); + + $redirect = edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'edd-message' => 'order_restored', + 'order_type' => esc_attr( $data['order_type'] ), + ) ); + + edd_redirect( $redirect ); + } +} +add_action( 'edd_restore_order', 'edd_trigger_restore_order' ); + +/** + * Retrieves a new download link for a purchased file + * + * @since 2.0 + * @return string +*/ +function edd_ajax_generate_file_download_link() { + + $customer_view_role = apply_filters( 'edd_view_customers_role', 'view_shop_reports' ); + if ( ! current_user_can( $customer_view_role ) ) { + die( '-1' ); + } + + $payment_id = absint( $_POST['payment_id'] ); + $download_id = absint( $_POST['download_id'] ); + $price_id = isset( $_POST['price_id'] ) && is_numeric( $_POST['price_id'] ) ? absint( $_POST['price_id'] ) : null; + + if ( empty( $payment_id ) ) { + die( '-2' ); + } + + if ( empty( $download_id ) ) { + die( '-3' ); + } + + $limit = edd_get_file_download_limit( $download_id ); + if ( ! empty( $limit ) ) { + // Increase the file download limit when generating new links + edd_set_file_download_limit_override( $download_id, $payment_id ); + } + + $files = edd_get_download_files( $download_id, $price_id ); + if ( ! $files ) { + die( '-4' ); + } + + $order = edd_get_order( $payment_id ); + $file_urls = ''; + foreach ( $files as $file_key => $file ) { + $file_urls .= edd_get_download_file_url( $order, $order->email, $file_key, $download_id, $price_id ); + $file_urls .= "\n\n"; + } + + die( $file_urls ); +} +add_action( 'wp_ajax_edd_get_file_download_link', 'edd_ajax_generate_file_download_link' ); + +/** + * Renders the refund form that is used to process a refund. + * + * @since 3.0 + * + * @return void + */ +function edd_ajax_generate_refund_form() { + + // Verify we have a logged user. + if ( ! is_user_logged_in() ) { + $return = array( + 'success' => false, + 'message' => __( 'You must be logged in to perform this action.', 'easy-digital-downloads' ), + ); + + wp_send_json( $return, 401 ); + } + + // Verify the logged in user has permission to edit shop payments. + if ( ! current_user_can( 'edit_shop_payments' ) ) { + $return = array( + 'success' => false, + 'message' => __( 'Your account does not have permission to perform this action.', 'easy-digital-downloads' ), + ); + + wp_send_json( $return, 401 ); + } + + $order_id = isset( $_POST['order_id'] ) && is_numeric( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : false; + + if ( empty( $order_id ) ) { + $return = array( + 'success' => false, + 'message' => __( 'Invalid order ID', 'easy-digital-downloads' ), + ); + + wp_send_json( $return, 400 ); + } + + $order = edd_get_order( $order_id ); + if ( empty( $order ) ) { + $return = array( + 'success' => false, + 'message' => __( 'Invalid order', 'easy-digital-downloads' ), + ); + + wp_send_json( $return, 404 ); + } + + if ( 'refunded' === $order->status ) { + $return = array( + 'success' => false, + 'message' => __( 'Order is already refunded', 'easy-digital-downloads' ), + ); + + wp_send_json( $return, 404 ); + } + + if ( 'refund' === $order->type ) { + $return = array( + 'success' => false, + 'message' => __( 'Cannot refund an order that is already refunded.', 'easy-digital-downloads' ), + ); + + wp_send_json( $return, 404 ); + } + + // Output buffer the form before we include it in the JSON response. + ob_start(); + ?> + +
    + prepare_items(); + $refund_items->display(); + ?> +
    + true, + 'html' => $html, + ); + + wp_send_json( $return, 200 ); + +} +add_action( 'wp_ajax_edd_generate_refund_form', 'edd_ajax_generate_refund_form' ); + +/** + * Processes the results from the Submit Refund form + * + * @since 3.0 + * @return void + */ +function edd_ajax_process_refund_form() { + + // Verify we have a logged user. + if ( ! is_user_logged_in() ) { + wp_send_json_error( __( 'You must be logged in to perform this action.', 'easy-digital-downloads' ), 401 ); + } + + // Verify the logged in user has permission to edit shop payments. + if ( ! current_user_can( 'edit_shop_payments' ) ) { + wp_send_json_error( __( 'Your account does not have permission to perform this action.', 'easy-digital-downloads' ), 401 ); + } + + if ( empty( $_POST['data'] ) || empty( $_POST['order_id'] ) ) { + wp_send_json_error( __( 'Missing form data or order ID.', 'easy-digital-downloads' ), 400 ); + } + + // Get our data out of the serialized string. + parse_str( $_POST['data'], $form_data ); + + // Verify the nonce. + $nonce = ! empty( $form_data['edd_process_refund'] ) ? sanitize_text_field( $form_data['edd_process_refund'] ) : false; + if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'edd_process_refund' ) ) { + wp_send_json_error( __( 'Nonce validation failed when submitting refund.', 'easy-digital-downloads' ), 401 ); + } + + // Collect selected order items. + $order_items = array(); + if ( ! empty( $form_data['refund_order_item'] ) && is_array( $form_data['refund_order_item'] ) ) { + foreach( $form_data['refund_order_item'] as $order_item_id => $order_item ) { + // If there's no quantity or subtotal - bail. + if ( empty( $order_item['quantity'] ) || empty( $order_item['subtotal'] ) ) { + continue; + } + + $order_items[] = array( + 'order_item_id' => absint( $order_item_id ), + 'quantity' => intval( $order_item['quantity'] ), + 'subtotal' => edd_sanitize_amount( $order_item['subtotal'] ), + 'tax' => ! empty( $order_item['tax'] ) ? edd_sanitize_amount( $order_item['tax'] ) : 0.00 + ); + } + } + + // Collect selected adjustments. + $adjustments = array(); + if ( ! empty( $form_data['refund_order_adjustment'] ) && is_array( $form_data['refund_order_adjustment'] ) ) { + foreach( $form_data['refund_order_adjustment'] as $adjustment_id => $adjustment ) { + // If there's no quantity or subtotal - bail. + if ( empty( $adjustment['quantity'] ) || empty( $adjustment['subtotal'] ) ) { + continue; + } + + $adjustments[] = array( + 'adjustment_id' => absint( $adjustment_id ), + 'quantity' => intval( $adjustment['quantity'] ), + 'subtotal' => floatval( edd_sanitize_amount( $adjustment['subtotal'] ) ), + 'tax' => ! empty( $adjustment['tax'] ) ? floatval( edd_sanitize_amount( $adjustment['tax'] ) ) : 0.00 + ); + } + } + + $order_id = absint( $_POST['order_id'] ); + $refund_id = edd_refund_order( $order_id, $order_items, $adjustments ); + + if ( is_wp_error( $refund_id ) ) { + wp_send_json_error( $refund_id->get_error_message() ); + } elseif ( ! empty( $refund_id ) ) { + $return = array( + 'refund_id' => $refund_id, + 'message' => sprintf( __( 'Refund successfully processed.', 'easy-digital-downloads' ) ), + 'refund_url' => edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'view' => 'view-refund-details', + 'id' => urlencode( $refund_id ), + ) + ) + ); + wp_send_json_success( $return, 200 ); + } else { + wp_send_json_error( __( 'Unable to process refund.', 'easy-digital-downloads' ), 401 ); + } +} +add_action( 'wp_ajax_edd_process_refund_form', 'edd_ajax_process_refund_form' ); + +/** + * Process Orders list table bulk actions. This is necessary because we need to + * redirect to ensure filters do not get applied when bulk actions are being + * processed. This processing cannot happen within the `EDD_Payment_History_Table` + * class as at that point, it is too late to do a redirect. + * + * @since 3.0 + */ +function edd_orders_list_table_process_bulk_actions() { + + // Bail if this method was called directly. + if ( 'load-download_page_edd-payment-history' !== current_action() ) { + _doing_it_wrong( __FUNCTION__, 'This method is not meant to be called directly.', 'EDD 3.0' ); + } + + $action = isset( $_REQUEST['action'] ) // WPCS: CSRF ok. + ? sanitize_text_field( $_REQUEST['action'] ) + : ''; + + // If this is a 'delete' action, the capability changes from edit to delete. + $cap = 'delete' === $action ? 'delete_shop_payments' : 'edit_shop_payments'; + + // Check the current user's capability. + if ( ! current_user_can( $cap ) ) { + return; + } + + // Bail if we aren't processing bulk actions. + if ( '-1' === $action ) { + return; + } + + $ids = isset( $_GET['order'] ) // WPCS: CSRF ok. + ? $_GET['order'] + : false; + + if ( ! is_array( $ids ) ) { + $ids = array( $ids ); + } + + if ( empty( $action ) ) { + return; + } + + check_admin_referer( 'bulk-orders' ); + + $ids = wp_parse_id_list( $ids ); + + foreach ( $ids as $id ) { + switch ( $action ) { + case 'trash': + edd_trash_order( $id ); + break; + case 'restore': + edd_restore_order( $id ); + break; + case 'delete': + edd_destroy_order( $id ); + break; + case 'set-status-complete': + edd_update_payment_status( $id, 'complete' ); + break; + + case 'set-status-pending': + edd_update_payment_status( $id, 'pending' ); + break; + + case 'set-status-processing': + edd_update_payment_status( $id, 'processing' ); + break; + + case 'set-status-revoked': + edd_update_payment_status( $id, 'revoked' ); + break; + + case 'set-status-failed': + edd_update_payment_status( $id, 'failed' ); + break; + + case 'set-status-abandoned': + edd_update_payment_status( $id, 'abandoned' ); + break; + + case 'set-status-preapproval': + edd_update_payment_status( $id, 'preapproval' ); + break; + + case 'set-status-cancelled': + edd_update_payment_status( $id, 'cancelled' ); + break; + + case 'resend-receipt': + edd_email_purchase_receipt( $id, false ); + break; + } + + do_action( 'edd_payments_table_do_bulk_action', $id, $action ); + } + + wp_safe_redirect( wp_get_referer() ); +} +add_action( 'load-download_page_edd-payment-history', 'edd_orders_list_table_process_bulk_actions' ); diff --git a/includes/admin/payments/add-order.php b/includes/admin/payments/add-order.php new file mode 100644 index 00000000000..a1aeff48c5c --- /dev/null +++ b/includes/admin/payments/add-order.php @@ -0,0 +1,158 @@ + 0, + 'parent' => 0, + 'order_number' => 0, + 'status' => 'complete', + 'date_created' => date( 'Y-m-d H:i:s' ), + 'date_modified' => date( 'Y-m-d H:i:s' ), + 'date_refundable' => null, + 'user_id' => 0, + 'customer_id' => 0, + 'email' => '', + 'ip' => edd_get_ip(), + 'gateway' => '', + 'mode' => '', + 'currency' => edd_get_currency(), + 'payment_key' => '', + 'subtotal' => 0, + 'discount' => 0, + 'tax' => 0, + 'total' => 0, + ) ); + + ?> + +
    + + + +
    +

    + +
    + + + + + + + + + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + + + + + +
    + +
    + + + + + + + +use_js ) + ? ' use-js' + : ''; + $role = $this->use_js ? 'tablist' : 'menu'; + ?> + +
    +
    +
      + get_all_section_links(); ?> +
    + +
    + get_all_section_contents(); ?> +
    +
    +
    +
    + + 'order', + 'plural' => 'orders', + 'ajax' => false + ) ); + + // Use registered types + $types = array_keys( edd_get_order_types() ); + if ( ! empty( $_GET['order_type'] ) && in_array( $_GET['order_type'], $types, true ) ) { + $this->type = sanitize_key( $_GET['order_type'] ); + + // Default to 'sale' if type is unrecognized + } else { + $this->type = 'sale'; + } + + $this->set_base_url(); + $this->filter_bar_hooks(); + $this->get_payment_counts(); + } + + /** + * Set the base URL. + * + * This retains the current order-type, or 'sale' by default. + * + * @since 3.0 + */ + private function set_base_url() { + // Carry the type over to the base URL + $this->base_url = edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'order_type' => sanitize_key( $this->type ), + ) ); + } + + /** + * Hook in filter bar actions + * + * @since 3.0 + */ + private function filter_bar_hooks() { + add_action( 'edd_admin_filter_bar_orders', array( $this, 'filter_bar_items' ) ); + add_action( 'edd_after_admin_filter_bar_orders', array( $this, 'filter_bar_searchbox' ) ); + } + + /** + * Display advanced filters. + * + * @since 1.4 + * @since 3.0 Add a filter for modes. + * Display 'Advanced Filters' + */ + public function advanced_filters() { + // Hide when viewing Refunds. + if ( 'refund' === $this->type ) { + return; + } + + edd_admin_filter_bar( 'orders' ); + } + + /** + * Output filter bar items + * + * @since 3.0 + */ + public function filter_bar_items() { + + // Get values + $start_date = isset( $_GET['start-date'] ) ? sanitize_text_field( $_GET['start-date'] ) : null; + $end_date = isset( $_GET['end-date'] ) ? sanitize_text_field( $_GET['end-date'] ) : null; + $gateway = isset( $_GET['gateway'] ) ? sanitize_key( $_GET['gateway'] ) : 'all'; + $mode = isset( $_GET['mode'] ) ? sanitize_key( $_GET['mode'] ) : 'all'; + $order_total_filter_type = isset( $_GET['order-amount-filter-type'] ) ? sanitize_text_field( $_GET['order-amount-filter-type'] ) : false; + $order_total_filter_amount = isset( $_GET['order-amount-filter-value'] ) ? sanitize_text_field( $_GET['order-amount-filter-value'] ) : ''; + $country = isset( $_GET['order-country-filter-value'] ) ? sanitize_text_field( $_GET['order-country-filter-value'] ) : ''; + $region = isset( $_GET['order-region-filter-value'] ) ? sanitize_text_field( $_GET['order-region-filter-value'] ) : ''; + $product_id = ! empty( $_GET['product-id'] ) ? sanitize_text_field( $_GET['product-id'] ) : false; + + $status = $this->get_status(); + $clear_url = $this->base_url; + + // Filters + $all_modes = edd_get_payment_modes(); + $all_gateways = edd_get_payment_gateways(); + + // No modes + if ( empty( $all_modes ) ) { + $modes = array(); + + // Add "All" and pluck labels + } else { + $modes = array_merge( array( + 'all' => __( 'All modes', 'easy-digital-downloads' ) + ), wp_list_pluck( $all_modes, 'admin_label' ) ); + } + + // No gateways + if ( empty( $all_gateways ) ) { + $gateways = array(); + + // Add "All" and pluck labels + } else { + $gateways = array_merge( array( + 'all' => __( 'All gateways', 'easy-digital-downloads' ) + ), wp_list_pluck( $all_gateways, 'admin_label' ) ); + } + + /** + * Allow gateways that aren't registered the standard way to be displayed in the dropdown. + * + * @since 2.8.11 + */ + $gateways = apply_filters( 'edd_payments_table_gateways', $gateways ); + + // Output the items + if ( ! empty( $modes ) ) : ?> + + + html->select( array( + 'options' => $modes, + 'name' => 'mode', + 'id' => 'mode', + 'selected' => $mode, + 'show_option_all' => false, + 'show_option_none' => false + ) ); ?> + + + + + + html->date_field( array( + 'id' => 'start-date', + 'name' => 'start-date', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ), + 'value' => $start_date + ) ); + + echo EDD()->html->date_field( array( + 'id' => 'end-date', + 'name' => 'end-date', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ), + 'value' => $end_date + ) ); + + ?> + + + html->select( array( + 'options' => $gateways, + 'name' => 'gateway', + 'id' => 'gateway', + 'selected' => $gateway, + 'show_option_all' => false, + 'show_option_none' => false + ) ); ?> + + + + + + + +
    +
    + + __( 'equal to', 'easy-digital-downloads' ), + 'gt' => __( 'greater than', 'easy-digital-downloads' ), + 'lt' => __( 'less than', 'easy-digital-downloads' ), + ); + + echo EDD()->html->select( array( + 'id' => 'order-amount-filter-type', + 'name' => 'order-amount-filter-type', + 'options' => $options, + 'selected' => $order_total_filter_type, + 'show_option_all' => false, + 'show_option_none' => false, + ) ); + ?> + + +
    + +
    + + html->product_dropdown( array( + 'id' => 'orders-filter-product-id', + 'name' => 'product-id', + 'chosen' => true, + 'selected' => $product_id, + 'variations' => true + ) ); + ?> +
    + +
    + + html->country_select( + array( + 'name' => 'order-country-filter-value', + ), + esc_html( $country ) + ); + echo EDD()->html->region_select( + array( + 'name' => 'order-region-filter-value', + ), + esc_html( $country ), + esc_html( $region ) + ); + ?> +
    + + + +
    + + + + +
    + + +
    +
    + + + + + + + + + + + + + + search_box( esc_html__( 'Search', 'easy-digital-downloads' ), 'edd-payments' ); + } + + /** + * Show the search field. + * + * @since 1.4 + * + * @param string $text Label for the search box. + * @param string $input_id ID of the search box. + */ + public function search_box( $text, $input_id ) { + + // Bail if no customers and no search. + if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { + return; + } + + $input_id = $input_id . '-search-input'; + + if ( ! empty( $_REQUEST['orderby'] ) ) { + echo ''; + } + + if ( ! empty( $_REQUEST['order'] ) ) { + echo ''; + } + + ?> + +

    + + + +

    + + get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'edd_payment' LIMIT 1" ); + if ( ! empty( $orders ) ) { + esc_html_e( 'Easy Digital Downloads needs to upgrade the database. Orders will be available when that has completed.', 'easy-digital-downloads' ); + return; + } + } + + $status = $this->get_status(); + + if ( ! empty( $status ) ) { + $status_label = edd_get_status_label( $status ); + + switch ( $status ) { + case 'trash': + /* Translators: %s is for the status of 'Trash', telling the user no items were found in the trash. */ + $message = sprintf( __( 'No orders found in %s.', 'easy-digital-downloads' ), $status_label ); + break; + default: + /* Translators: %s is for the currently viewed order status filter */ + $message = sprintf( __( 'No %s orders found.', 'easy-digital-downloads' ), $status_label ); + break; + } + + echo esc_html( $message ); + } else { + esc_html_e( 'No orders found.', 'easy-digital-downloads' ); + } + } + + /** + * Retrieve the table columns. + * + * @since 1.4 + * + * @return array $columns Array of all the list table columns. + */ + public function get_columns() { + $columns = array( + 'cb' => '', // Render a checkbox instead of text + 'number' => __( 'Number', 'easy-digital-downloads' ), + 'customer' => __( 'Customer', 'easy-digital-downloads' ), + 'gateway' => __( 'Gateway', 'easy-digital-downloads' ), + 'amount' => __( 'Total', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ), + 'status' => __( 'Status', 'easy-digital-downloads' ), + ); + + if ( 'refund' === $this->type ) { + unset( $columns['status'] ); + } + + /** + * Filters the columns for Orders and Refunds table. + * + * @since unknown + * + * @param array $columns Table columns. + */ + $columns = apply_filters( 'edd_payments_table_columns', $columns ); + + return $columns; + } + + /** + * Retrieve the sortable columns. + * + * @since 1.4 + * + * @return array Array of all the sortable columns. + */ + public function get_sortable_columns() { + return apply_filters( 'edd_payments_table_sortable_columns', array( + 'number' => array( 'id', true ), + 'status' => array( 'status', false ), + 'customer' => array( 'customer_id', false ), + 'gateway' => array( 'gateway', false ), + 'amount' => array( 'total', false ), + 'date' => array( 'date_created', false ) + ) ); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'number'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 1.4 + * @since 3.0 Updated to use the new EDD\Orders\Order class. + * + * @param EDD\Orders\Order $order Order object. + * @param string $column_name The name of the column. + * + * @return string Column name. + */ + public function column_default( $order, $column_name ) { + $timezone_abbreviation = edd_get_timezone_abbr(); + switch ( $column_name ) { + case 'amount': + $currency = ! empty( $order->currency ) ? $order->currency : edd_get_currency(); + $value = edd_display_amount( $order->total, $currency ); + break; + case 'date': + $value = ''; + break; + case 'gateway': + $value = edd_get_gateway_admin_label( $order->gateway ); + + if ( empty( $value ) ) { + $value = '—'; + } + + break; + case 'status': + $value = edd_get_order_status_badge( $order->status ); + break; + default: + $value = method_exists( $order, 'get_' . $column_name ) + ? call_user_func( array( $order, 'get_' . $column_name ) ) + : ''; + break; + } + + return apply_filters( 'edd_payments_table_column', $value, $order->id, $column_name ); + } + + /** + * Render the checkbox column. + * + * @since 1.4 + * @since 3.0 Updated to use the new EDD\Orders\Order class. + * + * @param EDD\Orders\Order $order Order object. + * @return string Displays a checkbox. + */ + public function column_cb( $order ) { + $order_number = 'sale' === $order->type ? $order->get_number() : $order->order_number; + return sprintf( + '', + 'order', + absint( $order->id ), + /* translators: the order number */ + esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $order_number ) ) + ); + } + + /** + * Render the ID column. + * + * @since 2.0 + * @since 3.0 Updated to use the new EDD\Orders\Order class. + * + * @param EDD\Orders\Order $order Order object. + * @return string Displays a checkbox. + */ + public function column_number( $order ) { + $status = $this->get_status(); + + // View URL. + $view_url = edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'view' => 'sale' === $order->type + ? 'view-order-details' + : 'view-refund-details', + 'id' => absint( $order->id ), + ) ); + + // Default row actions. + $row_actions = array( + 'view' => '' . esc_html__( 'Edit', 'easy-digital-downloads' ) . '', + ); + + // View receipt. + if ( 'sale' === $order->type && edd_can_view_receipt( $order ) ) { + $row_actions['receipt'] = sprintf( + '%s', + edd_get_receipt_page_uri( $order->id ), + __( 'View Receipt', 'easy-digital-downloads' ) + ); + } + + // Resend Receipt. + if ( 'sale' === $this->type && 'complete' === $order->status && ! empty( $order->email ) ) { + $row_actions['email_links'] = '' . __( 'Resend Receipt', 'easy-digital-downloads' ) . ''; + } + + // Keep Delete at the end. + if ( edd_is_order_trashable( $order->id ) ) { + $trash_url = wp_nonce_url( add_query_arg( array( + 'edd-action' => 'trash_order', + 'purchase_id' => absint( $order->id ), + ), $this->base_url ), 'edd_payment_nonce' ); + $row_actions['trash'] = '' . esc_html__( 'Trash', 'easy-digital-downloads' ) . ''; + } elseif ( edd_is_order_restorable( $order->id ) ) { + $restore_url = wp_nonce_url( add_query_arg( array( + 'edd-action' => 'restore_order', + 'purchase_id' => absint( $order->id ), + ), $this->base_url ), 'edd_payment_nonce' ); + $row_actions['restore'] = '' . esc_html__( 'Restore', 'easy-digital-downloads' ) . ''; + + $delete_url = wp_nonce_url( add_query_arg( array( + 'edd-action' => 'delete_order', + 'purchase_id' => absint( $order->id ), + ), $this->base_url ), 'edd_payment_nonce' ); + $row_actions['delete'] = '' . esc_html__( 'Delete Permanently', 'easy-digital-downloads' ) . ''; + + unset( $row_actions['view'] ); + } + + if ( has_filter( 'edd_payment_row_actions' ) ) { + $payment = edd_get_payment( $order->id ); + + /** + * Filters the row actions. + * + * @deprecated 3.0 + * + * @param array $row_actions + * @param EDD_Payment|false $payment + */ + $row_actions = apply_filters_deprecated( 'edd_payment_row_actions', array( $row_actions, $payment ), '3.0', 'edd_order_row_actions' ); + } + + /** + * Filters the row actions. + * + * @param array $row_actions Array of row actions. + * @param EDD\Orders\Order $order Order object. + * + * @since 3.0 + */ + $row_actions = apply_filters( 'edd_order_row_actions', $row_actions, $order ); + + // Row actions + $actions = $this->row_actions( $row_actions ); + + // Primary link + $order_number = 'sale' === $order->type ? $order->get_number() : $order->order_number; + $link = edd_is_order_restorable( $order->id ) ? '' . esc_html( $order_number ) . '' : '' . esc_html( $order_number ) . ''; + + // Concatenate & return the results + return $link . $actions; + } + + /** + * Render the Customer column. + * + * @since 2.4.3 + * @since 3.0 Updated to use the new EDD\Orders\Order class. + * + * @param EDD\Orders\Order $order Order object. + * @return string Data shown in the Customer column. + */ + public function column_customer( $order ) { + $customer_id = $order->customer_id; + $customer = edd_get_customer( $customer_id ); + + // Actions if exists + if ( ! empty( $customer ) ) { + + // Use customer name, if exists + $name = ! empty( $customer->name ) + ? $customer->name + : $order->email; + + // Link to View Customer + $url = edd_get_admin_url( array( + 'page' => 'edd-customers', + 'view' => 'overview', + 'id' => absint( $customer_id ), + ) ); + + $name = '' . esc_html( $name ) . ''; + if ( ! empty( $customer->name ) ) { + $name .= '
    ' . $order->email . '
    '; + } + } else { + $name = $order->email; + } + + /** + * Filters the output of the Email column in the Payments table. + * + * @since 1.4 + * @since 3.0 Run manually inside of the `customer` column for backwards compatibility. + * + * @param string $name Customer name. + * @param int $order_id ID of the Payment/Order. + * @param string $column_name Name of the current column (email). + */ + $name = apply_filters( 'edd_payments_table_column', $name, $order->id, 'email' ); + + return $name; + } + + /** + * Retrieve the bulk actions. + * + * @since 1.4 + * + * @return array $actions Bulk actions. + */ + public function get_bulk_actions() { + if ( 'refund' !== $this->type ) { + $action = array( + 'set-status-complete' => __( 'Mark Completed', 'easy-digital-downloads' ), + 'set-status-pending' => __( 'Mark Pending', 'easy-digital-downloads' ), + 'set-status-processing' => __( 'Mark Processing', 'easy-digital-downloads' ), + 'set-status-revoked' => __( 'Mark Revoked', 'easy-digital-downloads' ), + 'set-status-failed' => __( 'Mark Failed', 'easy-digital-downloads' ), + 'set-status-abandoned' => __( 'Mark Abandoned', 'easy-digital-downloads' ), + 'set-status-preapproval' => __( 'Mark Preapproved', 'easy-digital-downloads' ), + 'set-status-cancelled' => __( 'Mark Cancelled', 'easy-digital-downloads' ), + 'resend-receipt' => __( 'Resend Receipts', 'easy-digital-downloads' ), + ); + } else { + $action = array(); + } + + if ( 'trash' === $this->get_status() ) { + $action = array( + 'restore' => __( 'Restore', 'easy-digital-downloads' ), + ); + + if ( current_user_can( 'delete_shop_payments' ) ) { + $action['delete'] = __( 'Delete permanently', 'easy-digital-downloads' ); + } + } else { + $action['trash'] = __( 'Move to Trash', 'easy-digital-downloads' ); + } + + return apply_filters( 'edd_payments_table_bulk_actions', $action ); + } + + /** + * Process the bulk actions. + * + * @since 1.4 + * @since 3.0 Updated to display _doing_it_wrong(). + * + * @see edd_orders_list_table_process_bulk_actions() + */ + public function process_bulk_action() { + _doing_it_wrong( __FUNCTION__, 'Orders list table bulk actions are now handled by edd_orders_list_table_process_bulk_actions(). Please do not call this method directly.', 'EDD 3.0' ); + } + + /** + * Retrieve the payment counts. + * + * @since 1.4 + */ + public function get_payment_counts() { + + // Get the args (without pagination) + $args = $this->parse_args( false ); + + unset( $args['status'], $args['status__not_in'], $args['status__in'] ); + + // Get order counts by type + $this->counts = edd_get_order_counts( $args ); + } + + /** + * Retrieves all the data for all the orders. + * + * @since 1.4 + * @deprecated 3.0 Use get_data() + * + * @return array $payment_data Array of all the data for the orders. + */ + public function payments_data() { + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Payment_History_Table::get_data()' ); + + return $this->get_data(); + } + + /** + * Retrieves all of the orders data based on current filters. + * + * @since 3.0 + * + * @return array Orders table data. + */ + public function get_data() { + + // Parse args (with pagination) + $this->args = $this->parse_args( true ); + + // Force EDD\Orders\Order objects to be returned + $this->args['output'] = 'orders'; + + if ( empty( $this->args['status'] ) ) { + $this->args['status__not_in'] = array( 'trash' ); + } + + // Get data + $items = edd_get_orders( $this->args ); + + // Get customer IDs and count from payments + $customer_ids = array_unique( wp_list_pluck( $items, 'customer_id' ) ); + $cust_count = count( $customer_ids ); + + // Maybe prime customer objects (if more than number of queries) + if ( $cust_count > 1 ) { + edd_get_customers( array( + 'id__in' => $customer_ids, + 'no_found_rows' => true, + 'number' => $cust_count + ) ); + } + + // Return items + return $items; + } + + /** + * Retrieves the Payments table views. + * + * @since 1.4 + * + * @return array $views Available views. + */ + public function get_views() { + $views = parent::get_views(); + + /** + * Filters the Payment table's views. + * + * @since 1.4 + * + * @param array $views Payment table's views. + */ + $views = apply_filters( 'edd_payments_table_views', $views ); + + return $views; + } + + /** + * Builds an array of arguments for getting orders for the list table, counts, and pagination. + * + * @since 3.0 + * + * @param bool $paginate Whether to add pagination arguments + * + * @return array Array of arguments to use for querying orders. + */ + private function parse_args( $paginate = true ) { + $status = $this->get_status(); + $user = isset( $_GET['user'] ) ? absint( $_GET['user'] ) : null; + $customer = isset( $_GET['customer'] ) ? absint( $_GET['customer'] ) : null; + $search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : null; + $start_date = isset( $_GET['start-date'] ) ? sanitize_text_field( $_GET['start-date'] ) : null; + $end_date = isset( $_GET['end-date'] ) ? sanitize_text_field( $_GET['end-date'] ) : $start_date; + $gateway = isset( $_GET['gateway'] ) ? sanitize_text_field( $_GET['gateway'] ) : null; + $mode = isset( $_GET['mode'] ) ? sanitize_text_field( $_GET['mode'] ) : null; + $type = isset( $_GET['order_type'] ) ? sanitize_text_field( $_GET['order_type'] ) : 'sale'; + + /** + * Introduced as part of #6063. Allow a gateway to specified based on the context. + * + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6063 + * @since 2.8.11 + * + * @param string $gateway + */ + $gateway = apply_filters( 'edd_payments_table_search_gateway', $gateway ); + + if ( $gateway === 'all' ) { + $gateway = null; + } + + if ( $mode === 'all' ) { + $mode = null; + } + + $args = array_filter( array( + 'user_id' => $user, + 'customer_id' => $customer, + 'status' => $status, + 'gateway' => $gateway, + 'mode' => $mode, + 'type' => $type, + ) ); + + // If no specific ordering has been requested, order by `date_created`. + if ( empty( $_GET['orderby'] ) ) { + $args['orderby'] = 'date_created'; + $args['order'] = 'DESC'; + } + + // Update args by search query. + if ( ! empty( $search ) ) { + $args = $this->parse_search( $search, $args ); + } + + // Date query + if ( ! empty( $start_date ) || ! empty( $end_date ) ) { + + // start AND end + $args['date_query'] = array( + 'relation' => 'AND' + ); + + // Start (of day) + if ( ! empty( $start_date ) ) { + $args['date_query'][] = array( + 'column' => 'date_created', + 'after' => edd_get_utc_date_string( $start_date, 'mysql' ), + ); + } + + // End (of day) + if ( ! empty( $end_date ) ) { + $end_date_string = EDD()->utils->get_date_string( $end_date, 23, 59, 59 ); + $args['date_query'][] = array( + 'column' => 'date_created', + 'before' => edd_get_utc_date_string( $end_date_string, 'mysql' ), + ); + } + } + + // Maybe filter by order amount. + if ( isset( $_GET['order-amount-filter-type'] ) && ! empty( $_GET['order-amount-filter-value'] ) ) { + $filter_amount = floatval( sanitize_text_field( $_GET['order-amount-filter-value'] ) ); + + switch ( $_GET['order-amount-filter-type'] ) { + case 'lt': + $filter_type = '<'; + break; + case 'gt': + $filter_type = '>'; + break; + default: + $filter_type = '='; + break; + } + + $args['compare_query'] = array( + array( + 'key' => 'total', + 'value' => $filter_amount, + 'compare' => $filter_type, + ), + ); + } + + // Maybe filter by product. + if ( ! empty( $_GET['product-id'] ) ) { + if ( false !== strpos( $_GET['product-id'], '_' ) ) { + $product_pieces = explode( '_', $_GET['product-id'] ); + $args['product_id'] = absint( $product_pieces[0] ); + $args['product_price_id'] = absint( $product_pieces[1] ); + } else { + $args['product_id'] = absint( $_GET['product-id'] ); + } + } + + // Maybe filter by country. + if ( ! empty( $_GET['order-country-filter-value'] ) ) { + $args['country'] = sanitize_text_field( $_GET['order-country-filter-value'] ); + } + + // Maybe filter by region. + if ( ! empty( $_GET['order-region-filter-value'] ) ) { + $args['region'] = sanitize_text_field( $_GET['order-region-filter-value'] ); + } + + // Maybe filter by discount ID. + if ( ! empty( $_GET['discount_id'] ) ) { + $args['discount_id'] = absint( $_GET['discount_id'] ); + } + + /** + * Filters array of arguments for getting orders for the list table, counts, and pagination. + * + * @since 3.0 + * + * @param array $args Array of arguments to use for querying orders. + * @param bool $paginate $paginate Whether to add pagination arguments + */ + $args = apply_filters( 'edd_payments_table_parse_args', $args, $paginate ); + + // Return args, possibly with pagination + return ( true === $paginate ) + ? $this->parse_pagination_args( $args ) + : $args; + } + + /** + * Parse the search query. + * + * @since 3.0.2 + * @param string $search + * @param array $args + * @return array + */ + private function parse_search( $search, $args ) { + + // Order ID/number. + if ( is_numeric( $search ) ) { + $args['id'] = $search; + $args['order_number'] = $search; + + return $args; + } + + // Transaction ID + if ( is_string( $search ) && ( false !== strpos( $search, 'txn:' ) ) ) { + $args['txn'] = trim( str_replace( 'txn:', '', $search ) ); + + return $args; + } + + // Email + if ( is_email( $search ) ) { + $args['email'] = $search; + + return $args; + } + + // Download ID + if ( is_string( $search ) && ( false !== strpos( $search, '#' ) ) ) { + $args['product_id'] = intval( trim( str_replace( '#', '', $search ) ) ); + + return $args; + } + + // The customer’s name or ID prefixed by customer: + if ( ! is_array( $search ) && ( false !== strpos( $search, 'customer:' ) ) ) { + $search = trim( str_replace( 'customer:', '', $search ) ); + + // Search by customer ID. + if ( is_numeric( $search ) ) { + $args['customer_id'] = absint( $search ); + + return $args; + } + + // Get customer IDs that match the search string. + $customers = edd_get_customers( + array( + 'search' => $search, + 'fields' => 'ids', + ) + ); + if ( ! empty( $customers ) ) { + $args['customer_id__in'] = $customers; + } else { + $args['customer_id__in'] = array( null ); + } + + return $args; + } + + // The user ID prefixed by user: + if ( ! is_array( $search ) && ( false !== strpos( $search, 'user:' ) ) ) { + $search = trim( str_replace( 'user:', '', $search ) ); + if ( is_numeric( $search ) ) { + $args['user_id'] = absint( $search ); + + return $args; + } + } + + // The Discount Code prefixed by discount: + if ( is_string( $search ) && ( false !== strpos( $search, 'discount:' ) ) ) { + $discount = edd_get_discount_by_code( trim( str_replace( 'discount:', '', $search ) ) ); + if ( ! empty( $discount->id ) ) { + $args['discount_id'] = $discount->id; + } else { + // If no discount object is found, we force the results to be empty. + $args['id__in'] = array( null ); + } + + return $args; + } + + // Default search + $args['search'] = $search; + + return $args; + } + + /** + * Setup the final data for the table. + * + * @since 1.4 + */ + public function prepare_items() { + wp_reset_vars( array( 'action', 'order', 'orderby', 'order', 's' ) ); + + $hidden = array(); // No hidden columns + $columns = $this->get_columns(); + $sortable = $this->get_sortable_columns(); + $status = $this->get_status( 'total' ); + $this->items = $this->get_data(); + + $this->_column_headers = array( $columns, $hidden, $sortable ); + if ( empty( $this->counts[ $status ] ) ) { + return; + } + + $this->set_pagination_args( array( + 'total_pages' => ceil( $this->counts[ $status ] / $this->per_page ), + 'total_items' => $this->counts[ $status ], + 'per_page' => $this->per_page, + ) ); + } + + /** + * Generate the table navigation above or below the table. + * We're overriding this to turn off the referer param in `wp_nonce_field()`. + * + * @param string $which + * @since 3.1.0.4 + * @since 3.1.1 Outputs the dialogs for deleting orders. + */ + protected function display_tablenav( $which ) { + if ( 'top' === $which ) : + wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false ); + ?> +
    + +
    +
    + +
    + +
    + + has_items() ) : ?> +
    + bulk_actions( $which ); ?> +
    + extra_tablenav( $which ); + $this->pagination( $which ); + ?> + +
    +
    + 'refund-item', + 'plural' => 'refund-items', + 'ajax' => false, + ) ); + + $this->get_counts(); + } + + /** + * Get the base URL for the order item list table. + * + * @since 3.0 + * + * @return string Base URL. + */ + public function get_base_url() {} + + /** + * Retrieve the view types. + * + * @since 3.0 + * + * @return array $views All the views available. + */ + public function get_views() { + return array(); + } + + /** + * Retrieve the table columns. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns. + */ + public function get_columns() { + $columns = array( + 'cb' => '', + 'name' => __( 'Product', 'easy-digital-downloads' ), + 'amount' => __( 'Unit Price', 'easy-digital-downloads' ), + 'quantity' => __( 'Quantity', 'easy-digital-downloads' ), + 'subtotal' => __( 'Subtotal', 'easy-digital-downloads' ), + ); + + // Maybe add tax column. + $order = $this->get_order(); + if ( $order && $order->get_tax_rate() ) { + $columns['tax'] = __( 'Tax', 'easy-digital-downloads' ); + } + + // Total at the end. + $columns['total'] = __( 'Total', 'easy-digital-downloads' ); + + // Return columns. + return $columns; + } + + /** + * Retrieve the sortable columns. + * + * @since 3.0 + * + * @return array Array of all the sortable columns. + */ + public function get_sortable_columns() { return array(); } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * Generates a unique ID for an item, to be used as HTML IDs. + * We cannot simply use `$item->id` because it's possible that an order item and order adjustment + * could have the same ID. + * + * @param Order_Item|Order_Adjustment $item + * + * @since 3.0 + * @return string + */ + private function get_item_unique_id( $item ) { + return $item instanceof Order_Item ? 'order-item-' . $item->id : 'order-adjustment-' . $item->id; + } + + /** + * Returns a string that designates the type of object. This is used in HTML `name` attributes. + * + * @param Order_Item|Order_Adjustment $item + * + * @since 3.0 + * @return string + */ + private function get_object_type( $item ) { + return $item instanceof Order_Item ? 'order_item' : 'order_adjustment'; + } + + /** + * Returns the item display name. + * + * @param Order_Item|Order_Adjustment $item + * + * @since 3.0 + * @return string + */ + private function get_item_display_name( $item ) { + $name = ''; + if ( $item instanceof Order_Item ) { + return $item->get_order_item_name(); + } + if ( $item instanceof Order_Adjustment ) { + $name = __( 'Order Fee', 'easy-digital-downloads' ); + if ( 'credit' === $item->type ) { + $name = __( 'Order Credit', 'easy-digital-downloads' ); + } + if ( ! empty( $item->description ) ) { + $name .= ': ' . $item->description; + } + } + + return $name; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 3.0 + * + * @param Order_Item|Order_Adjustment $item Order item or adjustment object. + * @param string $column_name The name of the column. + * + * @return string Column name. + */ + public function column_default( $item, $column_name ) { + $object_type = $this->get_object_type( $item ); + $item_id = $this->get_item_unique_id( $item ); + + switch ( $column_name ) { + case 'amount': + return $this->format_currency( $item, $column_name ); + + case 'total': + return $this->format_currency( $item, $column_name, 0 ); + + case 'quantity': + return $this->get_quantity_column( $item, $column_name, $item_id, $object_type ); + + case 'subtotal': + case 'tax': + return $this->get_adjustable_column( $item, $column_name, $item_id, $object_type ); + + default: + return property_exists( $item, $column_name ) + ? $item->{$column_name} + : ''; + } + } + + /** + * This private function formats a column value for currency. + * + * @since 3.0 + * + * @param Order_Item|Order_Adjustment $item Item object. + * @param string $column_name ID of the column being displayed. + * @param false|float $amount_override Amount override, in case it's not in the order item. + * + * @return string Formatted amount. + */ + private function format_currency( $item, $column_name, $amount_override = false ) { + $symbol = $this->get_currency_symbol( $item->order_id ); + $currency_pos = edd_get_option( 'currency_position', 'before' ); + + $formatted_amount = ''; + + if ( 'before' === $currency_pos ) { + $formatted_amount .= $symbol; + } + + // Order Adjustments do not have an `amount` column. We can use `subtotal` instead. + if ( 'amount' === $column_name && $item instanceof Order_Adjustment ) { + $column_name = 'subtotal'; + } + + $amount = false !== $amount_override ? $amount_override : $item->{$column_name}; + + $formatted_amount .= '' . edd_format_amount( $amount, true, $this->get_order_currency_decimals( $item->order_id ) ) . ''; + + if ( 'after' === $currency_pos ) { + $formatted_amount .= $symbol; + } + + return $formatted_amount; + } + + /** + * This private function returns the form input for refundable items, + * or amounts for items which have already been refunded. + * + * @param Order_Item $item The item object. + * @param string $column_name ID of the column being displayed. + * @param string $item_id Unique ID of the order item for the refund modal. + * @param string $object_type The item type. + * @return string + */ + private function get_adjustable_column( $item, $column_name, $item_id, $object_type ) { + + if ( 'refunded' === $item->status ) { + return $this->format_currency( $item, $column_name, 0 ); + } + + $currency_pos = edd_get_option( 'currency_position', 'before' ); + + // Maximum amounts that can be refunded. + $refundable_amounts = $item->get_refundable_amounts(); + $amount_remaining = array_key_exists( $column_name, $refundable_amounts ) ? $refundable_amounts[ $column_name ] : $item->{$column_name}; + + /* + * Original amount. + * For subtotals, we actually do subtotal minus discounts for simplicity so that the end user + * doesn't have to juggle that. + */ + $original_amount = $item->{$column_name}; + if ( 'subtotal' === $column_name && ! empty( $item->discount ) ) { + $original_amount -= $item->discount; + } + ob_start(); + ?> +
    + +
    + '; + echo esc_html( $this->get_currency_symbol( $item->order_id ) ); + echo ''; + } + ?> + + + + '; + echo esc_html( $this->get_currency_symbol( $item->order_id ) ); + echo ''; + } + ?> +
    + + format_currency( $item, $column_name, $amount_remaining ); + ?> + +
    + get_refundable_amounts(); + $item_quantity = 'order_item' === $object_type ? $refundable_amounts['quantity'] : 1; + ob_start(); + ?> +
    + +
    + + + + $options, + 'name' => 'refund_' . esc_attr( $object_type ) . '[' . esc_attr( $item->id ) . '][quantity]', + 'id' => 'edd-order-item-quantity-' . esc_attr( $item_id ), + 'class' => 'edd-order-item-refund-quantity edd-order-item-refund-input', + 'disabled' => true, + 'show_option_all' => false, + 'show_option_none' => false, + 'chosen' => false, + 'selected' => $item_quantity, + 'data' => array( + 'max' => intval( $item_quantity ), + 'original' => intval( $item->quantity ), + ), + ); + ?> + html->select( $args ); ?> + +
    +
    + currency ); + } else { + $currency_decimals = 2; + } + } + + return $currency_decimals; + } + + /** + * Retrieves the currency symbol for a given order item. + * + * @param int $order_id + * + * @since 3.0 + * @return string|null + */ + private function get_currency_symbol( $order_id ) { + static $symbol = null; + + if ( is_null( $symbol ) ) { + $order = edd_get_order( $order_id ); + + if ( $order ) { + $symbol = edd_currency_symbol( $order->currency ); + } + } + + return $symbol; + } + + /** + * Render the checkbox column + * + * @since 3.0 + * + * @param Order_Item|Order_Adjustment $item Order Item or Order Adjustment object. + * + * @return string + */ + public function column_cb( $item ) { + $object_type = $this->get_object_type( $item ); + $refundable_amounts = $item->get_refundable_amounts(); + $total_remaining = array_key_exists( 'total', $refundable_amounts ) ? floatval( $refundable_amounts['total'] ) : 0.00; + + if ( 'refunded' !== $item->status && 0.00 != $total_remaining ) { + + return sprintf( + '', + /*$1%s*/ + 'refund_' . esc_attr( $object_type ), + /*$2%s*/ + esc_attr( $item->id ), + /* translators: product name */ + esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $this->get_item_display_name( $item ) ) ) + ); + } + + return ''; + } + + /** + * Render the Name Column + * + * @since 3.0 + * + * @param Order_Item|Order_Adjustment $item Order Item object. + * + * @return string Data shown in the Name column + */ + public function column_name( $item ) { + $checkbox_id = 'refund_' . $this->get_object_type( $item ) . '-' . $item->id; + $display_name = esc_html( $this->get_item_display_name( $item ) ); + $status_label = ! empty( $item->status ) && 'complete' !== $item->status ? ' — ' . edd_get_status_label( $item->status ) : ''; + + if ( 'refunded' === $item->status ) { + return '' . $display_name . '' . esc_html( $status_label ); + } + + return '' . $status_label; + } + + /** + * Message to be displayed when there are no items + * + * @since 3.0 + */ + public function no_items() { + esc_html_e( 'No items found.', 'easy-digital-downloads' ); + } + + /** + * Retrieve the bulk actions + * + * @since 3.0 + * @return array $actions Array of the bulk actions + */ + public function get_bulk_actions() { return array(); } + + /** + * Process the bulk actions + * + * @since 3.0 + */ + public function process_bulk_action() {} + + /** + * Retrieve the order_item code counts + * + * @todo Fees aren't included in this count, but where does this actually get used anyway? + * + * @since 3.0 + */ + public function get_counts() { + + // Maybe retrieve counts. + if ( ! edd_is_add_order_page() ) { + + // Check for an order ID + $order_id = ! empty( $_POST['order_id'] ) + ? absint( $_POST['order_id'] ) // WPCS: CSRF ok. + : 0; + + // Get counts + $this->counts = edd_get_order_item_counts( array( + 'order_id' => $order_id, + ) ); + } + } + + /** + * Retrieve all order data to be shown on the refund table. + * This includes order items and order adjustments. + * + * @since 3.0 + * @return Order[]|Order_Adjustment[] All order items and order adjustments associated with the current order. + */ + public function get_data() { + $order = $this->get_order(); + + if ( empty( $order ) ) { + return array(); + } + + // Get order items. + $order_items = edd_get_order_items( array( + 'order_id' => $order->id, + 'number' => 999, + ) ); + + // Get order fees + $order_fees = $order->get_fees(); + + // Get order credits. + $credits = edd_get_order_adjustments( array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'type' => 'credit', + ) ); + + return array_merge( $order_items, $order_fees, $credits ); + } + + /** + * Setup the final data for the table + * + * @since 3.0 + */ + public function prepare_items() { + $this->_column_headers = array( + $this->get_columns(), + array(), + $this->get_sortable_columns(), + ); + + $this->items = $this->get_data(); + } + + /** + * Generates content for a single row of the table + * + * @since 3.0 + * + * @param Order_Item|Order_Adjustment $item Order item object. + */ + public function single_row( $item ) { + + $is_adjustment = $item instanceof Order_Adjustment; + $item_class = $is_adjustment ? $item->object_id : $item->order_id; + // Status. + $classes = array_map( 'sanitize_html_class', array( + 'order-' . $item_class, + $item->status, + 'refunditem', + ) ); + + // Turn into a string. + $class = implode( ' ', $classes ); + $item_id = $this->get_item_unique_id( $item ); + + $is_credit = $is_adjustment && 'credit' === $item->type; + ?> + ="id ); ?>" class=""> + single_row_columns( $item ); ?> + + _args['singular']; + + wp_nonce_field( 'edd_process_refund', 'edd_process_refund' ); + $this->screen->render_screen_reader_content( 'heading_list' ); + ?> + + + + print_column_headers(); ?> + + + + > + display_rows_or_placeholder(); ?> + + +
    +
    + get_order() ); + + $this->display_tablenav( 'bottom' ); + ?> +
    + +
    + +
    + items as $item ) { + + if ( empty( $order_id ) ) { + $order_id = $item->order_id; + } + + $this->single_row( $item ); + } + + $currency_symbol = $this->get_currency_symbol( $order_id ); + + // Now we need to add the columns for the totals. + ?> + + + + + %s', $currency_symbol ); + $before = 'before' === $currency_position ? $currency_symbol_output : ''; + $after = 'after' === $currency_position ? $currency_symbol_output : ''; + $amount = edd_format_amount( 0.00, true, $this->get_order_currency_decimals( $order_id ) ); + printf( + '%1$s%2$s%3$s', + $before, // phpcs:ignore + esc_attr( $amount ), + $after // phpcs:ignore + ); + ?> + + + + get_order(); + if ( $order && $order->get_tax_rate() ) : + ?> + + + + + %1$s%2$s%3$s', + $before, // phpcs:ignore + esc_attr( $amount ), + $after // phpcs:ignore + ); + ?> + + + + + + + + + %1$s%2$s%3$s', + $before, // phpcs:ignore + esc_attr( $amount ), + $after // phpcs:ignore + ); + ?> + + + id ) { + return; + } + + // Do not show on Add or View Order/Refund. + if ( isset( $_GET['view'] ) ) { + return; + } + + $pass_manager = new Pass_Manager(); + if ( $pass_manager->isFree() ) { + $docs_url = edd_link_helper( + 'https://easydigitaldownloads.com/docs/', + array( + 'utm_medium' => 'orders-contextual-help', + 'utm_content' => 'documentation', + ) + ); + + $upgrade_url = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => 'orders-contextual-help', + 'utm_content' => 'lite-upgrade', + ) + ); + + $screen->set_help_sidebar( + '

    ' . __( 'For more information:', 'easy-digital-downloads' ) . '

    ' . + '

    ' . sprintf( __( 'Visit the documentation on the Easy Digital Downloads website.', 'easy-digital-downloads' ), $docs_url ) . '

    ' . + '

    ' . sprintf( + __( 'Need more from your Easy Digital Downloads store? Upgrade Now!', 'easy-digital-downloads' ), + $upgrade_url + ) . '

    ' + ); + } + + $screen->add_help_tab( array( + 'id' => 'edd-payments-overview', + 'title' => __( 'Overview', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'This screen provides access to all of the orders and refunds in your store.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Orders can be searched by email address, user name, or filtered by status, mode, date range, gateway, and more!', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'To maintain accurate reporting and accounting, we strongly advise against deleting any completed order data.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-orders', + 'title' => __( '— Orders', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'Orders are placed by customers when they buy things from your store.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Every order contains a snapshot of your store at the time the order was placed, and is made up of many different pieces of information.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Things like products, discounts, taxes, fees, and customer email address, are all examples of information that is saved with each order.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Both full and partial refunds are supported.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-refunds', + 'title' => __( '— Refunds', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'Refunds are created when a customer would like money back from a completed order.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Every refund refers back to the original order, and only contains the items and adjustments that were refunded.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Refunds could be entire orders, or single products.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Once an item is refunded, it cannot be undone; it can only be repurchased.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-payments-search', + 'title' => __( 'Search', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'The order history can be searched in several different ways.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'You can enter:', 'easy-digital-downloads' ) . '

    ' . + '
      +
    • ' . __( 'The specific order ID', 'easy-digital-downloads' ) . '
    • +
    • ' . __( 'The 32-character order key', 'easy-digital-downloads' ) . '
    • +
    • ' . __( 'The customer\'s email address', 'easy-digital-downloads' ) . '
    • +
    • ' . sprintf( + __( 'The customer\'s name or ID prefixed by %s', 'easy-digital-downloads' ), + 'customer:' + ) . '
    • +
    • ' . sprintf( + __( 'A user\'s ID prefixed by %s', 'easy-digital-downloads' ), + 'user:' + ) . '
    • +
    • ' . sprintf( + __( 'The %s ID prefixed by %s', 'easy-digital-downloads' ), + edd_get_label_singular(), + '#' + ) . '
    • +
    • ' . sprintf( + __( 'The Discount Code prefixed by %s', 'easy-digital-downloads' ), + 'discount:' + ) . '
    • +
    • ' . sprintf( + __( 'A transaction ID prefixed by %s', 'easy-digital-downloads' ), + 'txn:' + ) . '
    • +
    ', + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-payments-details', + 'title' => __( 'Details', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'Each order can be further inspected by clicking the corresponding View Order Details link. This will provide more information including:', 'easy-digital-downloads' ) . '

    ' . + + '
      +
    • Purchased File - ' . __( 'The file associated with the purchase.', 'easy-digital-downloads' ) . '
    • +
    • Purchase Date - ' . __( 'The exact date and time the order was completed.', 'easy-digital-downloads' ) . '
    • +
    • Discount Used - ' . __( 'If a coupon or discount was used during the checkout process.', 'easy-digital-downloads' ) . '
    • +
    • Name - ' . __( "The buyer's name.", 'easy-digital-downloads' ) . '
    • +
    • Email - ' . __( "The buyer's email address.", 'easy-digital-downloads' ) . '
    • +
    • Payment Notes - ' . __( 'Any customer-specific notes related to the order.', 'easy-digital-downloads' ) . '
    • +
    • Payment Method - ' . __( 'The name of the order gateway used to complete the order.', 'easy-digital-downloads' ) . '
    • +
    • Purchase Key - ' . __( 'A unique key used to identify the order.', 'easy-digital-downloads' ) . '
    • +
    ' + ) ); + + do_action( 'edd_payments_contextual_help', $screen ); +} +add_action( 'load-download_page_edd-payment-history', 'edd_payments_contextual_help' ); diff --git a/includes/admin/payments/edit-payment.php b/includes/admin/payments/edit-payment.php deleted file mode 100755 index 9c3d0f8492c..00000000000 --- a/includes/admin/payments/edit-payment.php +++ /dev/null @@ -1,96 +0,0 @@ - -
    -

    : -

    -
    - - - - - - - - - - - - - - - - - - - -
    - - - -

    -
    - - - ' . get_the_title($id) . ' - Remove'; - endforeach; - endif; - ?> -

    -
    - - - -
    - - - - - - -
    - -
    \ No newline at end of file diff --git a/includes/admin/payments/orders.php b/includes/admin/payments/orders.php new file mode 100644 index 00000000000..5c839313fb6 --- /dev/null +++ b/includes/admin/payments/orders.php @@ -0,0 +1,1181 @@ + + +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    +
    + + +
    + + + autofocus + + /> +
    +
    + +
    +
    + +
    + +
    + +use_js = true; + $sections->current_section = 'customer'; + $sections->item = $item; + $sections->base_url = ''; + + // Get all registered tabs & views + $o_sections = edd_get_order_details_sections( $item ); + + // Set the customer sections + $sections->set_sections( $o_sections ); + + // Display the sections + $sections->display(); +} + +/** + * Return the order details sections. + * + * @since 3.0 + * + * @param object $order + * @return array Sections. + */ +function edd_get_order_details_sections( $order ) { + $sections = array( + array( + 'id' => 'customer', + 'label' => __( 'Customer', 'easy-digital-downloads' ), + 'icon' => 'businessman', + 'callback' => 'edd_order_details_customer', + ), + array( + 'id' => 'email', + 'label' => __( 'Email', 'easy-digital-downloads' ), + 'icon' => 'email', + 'callback' => 'edd_order_details_email', + ), + array( + 'id' => 'address', + 'label' => __( 'Address', 'easy-digital-downloads' ), + 'icon' => 'admin-home', + 'callback' => 'edd_order_details_addresses', + ), + array( + 'id' => 'notes', + 'label' => __( 'Notes', 'easy-digital-downloads' ), + 'icon' => 'admin-comments', + 'callback' => 'edd_order_details_notes', + ), + array( + 'id' => 'logs', + 'label' => __( 'Logs', 'easy-digital-downloads' ), + 'icon' => 'admin-tools', + 'callback' => 'edd_order_details_logs', + ), + ); + + // Override sections if adding a new order. + if ( edd_is_add_order_page() ) { + $sections = array( + array( + 'id' => 'customer', + 'label' => __( 'Customer', 'easy-digital-downloads' ), + 'icon' => 'businessman', + 'callback' => 'edd_order_details_customer', + ), + array( + 'id' => 'address', + 'label' => __( 'Address', 'easy-digital-downloads' ), + 'icon' => 'admin-home', + 'callback' => 'edd_order_details_addresses', + ), + ); + } + + /** + * Filter the sections. + * + * @since 3.0 + * + * @param array $sections Sections. + * @param object $order Order object. + */ + return (array) apply_filters( 'edd_get_order_details_sections', $sections, $order ); +} + +/** + * Output the order details customer section + * + * @since 3.0 + * + * @param object $order + */ +function edd_order_details_customer( $order ) { + $customer = edd_get_customer( $order->customer_id ); + $payment = edd_get_payment( $order->id ); + $user_info = $payment + ? $payment->user_info + : array(); + + $change_text = edd_is_add_order_page() + ? esc_html__( 'Assign', 'easy-digital-downloads' ) + : esc_html__( 'Switch Customer', 'easy-digital-downloads' ); + + $customer_id = ! empty( $customer ) + ? $customer->id + : 0; ?> + +
    +
    +
    +
    + +
    + html->customer_dropdown( + array( + 'class' => 'edd-payment-change-customer-input edd-form-group__input', + 'selected' => $customer_id, + 'id' => 'customer-id', + 'name' => 'customer-id', + 'none_selected' => esc_html__( 'Search for a customer', 'easy-digital-downloads' ), + 'placeholder' => esc_html__( 'Search for a customer', 'easy-digital-downloads' ), + ) + ); // WPCS: XSS ok. + ?> +
    +
    + + + +
    + +
    +
    + +
    + +
    + +

    + or +

    +
    + + +
    + + get_meta(), $user_info ); + } + do_action( 'edd_payment_view_details', $order->id ); +} + +/** + * Output the order details email section + * + * @since 3.0 + * + * @param object $order + */ +function edd_order_details_email( $order ) { + $customer = edd_get_customer( $order->customer_id ); + $all_emails = array( 'primary' => $customer->email ); + + if ( $customer->email !== $order->email ) { + $all_emails['order'] = $order->email; + } + + foreach ( $customer->emails as $key => $email ) { + if ( $customer->email === $email ) { + continue; + } + + $all_emails[ $key ] = $email; + } + + $help = sprintf( + /* translators: email type */ + __('Send a new copy of the purchase receipt to the %s email address. If download URLs were included in the original receipt, new ones will be included.', 'easy-digital-downloads' ), + count( $all_emails ) > 1 ? __( 'selected', 'easy-digital-downloads' ) : __( 'customer', 'easy-digital-downloads' ) + ); + + $is_multiselect = count( $all_emails ) > 1; + $label_text = $is_multiselect ? __( 'Send email receipt to', 'easy-digital-downloads' ) : __( 'Email Address', 'easy-digital-downloads' ); +?> + +
    +
    + + +
    + + + + + +
    + +

    + +

    +
    + +

    + +

    + + id ); ?> + +
    + 0, + 'order_id' => 0, + 'first_name' => '', + 'last_name' => '', + 'address' => '', + 'address2' => '', + 'city' => '', + 'region' => '', + 'postal_code' => '', + 'country' => '', + ) + : $order->get_address(); ?> + +
    + id ); ?> + +
    +

    + + + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + html->country_select( + array( + 'name' => 'edd_order_address[country]', + 'id' => 'edd-order-address-country', + 'class' => 'edd-order-address-country edd-form-group__input', + 'show_option_all' => false, + 'data' => array( + 'nonce' => wp_create_nonce( 'edd-country-field-nonce' ), + 'search-type' => 'no_ajax', + 'search-placeholder' => esc_html__( 'Search Countries', 'easy-digital-downloads' ), + ), + ), + $address->country + ); // WPCS: XSS ok. + ?> +
    +
    + +
    + +
    + country ); + if ( ! empty( $states ) ) { + echo EDD()->html->region_select( + array( + 'name' => 'edd_order_address[region]', + 'id' => 'edd_order_address_region', + 'class' => 'edd-order-address-region edd-form-group__input', + 'data' => array( + 'search-type' => 'no_ajax', + 'search-placeholder' => esc_html__( 'Search Regions', 'easy-digital-downloads' ), + ), + ), + $address->country, + $address->region + ); // WPCS: XSS ok. + } else { + ?> + + +
    +
    + + + +
    + +
    + + id ); +} + +/** + * Output the order details notes section + * + * @since 3.0 + * + * @param object $order + */ +function edd_order_details_notes( $order ) { + $notes = edd_get_payment_notes( $order->id ); ?> + +
    + + id, 'order' ); // WPCS: XSS ok. ?> +
    + + + +
    + id ); + $download_log_url = edd_get_admin_url( + array( + 'page' => 'edd-tools', + 'tab' => 'logs', + 'payment' => absint( $order->id ), + ) + ); + $customer_log_url = edd_get_admin_url( + array( + 'page' => 'edd-tools', + 'tab' => 'logs', + 'customer' => absint( $order->customer_id ), + ) + ); + $customer_orders_url = edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'customer' => absint( $order->customer_id ), + ) + ); + ?> + +

    +

    +

    + + id ); + ?> +
    + + $order->id, + 'number' => 999, + ) ); + + foreach ( $items as $item ) { + $item_adjustments = array(); + + $adjustments = edd_get_order_adjustments( array( + 'object_id' => $item->id, + 'number' => 999, + 'object_type' => 'order_item', + 'type' => array( + 'discount', + 'credit', + 'fee', + ), + ) ); + + foreach ( $adjustments as $adjustment ) { + // @todo edd_get_order_adjustment_to_json()? + $adjustment_args = array( + 'id' => esc_html( $adjustment->id ), + 'objectId' => esc_html( $adjustment->object_id ), + 'objectType' => esc_html( $adjustment->object_type ), + 'typeId' => esc_html( $adjustment->type_id ), + 'type' => esc_html( $adjustment->type ), + 'description' => esc_html( $adjustment->description ), + 'subtotal' => esc_html( $adjustment->subtotal ), + 'tax' => esc_html( $adjustment->tax ), + 'total' => esc_html( $adjustment->total ), + 'dateCreated' => esc_html( $adjustment->date_created ), + 'dateModified' => esc_html( $adjustment->date_modified ), + 'uuid' => esc_html( $adjustment->uuid ), + ); + + $item_adjustments[] = $adjustment_args; + $_adjustments[] = $adjustment_args; + } + + // @todo edd_get_order_item_to_json()? + $_items[] = array( + 'id' => esc_html( $item->id ), + 'orderId' => esc_html( $item->order_id ), + 'productId' => esc_html( $item->product_id ), + 'productName' => esc_html( $item->get_order_item_name() ), + 'priceId' => esc_html( $item->price_id ), + 'cartIndex' => esc_html( $item->cart_index ), + 'type' => esc_html( $item->type ), + 'status' => esc_html( $item->status ), + 'statusLabel' => esc_html( edd_get_status_label( $item->status ) ), + 'quantity' => esc_html( $item->quantity ), + 'amount' => esc_html( $item->amount ), + 'subtotal' => esc_html( $item->subtotal ), + 'discount' => esc_html( $item->discount ), + 'tax' => esc_html( $item->tax ), + 'total' => esc_html( $item->total ), + 'dateCreated' => esc_html( $item->date_created ), + 'dateModified' => esc_html( $item->date_modified ), + 'uuid' => esc_html( $item->uuid ), + 'deliverable' => $item->is_deliverable(), + 'adjustments' => $item_adjustments, + ); + } + + $adjustments = edd_get_order_adjustments( array( + 'object_id' => $order->id, + 'number' => 999, + 'object_type' => 'order', + 'type' => array( + 'discount', + 'credit', + 'fee', + ), + ) ); + + foreach ( $adjustments as $adjustment ) { + // @todo edd_get_order_adjustment_to_json()? + $_adjustments[] = array( + 'id' => esc_html( $adjustment->id ), + 'objectId' => esc_html( $adjustment->object_id ), + 'objectType' => esc_html( $adjustment->object_type ), + 'typeId' => esc_html( $adjustment->type_id ), + 'type' => esc_html( $adjustment->type ), + 'description' => esc_html( $adjustment->description ), + 'subtotal' => esc_html( $adjustment->subtotal ), + 'tax' => esc_html( $adjustment->tax ), + 'total' => esc_html( $adjustment->total ), + 'dateCreated' => esc_html( $adjustment->date_created ), + 'dateModified' => esc_html( $adjustment->date_modified ), + 'uuid' => esc_html( $adjustment->uuid ), + ); + } + + $refunds = edd_get_order_refunds( $order->id ); + + foreach ( $refunds as $refund ) { + $_refunds[] = array( + 'id' => esc_html( $refund->id ), + 'number' => esc_html( $refund->order_number ), + 'total' => esc_html( $refund->total ), + 'dateCreated' => esc_html( $refund->date_created ), + 'dateCreatedi18n' => esc_html( edd_date_i18n( $refund->date_created ) ), + 'uuid' => esc_html( $refund->uuid ), + ); + } + } + + $has_tax = 'none'; + $tax_rate = $order->id ? $order->get_tax_rate() : false; + + $location = array( + 'rate' => $tax_rate, + 'country' => '', + 'region' => '', + 'inclusive' => edd_prices_include_tax(), + ); + + if ( edd_is_add_order_page() && edd_use_taxes() ) { + $default_rate = edd_get_tax_rate_by_location( + array( + 'country' => '', + 'region' => '', + ) + ); + if ( $default_rate ) { + $location['rate'] = floatval( $default_rate->amount ); + } + $has_tax = $location; + } elseif ( $tax_rate ) { + $has_tax = $location; + $has_tax['rate'] = $tax_rate; + + if ( $order->tax_rate_id ) { + $tax_rate_object = $order->get_tax_rate_object(); + + if ( $tax_rate_object ) { + $has_tax['country'] = $tax_rate_object->name; + $has_tax['region'] = $tax_rate_object->description; + } + } + } + + $has_quantity = true; + if ( edd_is_add_order_page() && ! edd_item_quantities_enabled() ) { + $has_quantity = false; + } + + wp_localize_script( + 'edd-admin-orders', + 'eddAdminOrderOverview', + array( + 'items' => $_items, + 'adjustments' => $_adjustments, + 'refunds' => $_refunds, + 'isAdding' => true === edd_is_add_order_page(), + 'hasQuantity' => $has_quantity, + 'hasTax' => $has_tax, + 'hasDiscounts' => true === edd_has_active_discounts(), + 'order' => array( + 'status' => $order->status, + 'currency' => $order->currency, + 'currencySymbol' => html_entity_decode( edd_currency_symbol( $order->currency ) ), + 'subtotal' => $order->subtotal, + 'discount' => $order->discount, + 'tax' => $order->tax, + 'total' => $order->total, + ), + 'nonces' => array( + 'edd_admin_order_get_item_amounts' => wp_create_nonce( 'edd_admin_order_get_item_amounts' ), + ), + 'i18n' => array( + 'closeText' => esc_html__( 'Close', 'easy-digital-downloads' ), + ), + ) + ); + + $templates = array( + 'actions', + 'subtotal', + 'tax', + 'total', + 'item', + 'adjustment', + 'adjustment-discount', + 'refund', + 'no-items', + 'copy-download-link', + 'form-add-order-item', + 'form-add-order-discount', + 'form-add-order-adjustment', + ); + + foreach ( $templates as $tmpl ) { + echo ''; + } +?> + +
    + + + + + + + + + + + +
    + +
    +
    + +id ); +} + +/** + * Output the order details sections box + * + * @since 3.0 + * + * @param object $order + */ +function edd_order_details_sections( $order ) { +?> + +
    +

    + +

    + +
    + +id ) + ? $order->get_transaction_id() + : ''; + + $unlimited = ! empty( $order->id ) + ? $order->has_unlimited_downloads() + : false; + + $readonly = ! empty( $order->id ) + ? 'readonly' + : ''; + + // Setup gateway list. + if ( empty( $order->id ) ) { + $known_gateways = edd_get_payment_gateways(); + + $gateways = array(); + + foreach ( $known_gateways as $id => $data ) { + $gateways[ $id ] = esc_html( $data['admin_label'] ); + } + } + + // Filter the transaction ID (here specifically for back-compat) + if ( ! empty( $transaction_id ) ) { + $transaction_id = apply_filters( 'edd_payment_details_transaction_id-' . $order->gateway, $transaction_id, $order->id ); + } ?> + +
    +

    + +

    + +
    +
    + id ); ?> + + + +
    + + gateway ); ?> +
    + +
    +
    + +
    + html->select( + array( + 'name' => 'gateway', + 'class' => 'edd-form-group__input', + 'id' => 'edd_gateway_select', + 'options' => $gateways, + 'selected' => 'manual', + 'show_option_none' => false, + 'show_option_all' => false, + ) + ); // WPCS: XSS ok. + ?> +
    +
    +
    + + +
    +
    + +
    + value="payment_key ); ?>" /> +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + +
    + + id ); // WPCS: XSS ok. ?> +
    + + + +
    + + +
    + + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    + /> + + + +
    +
    +
    + + id ); ?> +
    +
    +
    + +id )->get_recovery_url(); + + $order_date = edd_get_edd_timezone_equivalent_date_from_utc( EDD()->utils->date( $order->date_created, 'utc', true ) ); + + ?> + +
    +

    + +

    + +
    +
    +
    +
    + +
    + +
    +
    + + 'edd-payment-history', + 'order_type' => 'sale', + 'edd-action' => 'trash_order', + 'purchase_id' => absint( $order->id ), + ) ), + 'edd_payment_nonce' + ); + ?> +
    + + + +
    + +
    + + id ) && ! empty( $recovery_url ) ) : ?> +
    +
    + +
    + +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + +
    +
    + + + + +
    + + + : + + + +
    +
    +
    + + id ); ?> + +
    +
    + +
    + + $status, + 'label' => edd_get_payment_status_label( $order_status ), + 'icon' => $icon, + 'class' => "edd-admin-order-status-badge--{$order_status}", + ), + $order_status + ); + $status_badge = new EDD\Utils\StatusBadge( $status_badge_args ); + + /** + * Filters the markup for the order status badge icon. + * + * @since 3.0 + * + * @param string $icon Icon HTML markup. + */ + $icon = apply_filters( 'edd_get_order_status_badge_icon', $status_badge->get_icon(), $order_status ); + + return $status_badge->get( $icon ); +} diff --git a/includes/admin/payments/payments-history.php b/includes/admin/payments/payments-history.php index 1d16e4afc42..3ae69b9c349 100755 --- a/includes/admin/payments/payments-history.php +++ b/includes/admin/payments/payments-history.php @@ -1,333 +1,237 @@ + + + + $type ) { + // Skip if hidden + if ( empty( $type['show_ui'] ) ) { + continue; + } + + // Add to return array + $retval[ $type_id ] = ! empty( $type['labels']['plural'] ) + ? $type['labels']['plural'] + : ucwords( $type_id ); + } + + // Filter & return + return (array) apply_filters( 'edd_get_order_pages', $retval ); +} + +/** + * Get the payment view + * + * @since 3.0 + * + * @return string + */ +function edd_get_payment_view() { + return ! empty( $_GET['view'] ) // WPCS: CSRF ok. + ? sanitize_key( $_GET['view'] ) // WPCS: CSRF ok. + : 'list'; +} + +/** + * Render one of the Order pages. + * + * @since 1.0 + * @since 3.0 Nomenclature updated for consistency. + * Add a link to manually add orders. + * Changed to switch statement. + */ function edd_payment_history_page() { - global $edd_options; - - if(isset($_GET['edd-action']) && $_GET['edd-action'] == 'edit-payment') { - include_once(EDD_PLUGIN_DIR . '/includes/admin/payments/edit-payment.php'); - } else { - - $current_page = admin_url('edit.php?post_type=download&page=edd-payment-history'); + + // What are we viewing? + switch ( edd_get_payment_view() ) { + + // View Order + case 'view-order-details' : + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/view-order-details.php'; + break; + + // Add Order + case 'add-order' : + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/add-order.php'; + edd_add_order_page_content(); + break; + + // View Refund + case 'view-refund-details' : + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/view-refund.php'; + edd_view_refund_page_content(); + break; + + // List Table + case 'list' : + default : + edd_order_list_table_content(); + break; + } +} + +/** + * Output the list table used to list out all orders. + * + * @since 3.0 + */ +function edd_order_list_table_content() { + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/class-payments-table.php'; + $orders_table = new EDD_Payment_History_Table(); + $orders_table->prepare_items(); + + $active_tab = sanitize_key( $orders_table->get_request_var( 'order_type', 'sale' ) ); + $admin_url = edd_get_admin_url( array( 'page' => 'edd-payment-history' ) ); ?> + +
    +

    + 'add-order' ), $admin_url ); + printf( + '%s', + esc_url( $add_new_url ), + esc_html__( 'Add New', 'easy-digital-downloads' ) + ); + } ?> -
    - 0) { - $per_page = intval($_GET['show']); - } - $total_pages = 1; - $offset = $per_page * ($page-1); - - $mode = isset($_GET['mode']) ? $_GET['mode'] : 'live'; - if(edd_is_test_mode() && !isset($_GET['mode'])) $mode = 'test'; - - $orderby = isset( $_GET['orderby'] ) ? $_GET['orderby'] : 'ID'; - $order = isset( $_GET['order'] ) ? $_GET['order'] : 'DESC'; - $order_inverse = $order == 'DESC' ? 'ASC' : 'DESC'; - $order_class = strtolower($order_inverse); - $user = isset( $_GET['user'] ) ? $_GET['user'] : null; - $status = isset( $_GET['status'] ) ? $_GET['status'] : 'any'; - $meta_key = isset( $_GET['meta_key'] ) ? $_GET['meta_key'] : null; - - $payments = edd_get_payments( array( - 'offset' => $offset, - 'number' => $per_page, - 'mode' => $mode, - 'orderby' => $orderby, - 'order' => $order, - 'user' => $user, - 'status' => $status, - 'meta_key' => $meta_key - ) ); - $payment_count = wp_count_posts('edd_payment'); - - $total_count = $payment_count->publish + $payment_count->pending + $payment_count->refunded + $payment_count->trash; - - switch( $status ) { - case 'publish': - $current_count = $payment_count->publish; - break; - case 'pending': - $current_count = $payment_count->pending; - break; - case 'refunded': - $current_count = $payment_count->refunded; - break; - case 'any': - $current_count = $total_count; - break; - } - - $total_pages = ceil($current_count/$per_page); - +
    + + + + + +
    + + + + views(); + $orders_table->advanced_filters(); + $orders_table->display(); ?> -

    - - -
      -
    •  : CSV
    • - -
    - - - - - - - - - - - - - - -
    - display_name : $user; - ?> -

     - 

    - - - - - - - - - - - - - - - - - - - - - - - - - - - ID, '_edd_payment_meta', true); - $user_info = maybe_unserialize($payment_meta['user_info']); - $classes = array(); - $classes[] = edd_is_odd($i) ? 'alternate' : ''; - $payment_classes = get_post_class( apply_filters( 'edd_payment_row_classes', $classes ), $payment->ID ); - ?> - - - - - - - - - - - - -
    - - - - - - - -
    ID; ?> - -
    - '' . __('Edit', 'edd') . '', - 'email_links' => edd_is_payment_complete($payment->ID) ? '' . __('Resend Purchase Receipt', 'edd') . '' : NULL, - 'delete' => '' . __('Delete', 'edd') . '' - ); - $row_actions = apply_filters('edd_payment_row_actions', $row_actions, $payment); - $action_count = count($row_actions); $i = 1; - foreach($row_actions as $key => $action) { - if($action_count == $i) { $sep = ''; } else { $sep = ' | '; } - echo !is_null( $action ) ? '' . $action . '' . $sep : ''; - $i++; - } - ?> -
    -
    - - ID ); ?>post_date)); ?> - - - display_name : __('guest', 'edd'); - } else { - echo __('guest', 'edd'); - } - ?> - -
    -
    - -
    -

     

    - -
    - 1) : ?> -
    - $base, - 'format' => '&p=%#%', - 'prev_text' => '« ' . __('Previous', 'edd'), - 'next_text' => __('Next', 'edd') . ' »', - 'total' => $total_pages, - 'current' => $page, - 'end_size' => 1, - 'mid_size' => 5, - )); - ?> -
    - -
    - -
    - + + +
    + + base ) { + return $admin_title; + } + + // Get the view + $view = edd_get_payment_view(); + + // Which view? + switch ( $view ) { + + // Edit/View + case 'view-order-details': + case 'edit-payment': + $title = __( 'Edit Order', 'easy-digital-downloads' ) . ' — ' . $admin_title; + break; + + // Add + case 'add-order': + $title = __( 'Add New Order', 'easy-digital-downloads' ) . ' — ' . $admin_title; + break; + + // List + case 'list' : + default: + $title = $admin_title; + break; } + + return $title; } +add_filter( 'admin_title', 'edd_view_order_details_title', 10, 2 ); diff --git a/includes/admin/payments/refunds.php b/includes/admin/payments/refunds.php new file mode 100644 index 00000000000..dd5410d1237 --- /dev/null +++ b/includes/admin/payments/refunds.php @@ -0,0 +1,368 @@ + absint( $refund->parent ), + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + ) + ); +?> + +
    +
    + +
    + + + +
    + +
    +   + +
    + +
    +
    + + $refund->id, + 'number' => 999, + ) ); + + foreach ( $items as $item ) { + $item_adjustments = array(); + + $adjustments = edd_get_order_adjustments( array( + 'object_id' => $item->id, + 'number' => 999, + 'object_type' => 'order_item', + 'type' => array( + 'discount', + 'credit', + 'fee', + ), + ) ); + + foreach ( $adjustments as $adjustment ) { + // @todo edd_get_order_adjustment_to_json()? + $adjustment_args = array( + 'id' => esc_html( $adjustment->id ), + 'objectId' => esc_html( $adjustment->object_id ), + 'objectType' => esc_html( $adjustment->object_type ), + 'typeId' => esc_html( $adjustment->type_id ), + 'type' => esc_html( $adjustment->type ), + 'description' => esc_html( $adjustment->description ), + 'subtotal' => esc_html( $adjustment->subtotal ), + 'tax' => esc_html( $adjustment->tax ), + 'total' => esc_html( $adjustment->total ), + 'dateCreated' => esc_html( $adjustment->date_created ), + 'dateModified' => esc_html( $adjustment->date_modified ), + 'uuid' => esc_html( $adjustment->uuid ), + ); + + $item_adjustments[] = $adjustment_args; + $_adjustments[] = $adjustment_args; + } + + // @todo edd_get_order_item_to_json()? + $_items[] = array( + 'id' => esc_html( $item->id ), + 'orderId' => esc_html( $item->order_id ), + 'productId' => esc_html( $item->product_id ), + 'productName' => esc_html( $item->get_order_item_name() ), + 'priceId' => esc_html( $item->price_id ), + 'cartIndex' => esc_html( $item->cart_index ), + 'type' => esc_html( $item->type ), + 'status' => esc_html( $item->status ), + 'quantity' => esc_html( $item->quantity ), + 'amount' => esc_html( $item->amount ), + 'subtotal' => esc_html( $item->subtotal ), + 'discount' => esc_html( $item->discount ), + 'tax' => esc_html( $item->tax ), + 'total' => esc_html( $item->total ), + 'dateCreated' => esc_html( $item->date_created ), + 'dateModified' => esc_html( $item->date_modified ), + 'uuid' => esc_html( $item->uuid ), + ); + } + + $adjustments = edd_get_order_adjustments( array( + 'object_id' => $refund->id, + 'number' => 999, + 'object_type' => 'order', + 'type' => array( + 'discount', + 'credit', + 'fee', + ), + ) ); + + foreach ( $adjustments as $adjustment ) { + // @todo edd_get_order_adjustment_to_json()? + $_adjustments[] = array( + 'id' => esc_html( $adjustment->id ), + 'objectId' => esc_html( $adjustment->object_id ), + 'objectType' => esc_html( $adjustment->object_type ), + 'typeId' => esc_html( $adjustment->type_id ), + 'type' => esc_html( $adjustment->type ), + 'description' => esc_html( $adjustment->description ), + 'subtotal' => esc_html( $adjustment->subtotal ), + 'tax' => esc_html( $adjustment->tax ), + 'total' => esc_html( $adjustment->total ), + 'dateCreated' => esc_html( $adjustment->date_created ), + 'dateModified' => esc_html( $adjustment->date_modified ), + 'uuid' => esc_html( $adjustment->uuid ), + ); + } + + $has_tax = 'none'; + $tax_rate = $refund->id ? $refund->get_tax_rate() : false; + $location = array( + 'rate' => $tax_rate, + 'country' => '', + 'region' => '', + ); + if ( $tax_rate ) { + $has_tax = $location; + $has_tax['rate'] = $tax_rate; + if ( $refund->tax_rate_id ) { + $tax_rate_object = $refund->get_tax_rate_object(); + if ( $tax_rate_object ) { + $has_tax['country'] = $tax_rate_object->name; + $has_tax['region'] = $tax_rate_object->description; + } + } + } + + wp_localize_script( + 'edd-admin-orders', + 'eddAdminOrderOverview', + array( + 'items' => $_items, + 'adjustments' => $_adjustments, + 'refunds' => array(), + 'isAdding' => false, + 'isRefund' => true, + 'hasQuantity' => true, + 'hasTax' => $has_tax, + 'order' => array( + 'currency' => $refund->currency, + 'currencySymbol' => html_entity_decode( edd_currency_symbol( $refund->currency ) ), + 'subtotal' => $refund->subtotal, + 'discount' => $refund->discount, + 'tax' => $refund->tax, + 'total' => $refund->total, + ), + ) + ); + + $templates = array( + 'no-items', + 'subtotal', + 'tax', + 'total', + 'item', + 'adjustment', + 'adjustment-discount', + ); + + foreach ( $templates as $tmpl ) { + echo ''; + } +?> + +
    + + + + + + + + + +
    +
    + + + +
    +

    + +

    + +
    + +
    +
    + +utils->date( $refund->date_created, 'utc', true ) ); + + $trash_url = wp_nonce_url( + edd_get_admin_url( array( + 'edd-action' => 'trash_order', + 'purchase_id' => absint( $refund->id ), + 'order_type' => 'refund', + 'page' => 'edd-payment-history', + ) ), + 'edd_payment_nonce' + ); + + $order_url = edd_get_admin_url( + array( + 'id' => absint( $refund->parent ), + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + ) + ); + + $order = edd_get_order( $refund->parent ); +?> + +
    +

    + +

    + +
    + + +

    + + + + +
    + +
    +
    + + + + +
    +
    +
    + +parent ), + function( $related_refund ) use ( $refund ) { + return $related_refund->id !== $refund->id; + } + ); + + if ( empty( $refunds ) ) { + return; + } +?> + +
    +

    + +

    + + 'edd-payment-history', + 'view' => 'view-refund-details', + 'id' => absint( $refund->id ), + ) ); + ?> +
    +
    + + number ); ?> + +
    + +
    +
    + +
    + +type ) { + $refund_link = edd_get_admin_url( + array( + 'page' => 'edd-payment-history', + 'view' => 'view-refund-details', + 'id' => urlencode( $order->id ), + ) + ); + wp_die( sprintf( __( 'The specified ID is for a refund, not an order. Please access the refund directly.', 'easy-digital-downloads' ), esc_url( $refund_link ) ), __( 'Error', 'easy-digital-downloads' ) ); +} + +wp_enqueue_script( 'edd-admin-orders' ); +// Enqueued for backwards compatibility. Empty file. +wp_enqueue_script( 'edd-admin-payments' ); +?> + +
    + + + +
    +

    number ) ); ?>

    + +
    + + + + id ); ?> + + id ); ?> + +
    +
    +
    +
    +
    + id ); + + // Overview + edd_order_details_overview( $order ); + + // Details sections + edd_order_details_sections( $order ); + + // Legacy hook from pre version 3 of Easy Digital Downloads. + do_action( 'edd_view_order_details_billing_after', $order->id ); + + // After body + do_action( 'edd_view_order_details_main_after', $order->id ); + + ?> +
    +
    + +
    +
    + id ); + + // Attributes + edd_order_details_attributes( $order ); + + // Extras + edd_order_details_extras( $order ); + + // After sidebar + do_action( 'edd_view_order_details_sidebar_after', $order->id ); + + ?> +
    +
    +
    +
    +
    + + id ); + + wp_nonce_field( 'edd_update_payment_details_nonce' ); ?> + + + + + id ); ?> + +
    + +
    + +
    + + diff --git a/includes/admin/payments/view-refund.php b/includes/admin/payments/view-refund.php new file mode 100644 index 00000000000..b3220e25b0c --- /dev/null +++ b/includes/admin/payments/view-refund.php @@ -0,0 +1,128 @@ +type ) { + wp_die( __( 'The specified ID does not belong to an refund. Please try again.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ) ); + } + + wp_enqueue_script( 'edd-admin-orders' ); + // Enqueued for backwards compatibility. Empty file. + wp_enqueue_script( 'edd-admin-payments' ); +?> + + + +
    + +

    order_number ); ?>

    + + id ); + ?> + +
    +
    +
    + +
    +
    + id ); + + // Refund Items. + edd_refund_details_items( $refund ); + + // Notes. + edd_refund_details_notes( $refund ); + + /** + * Allows further output after the Refund details. + * + * @since 3.0 + * + * @param int $refund_id ID of the current Refund. + */ + do_action( 'edd_view_refund_details_main_after', $refund->id ); + ?> +
    +
    + +
    +
    + id ); + + // Attributes. + edd_refund_details_attributes( $refund ); + + // Related Refunds. + edd_refund_details_related_refunds( $refund ); + + /** + * Allows further output after Refund sidebar content. + * + * @since 3.0 + * + * @param int $refund_id ID of the current Refund. + */ + do_action( 'edd_view_refund_details_sidebar_after', $refund->id ); + ?> +
    +
    + +
    +
    +
    + +
    + + 'all-plugins', + 'utm_content' => 'upgrade-to-pro', + ) + ); + + $edd_links['edd-pro-upgrade'] = sprintf( '' . __( 'Upgrade to Pro', 'easy-digital-downloads' ) . '', $url ); + } + + $settings_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + ) + ); + + $edd_links['settings'] = '' . esc_html__( 'Settings', 'easy-digital-downloads' ) . ''; + + // Return array of links. + return array_merge( $edd_links, $links ); +} +add_filter( 'plugin_action_links_easy-digital-downloads/easy-digital-downloads.php', 'edd_plugin_action_links', 10, 2 ); + +/** + * Load any CSS we need for the plugins list table. + */ +function edd_plugin_list_styles() { + echo ''; +} +add_action( 'admin_print_styles-plugins.php', 'edd_plugin_list_styles' ); diff --git a/includes/admin/promos/email-summary/blurbs.json b/includes/admin/promos/email-summary/blurbs.json new file mode 100644 index 00000000000..19c5204de6c --- /dev/null +++ b/includes/admin/promos/email-summary/blurbs.json @@ -0,0 +1,54 @@ +{ + "blurbs":[ + { + "headline":"Are new features and products getting ignored?", + "content":"Smart marketers know that emails with high click-through rates that promote new products and features will lead to more sales. Connect your Easy Digital Downloads powered store to your favorite email marketing service to boost revenue.", + "button_text":"Read More", + "button_link":"https://easydigitaldownloads.com/downloads/category/extensions/email/?utm_source=plugin&utm_medium=email&utm_campaign=summaries", + "conditions":{ + "current_pass":"free" + } + }, + { + "headline":"Are you missing out on future revenue?", + "content":"On average, up to 60% of customers do not manually renew their license keys. That's lost revenue for your business. Enable subscriptions on your licensed products and keep the revenue flowing!", + "button_text":"Read More", + "button_link":"https://easydigitaldownloads.com/downloads/recurring-payments/?utm_source=plugin&utm_medium=email&utm_campaign=summaries", + "conditions":{ + "active_plugins":[ + "EDD-Software-Licensing/edd-software-licenses.php" + ], + "inactive_plugins":[ + "edd-recurring/edd-recurring.php" + ], + "current_pass":"pass-any" + } + }, + { + "headline":"Are you giving things away for free?", + "content":"Lead magnets are the easiest way to capture potential customers. Use our Free Downloads add-on, and start capturing those leads today.", + "button_text":"Read More", + "button_link":"https://easydigitaldownloads.com/blog/how-to-add-lead-magnets-in-wordpress-to-grow-your-email-list/?utm_source=plugin&utm_medium=email&utm_campaign=summaries", + "conditions":{ + "has_downloads":[ + "free" + ], + "inactive_plugins":[ + "edd-free-downloads/edd-free-downloads.php" + ] + } + }, + { + "headline":"Who are your biggest fans?", + "content":"Did you know that 72% of customers say that a product review is the key to making their purchasing decision? Learn how to add reviews to your store, and let your happy customers be your brand ambassadors!", + "button_text":"Read More", + "button_link":"https://easydigitaldownloads.com/blog/how-to-add-product-reviews-to-your-website/?utm_source=plugin&utm_medium=email&utm_campaign=summaries", + "conditions":{ + "inactive_plugins":[ + "edd-reviews/edd-reviews.php" + ], + "current_pass":"pass-any" + } + } + ] +} diff --git a/includes/admin/promos/notices/class-five-star-review-dashboard.php b/includes/admin/promos/notices/class-five-star-review-dashboard.php new file mode 100644 index 00000000000..9a5c33c22d0 --- /dev/null +++ b/includes/admin/promos/notices/class-five-star-review-dashboard.php @@ -0,0 +1,191 @@ + +
    + _display(); + ?> +
    + +
    +

    +
    +
    + +
    +
    + + + + + time() ) { + return false; + } + $orders = edd_count_orders( + array( + 'type' => 'sale', + 'status__in' => edd_get_complete_order_statuses(), + ) + ); + + return $orders >= 15; + } + + /** + * Builds the UTM parameters for the URLs. + * + * @since 2.11.4 + * + * @return string + */ + private function url() { + $url = edd_link_helper( + 'https://easydigitaldownloads.com/plugin-feedback/', + array( + 'utm_medium' => 'feedback-' . static::TYPE, + 'utm_content' => 'give-feedback', + ) + ); + + return $url; + } +} diff --git a/includes/admin/promos/notices/class-five-star-review-settings.php b/includes/admin/promos/notices/class-five-star-review-settings.php new file mode 100644 index 00000000000..ed9427d5607 --- /dev/null +++ b/includes/admin/promos/notices/class-five-star-review-settings.php @@ -0,0 +1,68 @@ + +
    + +
    + __( 'Log ID', 'easy-digital-downloads' ), + 'details' => __( 'Request Details', 'easy-digital-downloads' ), + 'version' => __( 'API Version', 'easy-digital-downloads' ), + 'ip' => __( 'Request IP', 'easy-digital-downloads' ), + 'speed' => __( 'Request Speed', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ) + ); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'ID'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 1.5 + * + * @param array $item Contains all the data of the api request + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'ip': + return '' . esc_html( $item['ip'] ) . ''; + default: + return $item[ $column_name ]; + } + } + + /** + * Output Error Message column + * + * @since 1.5 + * @param array $item Contains all the data of the log + * @return void + */ + public function column_details( $item ) { + ?> + + + $log->id, + 'version' => $log->version, + 'speed' => $log->time, + 'ip' => $log->ip, + 'date' => $log->date_created, + 'api_key' => $log->api_key, + 'request' => $log->request, + 'error' => $log->error, + 'user_id' => $log->user_id, + ); + } + } + + return $logs_data; + } + + /** + * Get the total number of items + * + * @since 3.0 + * + * @param array $log_query + * + * @return int + */ + public function get_total( $log_query = array() ) { + return edd_count_api_request_logs( $log_query ); + } +} diff --git a/includes/admin/reporting/class-base-logs-list-table.php b/includes/admin/reporting/class-base-logs-list-table.php new file mode 100644 index 00000000000..4529d1ea34d --- /dev/null +++ b/includes/admin/reporting/class-base-logs-list-table.php @@ -0,0 +1,560 @@ + 'log', + 'plural' => 'logs', + 'ajax' => false + ) ); + + $this->filter_bar_hooks(); + } + + /** + * Generate the table navigation above or below the table + * + * Removes the referrer nonce from parent class. + * + * @since 3.0.0 + * @param string $which + */ + protected function display_tablenav( $which ) { + ?> +
    + has_items() ) : ?> +
    + bulk_actions( $which ); ?> +
    + extra_tablenav( $which ); + $this->pagination( $which ); ?> + +
    +
    id ) ) { + $ret = $customer->id; + } + } + + return $ret; + } + + /** + * Return the start-date of the filter + * + * @since 3.0 + * + * @return string Start date to filter by + */ + public function get_filtered_start_date() { + return sanitize_text_field( $this->get_request_var( 'start-date', null ) ); + } + + /** + * Return the end-date of the filter + * + * @since 3.0 + * + * @return string End date to filter by + */ + public function get_filtered_end_date() { + return sanitize_text_field( $this->get_request_var( 'end-date', null ) ); + } + + /** + * Return the ID of the download we're filtering logs by + * + * @since 3.0 + * + * @return int Download ID. + */ + public function get_filtered_download() { + return absint( $this->get_request_var( 'download', false ) ); + } + + /** + * Return the ID of the payment we're filtering logs by + * + * @since 3.0 + * + * @return int Payment ID. + */ + public function get_filtered_payment() { + return absint( $this->get_request_var( 'payment', false ) ); + } + + /** + * Gets the meta query for the log query. + * + * This is used to return log entries that match our search query, user query, or download query. + * + * @since 3.0 + * + * @return array $meta_query + */ + public function get_meta_query() { + return array(); + } + + /** + * Outputs the log views. + * + * @since 3.0 + */ + public function bulk_actions( $which = '' ) { + return; + } + + /** + * Renders the Reports page views drop down + * + * @since 3.0 + * @return void + */ + public function log_views() { + $views = edd_log_default_views(); + $current_view = $this->get_filtered_view(); ?> + + + + + + + + + + + 'edd-log-download-filter', + 'name' => 'download', + 'chosen' => true, + ); + + if ( ! empty( $download ) ) { + $args['selected'] = $download; + } + + echo EDD()->html->product_dropdown( $args ); + } + + /** + * Gets the log entries for the current view + * + * @since 3.0 + * + * @return array $logs_data Array of all the logs. + */ + function get_logs( $log_query = array() ) { + return array(); + } + + /** + * Get the total number of items + * + * @since 3.0 + * + * @param array $log_query + * + * @return int + */ + public function get_total( $log_query = array() ) { + return count( array() ); + } + + /** + * Empty method to hide view links on all logs table + * + * @since 3.0 + */ + public function get_views() { + // Intentionally empty + } + + /** + * Retrieves the logs data. + * + * @since 3.0 + * + * @return array Logs data. + */ + public function get_data() { + $log_query = $this->get_query_args(); + + return $this->get_logs( $log_query ); + } + + /** + * Setup the final data for the table. + * + * @since 3.0 + */ + public function prepare_items() { + + $this->_column_headers = array( + $this->get_columns(), + array(), + $this->get_sortable_columns() + ); + + $this->items = $this->get_data(); + $log_query = $this->get_query_args( false ); + $total_items = $this->get_total( $log_query ); + + $this->set_pagination_args( array( + 'total_pages' => ceil( $total_items / $this->per_page ), + 'total_items' => $total_items, + 'per_page' => $this->per_page, + ) ); + } + + /** + * Return array of query arguments + * + * @since 3.0 + * + * @param bool $paginate Whether to add pagination arguments + * + * @return array + */ + protected function get_query_args( $paginate = true ) { + + // Defaults + $retval = array( + 'product_id' => $this->get_filtered_download(), + 'customer_id' => $this->get_filtered_customer(), + 'order_id' => $this->get_filtered_payment(), + 'meta_query' => $this->get_meta_query(), + ); + + // Search + $search = $this->get_search(); + if ( ! empty( $search ) ) { + if ( filter_var( $search, FILTER_VALIDATE_IP ) ) { + $retval['ip'] = $search; + } elseif ( is_email( $search ) ) { + if ( 'api_requests' === $this->log_type ) { + // API requests are linked to user accounts, so we're checking user data here. + $user = get_user_by( 'email', $search ); + if ( ! empty( $user->ID ) ) { + $retval['user_id'] = $user->ID; + } else { + // This is a fallback to help ensure an invalid email will produce zero results. + $retval['search'] = $search; + } + } else { + // All other logs are linked to customers. + $customer = edd_get_customer_by( 'email', $search ); + if ( ! empty( $customer->id ) ) { + $retval['customer_id'] = $customer->id; + } else { + // This is a fallback to help ensure an invalid email will produce zero results. + $retval['search'] = $search; + } + } + } elseif ( 'api_requests' === $this->log_type && 32 === strlen( $search ) ) { + // Look for an API key + $retval['api_key'] = $search; + } elseif ( 'api_requests' === $this->log_type && stristr( $search, 'token:' ) ) { + // Look for an API token + $retval['token'] = str_ireplace( 'token:', '', $search ); + } elseif ( is_numeric( $search ) ) { + if ( 'api_requests' === $this->log_type ) { + // API requests are linked to user accounts, so we're checking user data here. + $user = get_user_by( 'email', $search ); + if ( ! empty( $user->ID ) ) { + $retval['user_id'] = $user->ID; + } else { + $retval['search'] = $search; + } + } else { + // All other logs are linked to customers. + $customer = edd_get_customer( $search ); + if ( ! empty( $customer->id ) ) { + $retval['customer_id'] = $customer->id; + } elseif ( 'file_downloads' === $this->log_type ) { + $retval['product_id'] = $search; + } else { + $retval['search'] = $search; + } + } + } else { + if ( 'file_downloads' === $this->log_type ) { + $this->file_search = true; + } else { + $retval['search'] = $search; + } + } + } + + // Start date + $start_date = $this->get_filtered_start_date(); + $end_date = $this->get_filtered_end_date(); + + // Setup original array + if ( ! empty( $start_date ) || ! empty( $end_date ) ) { + $retval['date_created_query']['column'] = 'date_created'; + + // Start date + if ( ! empty( $start_date ) ) { + $retval['date_created_query'][] = array( + 'column' => 'date_created', + 'after' => \Carbon\Carbon::parse( date( 'Y-m-d H:i:s', strtotime( "{$start_date} midnight" ) ), edd_get_timezone_id() )->setTimezone( 'UTC' )->toDateTimeString(), + ); + } + + // End date + if ( ! empty( $end_date ) ) { + $retval['date_created_query'][] = array( + 'column' => 'date_created', + 'before' => \Carbon\Carbon::parse( date( 'Y-m-d H:i:s', strtotime( "{$end_date} + 1 day" ) ), edd_get_timezone_id() )->setTimezone( 'UTC' )->toDateTimeString(), + ); + } + } + + $retval = array_filter( $retval ); + + // Return query arguments + return ( true === $paginate ) + ? $this->parse_pagination_args( $retval ) + : $retval; + } + + /** + * Output advanced filters for payments + * + * @since 3.0 + */ + public function advanced_filters() { + edd_admin_filter_bar( 'logs' ); + } + + /** + * Output filter bar items + * + * @since 3.0 + */ + public function filter_bar_items() { + + // Get values + $start_date = $this->get_filtered_start_date(); + $end_date = $this->get_filtered_end_date(); + $download = $this->get_filtered_download(); + $customer = $this->get_filtered_customer(); + $view = $this->get_filtered_view(); + $clear_url = edd_get_admin_url( array( + 'page' => 'edd-tools', + 'tab' => 'logs', + 'view' => sanitize_key( $view ), + ) ); ?> + + + log_views(); ?> + + + + html->date_field( array( + 'id' => 'start-date', + 'name' => 'start-date', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ), + 'value' => $start_date + ) ); + + echo EDD()->html->date_field( array( + 'id' => 'end-date', + 'name' => 'end-date', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ), + 'value' => $end_date + ) ); + + ?> + + + downloads_filter( $download ); ?> + + + + + + + + + + + + + + + + + + + search_box( __( 'Search', 'easy-digital-downloads' ), 'edd-logs' ); + } + + /** + * Show the search field + * + * @since 3.0 + * + * @param string $text Label for the search box + * @param string $input_id ID of the search box + * + * @return void + */ + public function search_box( $text, $input_id ) { + + // Bail if no customers and no search + if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { + return; + } + + $input_id = $input_id . '-search-input'; + + if ( ! empty( $_REQUEST['orderby'] ) ) { + echo ''; + } + + if ( ! empty( $_REQUEST['order'] ) ) { + echo ''; + } + + ?> + +

    + + +

    + + 'report-earning', + 'plural' => 'report-earnings', + 'ajax' => false + ) ); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'label'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 2.4 + * + * @param array $item Contains all the data of the downloads + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + return $item[ $column_name ]; + } + + /** + * Retrieve the table columns + * + * @since 2.4 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'label' => __( 'Category', 'easy-digital-downloads' ), + 'total_sales' => __( 'Total Sales', 'easy-digital-downloads' ), + 'total_earnings' => __( 'Total Earnings', 'easy-digital-downloads' ), + 'avg_sales' => __( 'Monthly Sales Avg', 'easy-digital-downloads' ), + 'avg_earnings' => __( 'Monthly Earnings Avg', 'easy-digital-downloads' ) + ); + } + + /** + * Outputs the reporting views + * + * @since 1.5 + * @return void + */ + public function display_tablenav( $which = '' ) { + ?> +
    +
    + +
    +
    + get_data(); + } + + /** + * Builds and retrieves all of the categories reports data. + * + * @since 3.0 + * + * @return array Categories reports table data. + */ + public function get_data() { + + /* + * Date filtering + */ + $dates = edd_get_report_dates(); + + $include_taxes = empty( $_GET['exclude_taxes'] ) ? true : false; + + if ( ! empty( $dates[ 'year' ] ) ) { + $date = new DateTime(); + $date->setDate( $dates[ 'year' ], $dates[ 'm_start' ], $dates[ 'day' ] ); + $start_date = $date->format( 'Y-m-d' ); + + $date->setDate( $dates[ 'year_end' ], $dates[ 'm_end' ], $dates[ 'day_end' ] ); + $end_date = $date->format( 'Y-m-d' ); + $cached_report_key = 'edd_earnings_by_category_data' . $start_date . '_' . $end_date; + } else { + $start_date = false; + $end_date = false; + $cached_report_key = 'edd_earnings_by_category_data'; + } + + $cached_reports = get_transient( $cached_report_key ); + + if ( false !== $cached_reports ) { + $reports_data = $cached_reports; + + } else { + $reports_data = array(); + $categories = get_terms( 'download_category', array( + 'parent' => 0, + 'hierarchical' => 0, + 'hide_empty' => false + ) ); + + foreach ( $categories as $category ) { + + $category_slugs = array( $category->slug ); + $child_terms = get_terms( 'download_category', array( + 'parent' => $category->term_id, + 'hierarchical' => 0 + ) ); + + if ( ! empty( $child_terms ) ) { + foreach ( $child_terms as $child_term ) { + $category_slugs[] = $child_term->slug; + } + } + + $downloads = get_posts( array( + 'post_type' => 'download', + 'posts_per_page' => -1, + 'fields' => 'ids', + 'tax_query' => array( + array( + 'taxonomy' => 'download_category', + 'field' => 'slug', + 'terms' => $category_slugs + ) + ) + ) ); + + $sales = $avg_sales = 0; + $earnings = $avg_earnings = 0.00; + + foreach ( $downloads as $download ) { + $current_sales = EDD()->payment_stats->get_sales( $download, $start_date, $end_date ); + $current_earnings = EDD()->payment_stats->get_earnings( $download, $start_date, $end_date, $include_taxes ); + + $current_average_sales = edd_get_average_monthly_download_sales( $download ); + $current_average_earnings = edd_get_average_monthly_download_earnings( $download ); + + $sales += $current_sales; + $earnings += $current_earnings; + $avg_sales += $current_average_sales; + $avg_earnings += $current_average_earnings; + } + + $avg_earnings = round( $avg_earnings, edd_currency_decimal_filter() ); + if ( ! empty( $avg_earnings ) && $avg_sales < 1 ) { + $avg_sales = __( 'Less than 1', 'easy-digital-downloads' ); + } else { + $avg_sales = round( edd_format_amount( $avg_sales, false ) ); + } + + $reports_data[] = array( + 'ID' => $category->term_id, + 'label' => $category->name, + 'total_sales' => edd_format_amount( $sales, false ), + 'total_sales_raw' => $sales, + 'total_earnings' => edd_currency_filter( edd_format_amount( $earnings ) ), + 'total_earnings_raw' => $earnings, + 'avg_sales' => $avg_sales, + 'avg_earnings' => edd_currency_filter( edd_format_amount( $avg_earnings ) ), + 'is_child' => false, + ); + + if ( ! empty( $child_terms ) ) { + foreach ( $child_terms as $child_term ) { + $child_downloads = get_posts( array( + 'post_type' => 'download', + 'posts_per_page' => -1, + 'fields' => 'ids', + 'tax_query' => array( + array( + 'taxonomy' => 'download_category', + 'field' => 'slug', + 'terms' => $child_term->slug + ) + ) + ) ); + + $child_sales = $child_avg_sales = 0; + $child_earnings = $child_avg_earnings = 0.00; + + foreach ( $child_downloads as $child_download ) { + $current_average_sales = $current_sales = EDD()->payment_stats->get_sales( $child_download, $start_date, $end_date ); + $current_average_earnings = $current_earnings = EDD()->payment_stats->get_earnings( $child_download, $start_date, $end_date ); + + $release_date = get_post_field( 'post_date', $child_download ); + $diff = abs( current_time( 'timestamp' ) - strtotime( $release_date ) ); + $months = floor( $diff / ( 30 * 60 * 60 * 24 ) ); // Number of months since publication + + if ( $months > 0 ) { + $current_average_sales = ( $current_sales / $months ); + $current_average_earnings = ( $current_earnings / $months ); + } + + $child_sales += $current_sales; + $child_earnings += $current_earnings; + $child_avg_sales += $current_average_sales; + $child_avg_earnings += $current_average_earnings; + } + + $child_avg_sales = round( $child_avg_sales / count( $child_downloads ) ); + $child_avg_earnings = round( $child_avg_earnings / count( $child_downloads ), edd_currency_decimal_filter() ); + + $reports_data[] = array( + 'ID' => $child_term->term_id, + 'label' => '— ' . $child_term->name, + 'total_sales' => edd_format_amount( $child_sales, false ), + 'total_sales_raw' => $child_sales, + 'total_earnings' => edd_currency_filter( edd_format_amount( $child_earnings ) ), + 'total_earnings_raw' => $child_earnings, + 'avg_sales' => edd_format_amount( $child_avg_sales, false ), + 'avg_earnings' => edd_currency_filter( edd_format_amount( $child_avg_earnings ) ), + 'is_child' => true + ); + } + } + } + } + + return $reports_data; + } + + /** + * Output the Category Sales Mix Pie Chart + * + * @since 2.4 + * @return string The HTML for the outputted graph + */ + public function output_sales_graph() { + if ( empty( $this->items ) ) { + return; + } + + $data = array(); + $total_sales = 0; + + foreach ( $this->items as $item ) { + $total_sales += $item['total_sales_raw']; + + if ( ! empty( $item[ 'is_child' ] ) || empty( $item[ 'total_sales_raw' ] ) ) { + continue; + } + + $data[ $item[ 'label' ] ] = $item[ 'total_sales_raw' ]; + } + + + if ( empty( $total_sales ) ) { + echo '

    ' . __( 'No sales for dates provided.', 'easy-digital-downloads' ) . '

    '; + } + + // Sort High to Low, prior to filter so people can reorder if they please + arsort( $data ); + $data = apply_filters( 'edd_category_sales_graph_data', $data ); + + $options = apply_filters( 'edd_category_sales_graph_options', array( + 'legend_formatter' => 'eddLegendFormatterSales', + ), $data ); + + $pie_graph = new EDD_Pie_Graph( $data, $options ); + $pie_graph->display(); + } + + /** + * Output the Category Earnings Mix Pie Chart + * + * @since 2.4 + * @return string The HTML for the outputted graph + */ + public function output_earnings_graph() { + if ( empty( $this->items ) ) { + return; + } + + $data = array(); + $total_earnings = 0; + + foreach ( $this->items as $item ) { + $total_earnings += $item['total_earnings_raw']; + + if ( ! empty( $item[ 'is_child' ] ) || empty( $item[ 'total_earnings_raw' ] ) ) { + continue; + } + + $data[ $item[ 'label' ] ] = $item[ 'total_earnings_raw' ]; + + } + + if ( empty( $total_earnings ) ) { + echo '

    ' . __( 'No earnings for dates provided.', 'easy-digital-downloads' ) . '

    '; + } + + // Sort High to Low, prior to filter so people can reorder if they please + arsort( $data ); + $data = apply_filters( 'edd_category_earnings_graph_data', $data ); + + $options = apply_filters( 'edd_category_earnings_graph_options', array( + 'legend_formatter' => 'eddLegendFormatterEarnings', + ), $data ); + + $pie_graph = new EDD_Pie_Graph( $data, $options ); + $pie_graph->display(); + } + + /** + * Setup the final data for the table + * + * @since 2.4 + * @uses EDD_Categories_Reports_Table::get_columns() + * @uses EDD_Categories_Reports_Table::get_sortable_columns() + * @uses EDD_Categories_Reports_Table::reports_data() + * @return void + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); // No hidden columns + $sortable = $this->get_sortable_columns(); + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } +} diff --git a/includes/admin/reporting/class-download-reports-table.php b/includes/admin/reporting/class-download-reports-table.php new file mode 100755 index 00000000000..9a734c4f5a9 --- /dev/null +++ b/includes/admin/reporting/class-download-reports-table.php @@ -0,0 +1,298 @@ + 'report-download', + 'plural' => 'report-downloads', + 'ajax' => false + ) ); + + add_action( 'edd_report_view_actions', array( $this, 'category_filter' ) ); + + $this->query(); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'title'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 1.5 + * + * @param array $item Contains all the data of the downloads + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + switch( $column_name ){ + case 'earnings' : + return edd_currency_filter( edd_format_amount( $item[ $column_name ] ) ); + case 'average_sales' : + return round( $item[ $column_name ] ); + case 'average_earnings' : + return edd_currency_filter( edd_format_amount( $item[ $column_name ] ) ); + case 'details' : + $url = edd_get_admin_url( + array( + 'page' => 'edd-reports', + 'view' => 'downloads', + 'download-id' => absint( $item['ID'] ), + ) + ); + return '' . __( 'View Detailed Report', 'easy-digital-downloads' ) . ''; + default: + return $item[ $column_name ]; + } + } + + /** + * Retrieve the table columns + * + * @since 1.5 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'title' => edd_get_label_singular(), + 'sales' => __( 'Sales', 'easy-digital-downloads' ), + 'earnings' => __( 'Earnings', 'easy-digital-downloads' ), + 'average_sales' => __( 'Monthly Average Sales', 'easy-digital-downloads' ), + 'average_earnings' => __( 'Monthly Average Earnings', 'easy-digital-downloads' ), + 'details' => __( 'Detailed Report', 'easy-digital-downloads' ) + ); + } + + /** + * Retrieve the sortable columns + * + * @since 1.4 + * @return array Array of all the sortable columns + */ + public function get_sortable_columns() { + return array( + 'title' => array( 'title', true ), + 'sales' => array( 'sales', false ), + 'earnings' => array( 'earnings', false ) + ); + } + + /** + * Retrieve the category being viewed + * + * @since 1.5.2 + * @return int Category ID + */ + public function get_category() { + return absint( $this->get_request_var( 'category', 0 ) ); + } + + /** + * Retrieve the total number of downloads + * + * @since 1.5 + * @return int $total Total number of downloads + */ + public function get_total_downloads() { + $total = 0; + $counts = wp_count_posts( 'download', 'readable' ); + + foreach( $counts as $count ) { + $total += $count; + } + + return $total; + } + + /** + * Outputs the reporting views + * + * These aren't really bulk actions but this outputs the markup in the + * right place. + * + * @since 1.5 + * @return void + */ + public function bulk_actions( $which = '' ) { + edd_report_views(); + } + + /** + * Attaches the category filter to the log views + * + * @since 1.5.2 + * @return void + */ + public function category_filter() { + if ( get_terms( 'download_category' ) ) { + echo EDD()->html->category_dropdown( 'category', $this->get_category() ); + } + } + + /** + * Performs the products query + * + * @since 1.5.2 + * @return void + */ + public function query() { + + $orderby = sanitize_text_field( $this->get_request_var( 'orderby', 'title' ) ); + $order = sanitize_text_field( $this->get_request_var( 'order', 'DESC' ) ); + $category = $this->get_category(); + + $args = array( + 'post_type' => 'download', + 'post_status' => 'publish', + 'order' => $order, + 'fields' => 'ids', + 'posts_per_page' => $this->per_page, + 'paged' => $this->get_paged(), + 'suppress_filters' => true + ); + + if ( ! empty( $category ) ) { + $args['tax_query'] = array( + array( + 'taxonomy' => 'download_category', + 'terms' => $category + ) + ); + } + + switch ( $orderby ) { + case 'title' : + $args['orderby'] = 'title'; + break; + + case 'sales' : + $args['orderby'] = 'meta_value_num'; + $args['meta_key'] = '_edd_download_sales'; + break; + + case 'earnings' : + $args['orderby'] = 'meta_value_num'; + $args['meta_key'] = '_edd_download_earnings'; + break; + } + + $r = apply_filters( 'edd_download_reports_prepare_items_args', $args, $this ); + + $this->products = new WP_Query( $r ); + } + + /** + * Build and retrieves all of the download reports data. + * + * @since 1.5 + * @deprecated 3.0 Use get_data() + * + * @return array All the data for customer reports. + */ + public function reports_data() { + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Download_Reports_Table::get_data()' ); + + return $this->get_data(); + } + + /** + * Retrieves all of the download reports data. + * + * @since 3.0 + * + * @return array Download reports table data. + */ + public function get_data() { + $reports_data = array(); + + $downloads = $this->products->posts; + + if ( $downloads ) { + foreach ( $downloads as $download ) { + $reports_data[] = array( + 'ID' => $download, + 'title' => get_the_title( $download ), + 'sales' => edd_get_download_sales_stats( $download ), + 'earnings' => edd_get_download_earnings_stats( $download ), + 'average_sales' => edd_get_average_monthly_download_sales( $download ), + 'average_earnings' => edd_get_average_monthly_download_earnings( $download ), + ); + } + } + + return $reports_data; + } + + /** + * Setup the final data for the table + * + * @since 1.5 + * @uses EDD_Download_Reports_Table::get_columns() + * @uses EDD_Download_Reports_Table::get_sortable_columns() + * @uses EDD_Download_Reports_Table::get_total_downloads() + * @uses EDD_Download_Reports_Table::get_data() + * @uses EDD_Download_Reports_Table::set_pagination_args() + * @return void + */ + public function prepare_items() { + $this->_column_headers = array( + $this->get_columns(), + array(), + $this->get_sortable_columns() + ); + + $total_items = $this->get_total_downloads(); + $this->items = $this->get_data(); + + $this->set_pagination_args( array( + 'total_pages' => ceil( $total_items / $this->per_page ), + 'total_items' => $total_items, + 'per_page' => $this->per_page, + ) ); + } +} diff --git a/includes/admin/reporting/class-edd-graph.php b/includes/admin/reporting/class-edd-graph.php new file mode 100755 index 00000000000..0cd1e1317de --- /dev/null +++ b/includes/admin/reporting/class-edd-graph.php @@ -0,0 +1,291 @@ + array( + array( 1, 5 ), + array( 3, 8 ), + array( 10, 2 ) + ), + + 'Second Label' => array( + array( 1, 7 ), + array( 4, 5 ), + array( 12, 8 ) + ) + ); + + $graph = new EDD_Graph( $data ); + $graph->display(); + + */ + + /** + * Data to graph + * + * @var array + * @since 1.9 + */ + private $data; + + /** + * Unique ID for the graph + * + * @var string + * @since 1.9 + */ + private $id = ''; + + /** + * Graph options + * + * @var array + * @since 1.9 + */ + private $options = array(); + + /** + * Get things started + * + * @since 1.9 + */ + public function __construct( $_data ) { + + $this->data = $_data; + + // Generate unique ID + $this->id = 'a' . md5( rand() ); + + // Setup default options; + $this->options = array( + 'y_mode' => null, + 'x_mode' => null, + 'y_decimals' => 0, + 'x_decimals' => 0, + 'y_position' => 'right', + 'time_format' => '%d/%b', + 'ticksize_unit' => 'day', + 'ticksize_num' => 1, + 'multiple_y_axes' => false, + 'bgcolor' => '#f9f9f9', + 'bordercolor' => '#ccc', + 'color' => '#bbb', + 'borderwidth' => 2, + 'bars' => false, + 'lines' => true, + 'points' => true, + 'additional_options' => '', + ); + + } + + /** + * Set an option + * + * @param $key The option key to set + * @param $value The value to assign to the key + * @since 1.9 + */ + public function set( $key, $value ) { + $this->options[ $key ] = $value; + } + + /** + * Get an option + * + * @param $key The option key to get + * @since 1.9 + */ + public function get( $key ) { + return isset( $this->options[ $key ] ) ? $this->options[ $key ] : false; + } + + /** + * Get graph data + * + * @since 1.9 + */ + public function get_data() { + return apply_filters( 'edd_get_graph_data', $this->data, $this ); + } + + /** + * Load the graphing library script + * + * @since 1.9 + */ + public function load_scripts() { + wp_enqueue_script( 'edd-jquery-flot', EDD_PLUGIN_URL . 'assets/js/vendor/jquery.flot.min.js' ); + wp_enqueue_script( 'edd-jquery-flot-time', EDD_PLUGIN_URL . 'assets/js/vendor/jquery.flot.time.min.js' ); + + /** + * Fires immediately after the legacy Flot JS graphing framework is enqueued. + * + * @since 1.9 + */ + do_action( 'edd_graph_load_scripts' ); + } + + /** + * Build the graph and return it as a string + * + * @var array + * @since 1.9 + * @return string + */ + public function build_graph() { + + $yaxis_count = 1; + + $this->load_scripts(); + ob_start(); +?> + +
    +build_graph(); + do_action( 'edd_after_graph', $this ); + } + +} diff --git a/includes/admin/reporting/class-edd-pie-graph.php b/includes/admin/reporting/class-edd-pie-graph.php new file mode 100644 index 00000000000..27fc32e77b8 --- /dev/null +++ b/includes/admin/reporting/class-edd-pie-graph.php @@ -0,0 +1,199 @@ + 'value' ), + array( 'Label 2' => 'value 2' ), + ); + + $graph = new EDD_Pie_Graph( $data ); + $graph->display(); + + */ + + /** + * Data to graph + * + * @var array + * @since 2.4 + */ + private $data; + + /** + * Unique ID for the graph + * + * @var string + * @since 2.4 + */ + private $id = ''; + + /** + * Graph options + * + * @var array + * @since 2.4 + */ + private $options = array(); + + /** + * Get things started + * + * @since 2.4 + */ + public function __construct( $_data, $options = array() ) { + + $this->data = $_data; + + // Set this so filters recieving $this can quickly know if it's a graph they want to modify + $this->type = 'pie'; + + // Generate unique ID, add 'a' since md5 can leave a numerical first character + $this->id = 'a' . md5( rand() ); + + // Setup default options; + $this->options = wp_parse_args( $options, array( + 'radius' => 1, + 'legend' => true, + 'legend_formatter' => false, + 'legend_columns' => 3, + 'legend_position' => 's', + 'show_labels' => false, + 'label_threshold' => 0.01, + 'label_formatter' => 'eddLabelFormatter', + 'label_bg_opacity' => 0.75, + 'label_radius' => 1, + 'height' => '300', + 'hoverable' => true, + 'clickable' => false, + 'threshold' => false, + ) ); + + add_action( 'edd_graph_load_scripts', array( $this, 'load_additional_scripts' ) ); + } + + /** + * Load the graphing library script + * + * @since 2.4 + */ + public function load_additional_scripts() { + wp_enqueue_script( 'edd-jquery-flot-pie', EDD_PLUGIN_URL . 'assets/js/vendor/jquery.flot.pie.min.js' ); + } + + /** + * Build the graph and return it as a string + * + * @var array + * @since 2.4 + * @return string + */ + public function build_graph() { + + if ( count( $this->data ) ) { + $this->load_scripts(); + + ob_start(); + ?> + +
    +
    +
    +
    + id, $this->data, $this->options ); + + } + +} diff --git a/includes/admin/reporting/class-export-customers.php b/includes/admin/reporting/class-export-customers.php new file mode 100755 index 00000000000..82a98de80c2 --- /dev/null +++ b/includes/admin/reporting/class-export-customers.php @@ -0,0 +1,165 @@ +export_type . '-' . date( 'm-d-Y' ) ) . '.csv"' ); + header( 'Expires: 0' ); + } + + /** + * Set the CSV columns + * + * @since 1.4.4 + * @return array $cols All the columns + */ + public function csv_cols() { + if ( ! empty( $_POST['edd_export_download'] ) ) { + $cols = array( + 'first_name' => __( 'First Name', 'easy-digital-downloads' ), + 'last_name' => __( 'Last Name', 'easy-digital-downloads' ), + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'date' => __( 'Date Purchased', 'easy-digital-downloads' ) + ); + } else { + + $cols = array(); + + if( 'emails' != $_POST['edd_export_option'] ) { + $cols['name'] = __( 'Name', 'easy-digital-downloads' ); + } + + $cols['email'] = __( 'Email', 'easy-digital-downloads' ); + + if( 'full' == $_POST['edd_export_option'] ) { + $cols['purchases'] = __( 'Total Purchases', 'easy-digital-downloads' ); + $cols['amount'] = __( 'Total Purchased', 'easy-digital-downloads' ) . ' (' . html_entity_decode( edd_currency_filter( '' ) ) . ')'; + } + + } + + return $cols; + } + + /** + * Get the Export Data + * + * @since 1.4.4 + * @global object $wpdb Used to query the database using the WordPress + * Database API + * @global object $edd_logs EDD Logs Object + * @return array $data The data for the CSV file + */ + public function get_data() { + + $data = array(); + + if ( ! empty( $_POST['edd_export_download'] ) ) { + + $edd_logs = EDD()->debug_log; + + $args = array( + 'post_parent' => absint( $_POST['edd_export_download'] ), + 'log_type' => 'sale', + 'nopaging' => true + ); + + if( isset( $_POST['edd_price_option'] ) ) { + $args['meta_query'] = array( + array( + 'key' => '_edd_log_price_id', + 'value' => (int) $_POST['edd_price_option'] + ) + ); + } + + $logs = $edd_logs->get_connected_logs( $args ); + + if ( $logs ) { + foreach ( $logs as $log ) { + $payment_id = get_post_meta( $log->ID, '_edd_log_payment_id', true ); + $user_info = edd_get_payment_meta_user_info( $payment_id ); + $data[] = array( + 'first_name' => $user_info['first_name'], + 'last_name' => $user_info['last_name'], + 'email' => $user_info['email'], + 'date' => $log->post_date + ); + } + } + + } else { + + // Export all customers + $customers = edd_get_customers( array( + 'limit' => 9999999, + ) ); + + $i = 0; + + foreach ( $customers as $customer ) { + + if( 'emails' != $_POST['edd_export_option'] ) { + $data[$i]['name'] = $customer->name; + } + + $data[$i]['email'] = $customer->email; + + if( 'full' == $_POST['edd_export_option'] ) { + + $data[$i]['purchases'] = $customer->purchase_count; + $data[$i]['amount'] = edd_format_amount( $customer->purchase_value ); + + } + $i++; + } + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } +} diff --git a/includes/admin/reporting/class-export-download-history.php b/includes/admin/reporting/class-export-download-history.php new file mode 100755 index 00000000000..ca2e1e7d412 --- /dev/null +++ b/includes/admin/reporting/class-export-download-history.php @@ -0,0 +1,113 @@ +export_type . '-' . $month . '-' . $year ) . '.csv"' ); + header( 'Expires: 0' ); + } + + + /** + * Set the CSV columns + * + * @since 1.4.4 + * @return array $cols All the columns + */ + public function csv_cols() { + $cols = array( + 'date' => __( 'Date', 'easy-digital-downloads' ), + 'user' => __( 'Downloaded by', 'easy-digital-downloads' ), + 'ip' => __( 'IP Address', 'easy-digital-downloads' ), + 'download' => __( 'Product', 'easy-digital-downloads' ), + 'file' => __( 'File', 'easy-digital-downloads' ) + ); + return $cols; + } + + /** + * Get the Export Data + * + * @since 1.4.4 + * @global object $edd_logs EDD Logs Object + * @return array $data The data for the CSV file + */ + public function get_data() { + $edd_logs = EDD()->debug_log; + + $data = array(); + + $args = array( + 'nopaging' => true, + 'log_type' => 'file_download', + 'monthnum' => isset( $_POST['month'] ) ? absint( $_POST['month'] ) : date( 'n' ), + 'year' => isset( $_POST['year'] ) ? absint( $_POST['year'] ) : date( 'Y' ) + ); + + $logs = $edd_logs->get_connected_logs( $args ); + + if ( $logs ) { + foreach ( $logs as $log ) { + $user_info = get_post_meta( $log->ID, '_edd_log_user_info', true ); + $files = edd_get_download_files( $log->post_parent ); + $file_id = (int) get_post_meta( $log->ID, '_edd_log_file_id', true ); + $file_name = isset( $files[ $file_id ]['name'] ) ? $files[ $file_id ]['name'] : null; + $user = get_userdata( $user_info['id'] ); + $user = $user ? $user->user_login : $user_info['email']; + + $data[] = array( + 'date' => $log->post_date, + 'user' => $user, + 'ip' => get_post_meta( $log->ID, '_edd_log_ip', true ), + 'download' => get_the_title( $log->post_parent ), + 'file' => $file_name + ); + } + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } +} diff --git a/includes/admin/reporting/class-export-payments.php b/includes/admin/reporting/class-export-payments.php new file mode 100755 index 00000000000..f96eef98e8c --- /dev/null +++ b/includes/admin/reporting/class-export-payments.php @@ -0,0 +1,201 @@ +export_type . '-' . $month . '-' . $year ) . '.csv"' ); + header( 'Expires: 0' ); + } + + /** + * Set the CSV columns + * + * @since 1.4.4 + * @return array $cols All the columns + */ + public function csv_cols() { + $cols = array( + 'id' => __( 'ID', 'easy-digital-downloads' ), // unaltered payment ID (use for querying) + 'seq_id' => __( 'Payment Number', 'easy-digital-downloads' ), // sequential payment ID + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'first' => __( 'First Name', 'easy-digital-downloads' ), + 'last' => __( 'Last Name', 'easy-digital-downloads' ), + 'address1' => __( 'Address', 'easy-digital-downloads' ), + 'address2' => __( 'Address (Line 2)', 'easy-digital-downloads' ), + 'city' => __( 'City', 'easy-digital-downloads' ), + 'state' => __( 'State', 'easy-digital-downloads' ), + 'country' => __( 'Country', 'easy-digital-downloads' ), + 'zip' => __( 'Zip / Postal Code', 'easy-digital-downloads' ), + 'products' => __( 'Products', 'easy-digital-downloads' ), + 'skus' => __( 'SKUs', 'easy-digital-downloads' ), + 'currency' => __( 'Currency', 'easy-digital-downloads' ), + 'amount' => __( 'Amount', 'easy-digital-downloads' ), + 'tax' => __( 'Tax', 'easy-digital-downloads' ), + 'discount' => __( 'Discount Code', 'easy-digital-downloads' ), + 'gateway' => __( 'Payment Method', 'easy-digital-downloads' ), + 'trans_id' => __( 'Transaction ID', 'easy-digital-downloads' ), + 'key' => __( 'Purchase Key', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ), + 'user' => __( 'User', 'easy-digital-downloads' ), + 'status' => __( 'Status', 'easy-digital-downloads' ) + ); + + if( ! edd_use_skus() ){ + unset( $cols['skus'] ); + } + if ( ! edd_get_option( 'enable_sequential' ) ) { + unset( $cols['seq_id'] ); + } + + return $cols; + } + + /** + * Get the Export Data + * + * @since 1.4.4 + * @global object $wpdb Used to query the database using the WordPress + * Database API + * @return array $data The data for the CSV file + */ + public function get_data() { + global $wpdb; + + $data = array(); + + $payments = edd_get_payments( array( + 'offset' => 0, + 'number' => 9999999, + 'mode' => edd_is_test_mode() ? 'test' : 'live', + 'status' => isset( $_POST['edd_export_payment_status'] ) ? $_POST['edd_export_payment_status'] : 'any', + 'month' => isset( $_POST['month'] ) ? absint( $_POST['month'] ) : date( 'n' ), + 'year' => isset( $_POST['year'] ) ? absint( $_POST['year'] ) : date( 'Y' ) + ) ); + + foreach ( $payments as $payment ) { + $payment_meta = edd_get_payment_meta( $payment->ID ); + $user_info = edd_get_payment_meta_user_info( $payment->ID ); + $downloads = edd_get_payment_meta_cart_details( $payment->ID ); + $total = edd_get_payment_amount( $payment->ID ); + $user_id = isset( $user_info['id'] ) && $user_info['id'] != -1 ? $user_info['id'] : $user_info['email']; + $products = ''; + $skus = ''; + + if ( $downloads ) { + foreach ( $downloads as $key => $download ) { + // Download ID + $id = isset( $payment_meta['cart_details'] ) ? $download['id'] : $download; + + // If the download has variable prices, override the default price + $price_override = isset( $payment_meta['cart_details'] ) ? $download['price'] : null; + + $price = edd_get_download_final_price( $id, $user_info, $price_override ); + + // Display the Downoad Name + $products .= get_the_title( $id ) . ' - '; + + if ( edd_use_skus() ) { + $sku = edd_get_download_sku( $id ); + + if ( ! empty( $sku ) ) + $skus .= $sku; + } + + if ( isset( $downloads[ $key ]['item_number'] ) && isset( $downloads[ $key ]['item_number']['options'] ) ) { + $price_options = $downloads[ $key ]['item_number']['options']; + + if ( isset( $price_options['price_id'] ) ) { + $products .= edd_get_price_option_name( $id, $price_options['price_id'], $payment->ID ) . ' - '; + } + } + $products .= html_entity_decode( edd_currency_filter( $price ) ); + + if ( $key != ( count( $downloads ) -1 ) ) { + $products .= ' / '; + + if( edd_use_skus() ) + $skus .= ' / '; + } + } + } + + if ( is_numeric( $user_id ) ) { + $user = get_userdata( $user_id ); + } else { + $user = false; + } + + $currency_code = edd_get_payment_currency_code( $payment->ID ); + + $data[] = array( + 'id' => $payment->ID, + 'seq_id' => edd_get_payment_number( $payment->ID ), + 'email' => $payment_meta['email'], + 'first' => $user_info['first_name'], + 'last' => $user_info['last_name'], + 'address1' => isset( $user_info['address']['line1'] ) ? $user_info['address']['line1'] : '', + 'address2' => isset( $user_info['address']['line2'] ) ? $user_info['address']['line2'] : '', + 'city' => isset( $user_info['address']['city'] ) ? $user_info['address']['city'] : '', + 'state' => isset( $user_info['address']['state'] ) ? $user_info['address']['state'] : '', + 'country' => isset( $user_info['address']['country'] ) ? $user_info['address']['country'] : '', + 'zip' => isset( $user_info['address']['zip'] ) ? $user_info['address']['zip'] : '', + 'products' => $products, + 'skus' => $skus, + 'currency' => $currency_code, + 'amount' => html_entity_decode( edd_format_amount( $total, $currency_code ) ), + 'tax' => html_entity_decode( edd_format_amount( edd_get_payment_tax( $payment->ID, $payment_meta ), $currency_code ) ), + 'discount' => isset( $user_info['discount'] ) && $user_info['discount'] != 'none' ? $user_info['discount'] : __( 'none', 'easy-digital-downloads' ), + 'gateway' => edd_get_gateway_admin_label( edd_get_payment_meta( $payment->ID, '_edd_payment_gateway', true ) ), + 'trans_id' => edd_get_payment_transaction_id( $payment->ID ), + 'key' => $payment_meta['key'], + 'date' => $payment->post_date, + 'user' => $user ? $user->display_name : __( 'guest', 'easy-digital-downloads' ), + 'status' => edd_get_payment_status( $payment, true ) + ); + + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } +} diff --git a/includes/admin/reporting/class-export.php b/includes/admin/reporting/class-export.php new file mode 100755 index 00000000000..fadc1991dc5 --- /dev/null +++ b/includes/admin/reporting/class-export.php @@ -0,0 +1,202 @@ +export_type . '-' . date( 'm-d-Y' ) . '.csv"' ); + header( 'Expires: 0' ); + + /** + * We need to append a BOM to the export so that Microsoft Excel knows + * that the file is in Unicode. + * + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/4859 + */ + echo "\xEF\xBB\xBF"; + } + + /** + * Set the CSV columns. + * + * @since 1.4.4 + * + * @return array $cols CSV columns. + */ + public function csv_cols() { + $cols = array( + 'id' => __( 'ID', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ), + ); + return $cols; + } + + /** + * Retrieve the CSV columns. + * + * @since 1.4.4 + * + * @return array $cols Array of the columns. + */ + public function get_csv_cols() { + $cols = $this->csv_cols(); + return apply_filters( 'edd_export_csv_cols_' . $this->export_type, $cols ); + } + + /** + * Output the CSV columns. + * + * @since 1.4.4 + */ + public function csv_cols_out() { + $cols = $this->get_csv_cols(); + + $i = 1; + + // Output each column. + foreach ( $cols as $col_id => $column ) { + echo '"' . addslashes( $column ) . '"'; + + echo count( $cols ) === $i + ? '' + : ','; + + $i++; + } + echo "\r\n"; + } + + /** + * Get the data being exported. + * + * @since 1.4.4 + * + * @return array $data Data for export. + */ + public function get_data() { + + // Just a sample data array + $data = array( + 0 => array( + 'id' => '', + 'data' => date( 'F j, Y' ), + ), + 1 => array( + 'id' => '', + 'data' => date( 'F j, Y' ), + ), + ); + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } + + /** + * Output the CSV rows. + * + * @since 1.4.4 + */ + public function csv_rows_out() { + $data = $this->get_data(); + + $cols = $this->get_csv_cols(); + + // Output each row. + foreach ( $data as $row ) { + $i = 1; + + foreach ( $row as $col_id => $column ) { + + // Make sure the column is valid. + if ( array_key_exists( $col_id, $cols ) ) { + echo '"' . addslashes( $column ) . '"'; + + echo count( $cols ) === $i + ? '' + : ','; + + $i++; + } + } + echo "\r\n"; + } + } + + /** + * Perform the export. + * + * @since 1.4.4 + * + * + * @uses EDD_Export::can_export() + * @uses EDD_Export::headers() + * @uses EDD_Export::csv_cols_out() + * @uses EDD_Export::csv_rows_out() + */ + public function export() { + + // Bail if user if unauthorized. + if ( ! $this->can_export() ) { + wp_die( __( 'You do not have permission to export data.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Set headers + $this->headers(); + + // Output CSV columns (headers) + $this->csv_cols_out(); + + // Output CSV rows + $this->csv_rows_out(); + + edd_die(); + } +} diff --git a/includes/admin/reporting/class-file-downloads-logs-list-table.php b/includes/admin/reporting/class-file-downloads-logs-list-table.php new file mode 100755 index 00000000000..b48cafdbfb7 --- /dev/null +++ b/includes/admin/reporting/class-file-downloads-logs-list-table.php @@ -0,0 +1,258 @@ +ID, $item['price_id'] ) + : edd_get_download_name( $download->ID ); + + return '' . esc_html( $column_value ) . ''; + case 'customer' : + return ! empty( $item[ 'customer' ]->id ) + ? '' . esc_html( $item['customer']->name ) . '' + : '—'; + + case 'payment_id' : + $number = edd_get_payment_number( $item['payment_id'] ); + return ! empty( $number ) + ? '' . esc_html( $number ) . '' + : '—'; + case 'ip' : + return '' . esc_html( $item['ip'] ) . ''; + case 'file': + return ! empty( $item['file'] ) + ? esc_html( $item['file'] ) + : '—'; + default: + return $item[ $column_name ]; + } + } + + /** + * Set the table columns. + * + * @since 1.4 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'ID' => __( 'Log ID', 'easy-digital-downloads' ), + 'download' => edd_get_label_singular(), + 'customer' => __( 'Customer', 'easy-digital-downloads' ), + 'payment_id' => __( 'Order Number', 'easy-digital-downloads' ), + 'file' => __( 'File', 'easy-digital-downloads' ), + 'ip' => __( 'IP Address', 'easy-digital-downloads' ), + 'user_agent' => __( 'User Agent', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ) + ); + } + + /** + * Gets the log entries for the current view + * + * @since 1.4 + * + * @param $log_query array Arguments for getting logs. + * + * @return array $logs_data Array of all the logs. + */ + function get_logs( $log_query = array() ) { + $logs_data = array(); + + $logs = edd_get_file_download_logs( $log_query ); + + if ( $logs ) { + foreach ( $logs as $log ) { + /** @var $log File_Download_Log */ + + $customer_id = ! empty( $log->customer_id ) ? (int) $log->customer_id : edd_get_payment_customer_id( $log->order_id ); + + $files = $this->get_log_files( $log, $customer_id ); + + $file_id = $log->file_id; + + /** + * Filters the ID of the file that was actually downloaded from this log. + * + * @param int $file_id + * @param File_Download_Log $log + */ + $file_id = apply_filters( 'edd_log_file_download_file_id', $file_id, $log ); + + $file_name = edd_get_file_download_log_meta( $log->id, 'file_name', true ); + + if ( empty( $file_name ) && is_array( $files ) && isset( $files[ $file_id ] ) ) { + $file_name = ! empty( $files[ $file_id ]['name'] ) + ? $files[ $file_id ]['name'] + : edd_get_file_name( $files[ $file_id ] ); + } + + if ( empty( $this->file_search ) || ( ! empty( $this->file_search ) && strpos( strtolower( $file_name ), strtolower( $this->get_search() ) ) !== false ) ) { + $logs_data[] = array( + 'ID' => $log->id, + 'download' => $log->product_id, + 'customer' => new EDD_Customer( $customer_id ), + 'payment_id' => $log->order_id, + 'price_id' => $log->price_id, + 'file' => $file_name, + 'ip' => $log->ip, + 'user_agent' => $log->user_agent, + 'date' => $log->date_created, + ); + } + } + } + + return $logs_data; + } + + /** + * Retrieves the array of files associated with the log. + * + * This method contains a lot of logic to ensure backwards compatibility with how + * the `edd_log_file_download_download_files` filter was formatted in EDD 2.x. + * + * @since 3.0 + * + * @param File_Download_Log $log + * @param int $customer_id + * + * @return array + */ + protected function get_log_files( File_Download_Log $log, $customer_id ) { + $customer = ! empty( $customer_id ) ? edd_get_customer( $customer_id ) : false; + + /* + * Get the files associated with this download and store them in a property to prevent + * multiple queries for the same download. + * This is needed for backwards compatibility in the `edd_log_file_download_download_files` filter. + */ + if ( ! array_key_exists( $log->product_id, $this->queried_files ) ) { + $files = get_post_meta( $log->product_id, 'edd_download_files', true ); + $this->queried_files[ $log->product_id ] = $files; + } else { + $files = $this->queried_files[ $log->product_id ]; + } + + /* + * User info is needed for backwards compatibility in the `edd_log_file_download_download_files` filter. + */ + $user = ! empty( $customer->user_id ) ? get_userdata( $customer->user_id ) : false; + + $user_info = ! empty( $user ) + ? array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'name' => $user->display_name, + ) + : array(); + + /* + * Build up an array of meta. + */ + $meta = edd_get_file_download_log_meta( $log->id ); + + // These meta keys no longer exist, but we need to create them for use in the filter. + $backwards_compat_meta = array( + '_edd_log_user_info' => $user_info, + '_edd_log_user_id' => ! empty( $customer->user_id ) ? $customer->user_id : false, + '_edd_log_customer_id' => $customer_id, + '_edd_log_file_id' => $log->file_id, + '_edd_log_ip' => $log->ip, + '_edd_log_payment_id' => $log->order_id, + '_edd_log_price_id' => $log->price_id, + ); + + foreach( $backwards_compat_meta as $meta_key => $meta_value ) { + $meta[ $meta_key ] = array( $meta_value ); + } + + /** + * Filters the array of all files linked to the product. + * + * @param array $files Files linked to the product. + * @param File_Download_Log $log Log record from the database. + * @param array $meta What used to be the meta array in EDD 2.9 and lower. + */ + return apply_filters( 'edd_log_file_download_download_files', $files, $log, $meta ); + } + + /** + * Setup the final data for the table. + * + * @since 1.5 + */ + public function get_total( $log_query = array() ) { + return edd_count_file_download_logs( $log_query ); + } +} diff --git a/includes/admin/reporting/class-gateway-error-logs-list-table.php b/includes/admin/reporting/class-gateway-error-logs-list-table.php new file mode 100755 index 00000000000..a19ee6159e7 --- /dev/null +++ b/includes/admin/reporting/class-gateway-error-logs-list-table.php @@ -0,0 +1,158 @@ + + + + __( 'Log ID', 'easy-digital-downloads' ), + 'payment_id' => __( 'Order Number', 'easy-digital-downloads' ), + 'error' => __( 'Error', 'easy-digital-downloads' ), + 'message' => __( 'Error Message', 'easy-digital-downloads' ), + 'gateway' => __( 'Gateway', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ) + ); + } + + /** + * Gets the log entries for the current view + * + * @since 1.4 + * @param array $log_query Query arguments + * @global object $edd_logs EDD Logs Object + * @return array $logs_data Array of all the Log entries + */ + public function get_logs( $log_query = array() ) { + $logs_data = array(); + $log_query['type'] = 'gateway_error'; + + $logs = edd_get_logs( $log_query ); + + if ( $logs ) { + foreach ( $logs as $log ) { + /** @var $log EDD\Logs\Log */ + + $logs_data[] = array( + 'ID' => $log->id, + 'payment_id' => $log->object_id, + 'error' => $log->title ? $log->title : __( 'Payment Error', 'easy-digital-downloads' ), + 'gateway' => edd_get_payment_gateway( $log->object_id ), + 'date' => $log->date_created, + 'content' => $log->content, + ); + } + } + + return $logs_data; + } + + /** + * Setup the final data for the table. + * + * @since 1.5 + */ + public function get_total( $log_query = array() ) { + $log_query['type'] = 'gateway_error'; + + return edd_count_logs( $log_query ); + } +} diff --git a/includes/admin/reporting/class-gateways-reports-table.php b/includes/admin/reporting/class-gateways-reports-table.php new file mode 100755 index 00000000000..bd9b7ed009a --- /dev/null +++ b/includes/admin/reporting/class-gateways-reports-table.php @@ -0,0 +1,159 @@ + 'report-gateway', + 'plural' => 'report-gateways', + 'ajax' => false + ) ); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'label'; + } + + /** + * This function renders most of the columns in the list table. + * + * @since 1.5 + * + * @param array $item Contains all the data of the downloads + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + return $item[ $column_name ]; + } + + /** + * Retrieve the table columns + * + * @since 1.5 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'label' => __( 'Gateway', 'easy-digital-downloads' ), + 'complete_sales' => __( 'Complete Sales', 'easy-digital-downloads' ), + 'pending_sales' => __( 'Pending / Failed Sales', 'easy-digital-downloads' ), + 'total_sales' => __( 'Total Sales', 'easy-digital-downloads' ) + ); + } + + /** + * Outputs the reporting views + * + * @since 1.5 + * @return void + */ + public function bulk_actions( $which = '' ) { + // These aren't really bulk actions but this outputs the markup in + // the right place. + edd_report_views(); + } + + /** + * Builds and retrieves all of the payment gateways reports data. + * + * @since 1.5 + * @deprecated 3.0 Use get_data() + * + * @return array All the data for customer reports. + */ + public function reports_data() { + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Gateway_Reports_Table::get_data()' ); + + return $this->get_data(); + } + + /** + * Retrieves all of the payment gateways reports data. + * + * @since 3.0 + * + * @return array Payment gateways reports table data. + */ + public function get_data() { + + $reports_data = array(); + $gateways = edd_get_payment_gateways(); + + foreach ( $gateways as $gateway_id => $gateway ) { + + $complete_count = edd_count_sales_by_gateway( $gateway_id, edd_get_gross_order_statuses() ); + $pending_count = edd_count_sales_by_gateway( $gateway_id, edd_get_incomplete_order_statuses() ); + + $reports_data[] = array( + 'ID' => $gateway_id, + 'label' => $gateway['admin_label'], + 'complete_sales' => edd_format_amount( $complete_count, false ), + 'pending_sales' => edd_format_amount( $pending_count, false ), + 'total_sales' => edd_format_amount( $complete_count + $pending_count, false ), + ); + } + + return $reports_data; + } + + /** + * Setup the final data for the table + * + * @since 1.5 + * @uses EDD_Gateway_Reports_Table::get_columns() + * @uses EDD_Gateway_Reports_Table::get_sortable_columns() + * @uses EDD_Gateway_Reports_Table::reports_data() + * @return void + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); // No hidden columns + $sortable = $this->get_sortable_columns(); + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } +} + +/** + * Back-compat for typo + * + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6549 + */ +class_alias( 'EDD_Gateway_Reports_Table', 'EDD_Gateawy_Reports_Table' ); diff --git a/includes/admin/reporting/class-reports-sections.php b/includes/admin/reporting/class-reports-sections.php new file mode 100644 index 00000000000..27b403ba359 --- /dev/null +++ b/includes/admin/reporting/class-reports-sections.php @@ -0,0 +1,51 @@ +use_js ) + ? ' use-js' + : ''; + + $role = $this->use_js ? 'tablist' : 'menu'; + ?> +
    + +
    +
      + get_all_section_links(); ?> +
    + +
    + get_all_section_contents(); ?> +
    +
    +
    +
    + get_name( $price_id ); + $return = '' . esc_html( $title ) . ''; + break; + + case 'customer': + $name = ! empty( $item['customer']->name ) + ? $item['customer']->name + : '' . __( 'Unnamed Customer', 'easy-digital-downloads' ) . ''; + + $return = '#' . esc_html( $item['customer']->id ) . ' ' . esc_html( $name ) . ''; + break; + + case 'item_price': + $return = edd_currency_filter( edd_format_amount( $item['item_price'] ), $currency ); + break; + + case 'amount': + $return = edd_currency_filter( edd_format_amount( $item['amount'] / $item['quantity'] ), $currency ); + break; + + case 'ID': + $return = '' . absint( $item['ID'] ) . ''; + break; + + default: + $return = $item[ $column_name ]; + break; + } + + return $return; + } + + /** + * Retrieve the table columns + * + * @since 1.4 + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'ID' => __( 'Order Number', 'easy-digital-downloads' ), + 'customer' => __( 'Customer', 'easy-digital-downloads' ), + 'download' => edd_get_label_singular(), + 'amount' => __( 'Item Amount', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ), + ); + } + + /** + * Return array of query arguments. + * + * @since 3.0 + * + * @param boolean $paginate + * + * @return array + */ + protected function get_query_args( $paginate = true ) { + $retval = parent::get_query_args( $paginate ); + + $user = $this->get_filtered_user(); + + if ( $user ) { + // Show only logs from a specific user + $retval['user_id'] = $user; + } + + $search = $this->get_search(); + if ( $search ) { + if ( is_email( $search ) ) { + $field = 'email'; + } else { + // Look for a user + $field = 'user_id'; + + if ( ! is_numeric( $search ) ) { + // Searching for user by username + $user = get_user_by( 'login', $search ); + + if ( $user ) { + // Found one, set meta value to user's ID + $search = $user->ID; + } else { + // No user found so let's do a real search query + $users = new WP_User_Query( array( + 'search' => $search, + 'search_columns' => array( 'user_url', 'user_nicename' ), + 'number' => 1, + 'fields' => 'ids', + ) ); + + $found_user = $users->get_results(); + + if ( $found_user ) { + $search = $found_user[0]; + } + } + } + } + + if ( ! $this->file_search ) { + $retval[ $field ] = $search; + } + } + + return $retval; + } + + /** + * Gets the log entries for the current view. + * + * @since 1.4 + * @since 3.0 Refactored to fetch from order items table. + * + * @param array $log_query Query vars. + * @return array $data Array of all the sales. + */ + public function get_logs( $log_query = array() ) { + $data = $order_args = array(); + + // Customer ID + if ( ! empty( $log_query['customer_id'] ) ) { + $order_args = array( + 'customer_id' => $log_query['customer_id'], + 'no_found_rows' => true + ); + + // Customer Email + } elseif ( ! empty( $log_query['email'] ) ) { + $order_args = array( + 'email' => $log_query['email'], + 'no_found_rows' => true + ); + } + + // Maybe query for orders first + if ( ! empty( $order_args ) ) { + $orders = edd_get_orders( $order_args ); + $log_query['order_id__in'] = wp_list_pluck( $orders, 'id' ); + } + + // Query order items + $order_items = edd_get_order_items( $log_query ); + + // Bail if no order items + if ( empty( $order_items ) ) { + return $data; + } + + // Maybe prime orders + if ( empty( $orders ) ) { + $order_ids = array_values( array_unique( wp_list_pluck( $order_items, 'order_id' ) ) ); + + if ( count( $order_ids ) > 2 ) { + $orders = edd_get_orders( array( + 'id__in' => $order_ids, + 'no_found_rows' => true + ) ); + } + } + + // Maybe prime customers + if ( ! empty( $orders ) ) { + $customer_ids = array_values( array_unique( wp_list_pluck( $orders, 'customer_id' ) ) ); + + if ( count( $customer_ids ) > 2 ) { + edd_get_customers( array( + 'id__in' => $customer_ids, + 'no_found_rows' => true + ) ); + } + } + + // Loop through order items + foreach ( $order_items as $order_item ) { + $order = edd_get_order( $order_item->order_id ); + + $data[] = array( + 'ID' => $order->get_number(), + 'order_id' => $order->id, + 'customer' => edd_get_customer( $order->customer_id ), + 'download' => $order_item->product_id, + 'price_id' => $order_item->price_id, + 'item_price' => $order_item->amount, + 'amount' => $order_item->total, + 'date' => EDD()->utils->date( $order_item->date_created, null, true )->toDateTimeString(), + 'quantity' => $order_item->quantity, + 'currency' => $order->currency, + ); + } + + return $data; + } + + /** + * Get the total number of items + * + * @since 3.0 + * + * @param array $log_query + * + * @return int + */ + public function get_total( $log_query = array() ) { + return edd_count_order_items( $log_query ); + } +} diff --git a/includes/admin/reporting/contextual-help.php b/includes/admin/reporting/contextual-help.php new file mode 100755 index 00000000000..46686fd0949 --- /dev/null +++ b/includes/admin/reporting/contextual-help.php @@ -0,0 +1,92 @@ +id != 'download_page_edd-reports' ) { + return; + } + + $pass_manager = new Pass_Manager(); + if ( $pass_manager->isFree() ) { + $docs_url = edd_link_helper( + 'https://easydigitaldownloads.com/docs/', + array( + 'utm_medium' => 'reports-contextual-help', + 'utm_content' => 'documentation', + ) + ); + + $upgrade_url = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => 'reports-contextual-help', + 'utm_content' => 'lite-upgrade', + ) + ); + + $screen->set_help_sidebar( + '

    ' . __( 'For more information:', 'easy-digital-downloads' ) . '

    ' . + '

    ' . sprintf( __( 'Visit the documentation on the Easy Digital Downloads website.', 'easy-digital-downloads' ), $docs_url ) . '

    ' . + '

    ' . sprintf( + __( 'Need more from your Easy Digital Downloads store? Upgrade Now!', 'easy-digital-downloads' ), + $upgrade_url + ) . '

    ' + ); + } + + $screen->add_help_tab( array( + 'id' => 'edd-reports', + 'title' => __( 'Reports', 'easy-digital-downloads' ), + 'content' => '

    ' . __( 'This screen provides you with reports for your earnings, downloads, customers and taxes.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-reports-export', + 'title' => __( 'Export', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'This screen allows you to export your reports into a CSV format.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Sales and Earnings - This report exports all of the sales and earnings that you have made in the current year. It includes your sales and earnings for each product as well a graphs of sales and earnings so you can compare them for each month.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Payment History - This report exports all of the payments you have received on your EDD store in a CSV format. It includes the contact details of the customer, the products they have purchased as well as any discount codes they have used and the final price they have paid.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( "Customers - This report exports all of your customers in a CSV format. It exports the customer's name and email address and the amount of products they have purchased as well as the final price of their total purchases.", 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Download History - This report exports all of the downloads you have received in the current month into a CSV. It exports the date the file was downloaded, the customer it was downloaded by, their IP address, the name of the product and the file they downloaded.', 'easy-digital-downloads' ) . '

    ' + ) ); + + if( ! empty( $_GET['tab'] ) && 'logs' == $_GET['tab'] ) { + $screen->add_help_tab( array( + 'id' => 'edd-reports-log-search', + 'title' => __( 'Search File Downloads', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'The file download log can be searched in several different ways:', 'easy-digital-downloads' ) . '

    ' . + '
      +
    • ' . __( 'You can enter the customer\'s email address', 'easy-digital-downloads' ) . '
    • +
    • ' . __( 'You can enter the customer\'s IP address', 'easy-digital-downloads' ) . '
    • +
    • ' . __( 'You can enter the download file\'s name', 'easy-digital-downloads' ) . '
    • +
    ' + ) ); + } + + do_action( 'edd_reports_contextual_help', $screen ); +} +add_action( 'load-download_page_edd-reports', 'edd_reporting_contextual_help' ); diff --git a/includes/admin/reporting/export/class-batch-export-api-requests.php b/includes/admin/reporting/export/class-batch-export-api-requests.php new file mode 100644 index 00000000000..3b18964de26 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-api-requests.php @@ -0,0 +1,132 @@ + __( 'Log ID', 'easy-digital-downloads' ), + 'request' => __( 'API Request', 'easy-digital-downloads' ), + 'ip' => __( 'IP Address', 'easy-digital-downloads' ), + 'user' => __( 'API User', 'easy-digital-downloads' ), + 'key' => __( 'API Key', 'easy-digital-downloads' ), + 'version' => __( 'API Version', 'easy-digital-downloads' ), + 'speed' => __( 'Request Speed', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ) + ); + + return $cols; + } + + /** + * Get the export data. + * + * @since 2.7 + * @since 3.0 Updated to use new query methods. + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + $data = array(); + + $args = array( + 'number' => 30, + 'offset' => ( $this->step * 30 ) - 30 + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + $logs = edd_get_api_request_logs( $args ); + + foreach ( $logs as $log ) { + /** @var EDD\Logs\Api_Request_Log $log */ + + $data[] = array( + 'ID' => $log->id, + 'request' => $log->request, + 'ip' => $log->ip, + 'user' => $log->user_id, + 'key' => $log->api_key, + 'version' => $log->version, + 'speed' => $log->time, + 'date' => $log->date_created + ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return ! empty( $data ) + ? $data + : false; + } + + /** + * Return the calculated completion percentage. + * + * @since 2.7 + * @since 3.0 Updated to use new query methods. + * + * @return int Percentage complete. + */ + public function get_percentage_complete() { + $args = array( + 'fields' => 'ids', + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + $total = edd_count_api_request_logs( $args ); + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + public function set_properties( $request ) { + $this->start = isset( $request['api-requests-export-start'] ) ? sanitize_text_field( $request['api-requests-export-start'] ) : ''; + $this->end = isset( $request['api-requests-export-end'] ) ? sanitize_text_field( $request['api-requests-export-end'] ) : ''; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-customers.php b/includes/admin/reporting/export/class-batch-export-customers.php new file mode 100644 index 00000000000..395472827e6 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-customers.php @@ -0,0 +1,230 @@ + __( 'ID', 'easy-digital-downloads' ), + 'user_id' => __( 'User ID', 'easy-digital-downloads' ), + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'purchases' => __( 'Number of Purchases', 'easy-digital-downloads' ), + 'amount' => __( 'Customer Value', 'easy-digital-downloads' ), + 'payment_ids' => __( 'Payment IDs', 'easy-digital-downloads' ), + 'date_created' => __( 'Date Created', 'easy-digital-downloads' ), + ); + } + + /** + * Get the export data. + * + * @since 2.4 + * @since 3.0 Updated to use new query methods. + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + global $wpdb; + + $data = array(); + + // Taxonomy. + if ( ! empty( $this->taxonomy ) ) { + $taxonomy = $wpdb->prepare( 't.term_id = %d', $this->taxonomy ); + + $limit = $wpdb->prepare( '%d, %d', 30 * ( $this->step - 1 ), 30 ); + + $sql = "SELECT DISTINCT o.customer_id + FROM {$wpdb->terms} t + INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id + INNER JOIN {$wpdb->term_relationships} tr ON tr.term_taxonomy_id = tt.term_taxonomy_id + INNER JOIN {$wpdb->edd_order_items} oi ON tr.object_id = oi.product_id + INNER JOIN {$wpdb->edd_orders} o ON oi.order_id = o.id + WHERE {$taxonomy} + LIMIT {$limit}"; + + $results = $wpdb->get_col( $sql ); // WPCS: unprepared SQL ok. + + if ( $results ) { + foreach ( $results as $customer_id ) { + $customer = new EDD_Customer( $customer_id ); + $name = ! empty( $customer->name ) ? $customer->name : ''; + if ( preg_match( '~^[+\-=@]~m', $name ) ) { + $name = "'{$name}"; + } + + $data[] = array( + 'id' => $customer->id, + 'name' => $name, + 'email' => $customer->email, + 'purchases' => $customer->purchase_count, + 'amount' => edd_format_amount( $customer->purchase_value ), + ); + } + } + + // Download. + } elseif ( ! empty( $this->download ) ) { + // Export customers of a specific product + + $args = array( + 'product_id' => absint( $this->download ), + 'number' => 30, + 'offset' => 30 * ( $this->step - 1 ), + ); + + if ( null !== $this->price_id ) { + $args['price_id'] = (int) $this->price_id; + } + + $order_items = edd_get_order_items( $args ); + + if ( $order_items ) { + foreach ( $order_items as $item ) { + $order = edd_get_order( $item->order_id ); + + $customer = new EDD_Customer( $order->customer_id ); + $name = ! empty( $customer->name ) ? $customer->name : ''; + if ( preg_match( '~^[+\-=@]~m', $name ) ) { + $name = "'{$name}"; + } + + $data[] = array( + 'id' => $customer->id, + 'user_id' => $customer->user_id, + 'name' => $name, + 'email' => $customer->email, + 'purchases' => $customer->purchase_count, + 'amount' => edd_format_amount( $customer->purchase_value ), + 'payment_ids' => $customer->payment_ids, + 'date_created' => $customer->date_created, + ); + } + } + + // All customers. + } else { + $customers = edd_get_customers( array( + 'number' => 30, + 'offset' => 30 * ( $this->step - 1 ), + ) ); + + $i = 0; + + foreach ( $customers as $customer ) { + $name = ! empty( $customer->name ) ? $customer->name : ''; + if ( preg_match( '~^[+\-=@]~m', $name ) ) { + $name = "'{$name}"; + } + $data[ $i ]= array( + 'id' => $customer->id, + 'user_id' => $customer->user_id, + 'name' => $name, + 'email' => $customer->email, + 'purchases' => $customer->purchase_count, + 'amount' => edd_format_amount( $customer->purchase_value ), + 'payment_ids' => $customer->payment_ids, + 'date_created' => $customer->date_created, + ); + + $i++; + } + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } + + /** + * Return the calculated completion percentage. + * + * @since 2.4 + * + * @return float Percentage complete. + */ + public function get_percentage_complete() { + $percentage = 0; + + // We can't count the number when getting them for a specific download. + if ( empty( $this->download ) ) { + $total = edd_count_customers(); + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set the properties specific to the Customers export + * + * @since 2.4.2 + * + * @param array $request Form data passed into the batch processing. + */ + public function set_properties( $request ) { + $this->taxonomy = isset( $request['taxonomy'] ) + ? absint( $request['taxonomy'] ) + : null; + + $this->download = isset( $request['download'] ) + ? absint( $request['download'] ) + : null; + + $this->price_id = ! empty( $request['edd_price_option'] ) && 0 !== $request['edd_price_option'] + ? absint( $request['edd_price_option'] ) + : null; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-downloads.php b/includes/admin/reporting/export/class-batch-export-downloads.php new file mode 100644 index 00000000000..1ea03b64091 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-downloads.php @@ -0,0 +1,234 @@ + __( 'ID', 'easy-digital-downloads' ), + 'post_name' => __( 'Slug', 'easy-digital-downloads' ), + 'post_title' => __( 'Name', 'easy-digital-downloads' ), + 'post_date' => __( 'Date Created', 'easy-digital-downloads' ), + 'post_author' => __( 'Author', 'easy-digital-downloads' ), + 'post_content' => __( 'Description', 'easy-digital-downloads' ), + 'post_excerpt' => __( 'Excerpt', 'easy-digital-downloads' ), + 'post_status' => __( 'Status', 'easy-digital-downloads' ), + 'categories' => __( 'Categories', 'easy-digital-downloads' ), + 'tags' => __( 'Tags', 'easy-digital-downloads' ), + 'edd_price' => __( 'Price', 'easy-digital-downloads' ), + '_edd_files' => __( 'Files', 'easy-digital-downloads' ), + '_edd_download_limit' => __( 'File Download Limit', 'easy-digital-downloads' ), + '_thumbnail_id' => __( 'Featured Image', 'easy-digital-downloads' ), + 'edd_sku' => __( 'SKU', 'easy-digital-downloads' ), + 'edd_product_notes' => __( 'Notes', 'easy-digital-downloads' ), + '_edd_download_sales' => __( 'Sales', 'easy-digital-downloads' ), + '_edd_download_earnings' => __( 'Earnings', 'easy-digital-downloads' ), + ); + + return $cols; + } + + /** + * Get the export data. + * + * @since 2.5 + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + $data = array(); + + $meta = array( + 'edd_price', + '_edd_files', + '_edd_download_limit', + '_thumbnail_id', + 'edd_sku', + 'edd_product_notes', + '_edd_download_sales', + '_edd_download_earnings', + ); + + $args = array( + 'post_type' => 'download', + 'posts_per_page' => 30, + 'paged' => $this->step, + 'orderby' => 'ID', + 'order' => 'ASC', + ); + + if ( 0 !== $this->download ) { + $args['post__in'] = array( $this->download ); + } + + $downloads = new WP_Query( $args ); + + if ( $downloads->posts ) { + foreach ( $downloads->posts as $download ) { + $row = array(); + + foreach ( $this->csv_cols() as $key => $value ) { + + // Setup default value + $row[ $key ] = ''; + + if ( in_array( $key, $meta ) ) { + switch ( $key ) { + case '_thumbnail_id' : + $image_id = get_post_thumbnail_id( $download->ID ); + $row[ $key ] = wp_get_attachment_url( $image_id ); + break; + + case 'edd_price' : + if ( edd_has_variable_prices( $download->ID ) ) { + $prices = array(); + foreach ( edd_get_variable_prices( $download->ID ) as $price ) { + $prices[] = $price['name'] . ': ' . $price['amount']; + } + + $row[ $key ] = implode( ' | ', $prices ); + } else { + $row[ $key ] = edd_get_download_price( $download->ID ); + } + break; + + case '_edd_files' : + $files = array(); + + foreach ( edd_get_download_files( $download->ID ) as $file ) { + $f = $file['file']; + + if ( edd_has_variable_prices( $download->ID ) ) { + $condition = isset( $file['condition'] ) ? $file['condition'] : 'all'; + $f .= ';' . $condition; + } + + $files[] = $f; + + unset( $file ); + } + + $row[ $key ] = implode( ' | ', $files ); + break; + + default : + $row[ $key ] = get_post_meta( $download->ID, $key, true ); + break; + } + } elseif ( isset( $download->$key ) ) { + switch ( $key ) { + case 'post_author': + $row[ $key ] = get_the_author_meta( 'user_login', $download->post_author ); + break; + + default: + $row[ $key ] = $download->$key; + break; + } + } elseif ( 'tags' == $key ) { + $terms = get_the_terms( $download->ID, 'download_tag' ); + + if ( $terms ) { + $terms = wp_list_pluck( $terms, 'name' ); + $row[ $key ] = implode( ' | ', $terms ); + } + } elseif ( 'categories' == $key ) { + $terms = get_the_terms( $download->ID, 'download_category' ); + + if ( $terms ) { + $terms = wp_list_pluck( $terms, 'name' ); + $row[ $key ] = implode( ' | ', $terms ); + } + } + } + + $data[] = $row; + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } + + return false; + } + + /** + * Return the calculated completion percentage. + * + * @since 2.5 + * + * @return int Percentage complete. + */ + public function get_percentage_complete() { + $args = array( + 'post_type' => 'download', + 'posts_per_page' => - 1, + 'post_status' => 'any', + 'fields' => 'ids', + ); + + if ( 0 !== $this->download ) { + $args['post__in'] = array( $this->download ); + } + + $downloads = new WP_Query( $args ); + $total = (int) $downloads->post_count; + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set the properties specific to the downloads export. + * + * @since 3.0 + * + * @param array $request Form data passed into the batch processor. + */ + public function set_properties( $request ) { + $this->download = isset( $request['download_id'] ) ? absint( $request['download_id'] ) : null; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-earnings-report.php b/includes/admin/reporting/export/class-batch-export-earnings-report.php new file mode 100644 index 00000000000..af4e6ff72a9 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-earnings-report.php @@ -0,0 +1,383 @@ +export_type . '-' . date( 'm' ) . '-' . date( 'Y' ) ) . '.csv"' ); + header( 'Expires: 0' ); + } + + /** + * Get the column headers for the Earnings Report. + * + * @since 2.8.18 + * + * @return array CSV columns. + */ + public function get_csv_cols() { + + // Always start with the date column. + $pre_status_columns = array( + __( 'Monthly Sales Activity', 'easy-digital-downloads' ), + __( 'Gross Activity', 'easy-digital-downloads' ), + ); + + $status_cols = $this->get_status_cols(); + + // Append the arrays together so it starts with the date, then include the status list. + $cols = array_merge( $pre_status_columns, $status_cols ); + + // Include the 'net' after all other columns. + $cols[] = __( 'Net Activity', 'easy-digital-downloads' ); + + return $cols; + } + + /** + * Specifically retrieve the headers for supported order statuses. + * + * @since 2.8.18 + * + * @return array Order status columns. + */ + public function get_status_cols() { + $status_cols = edd_get_payment_statuses(); + $supported_statuses = $this->get_supported_statuses(); + + foreach ( $status_cols as $id => $label ) { + if ( ! in_array( $id, $supported_statuses ) ) { + unset( $status_cols[ $id ] ); + } + } + + return array_values( $status_cols ); + } + + /** + * Get a list of the statuses supported in this report. + * + * @since 2.8.18 + * + * @return array The status keys supported (not labels). + */ + public function get_supported_statuses() { + $statuses = edd_get_payment_statuses(); + + // Unset a few statuses we don't need in the report: + unset( $statuses['pending'], $statuses['processing'], $statuses['preapproval'] ); + $supported_statuses = array_keys( $statuses ); + + return array_unique( apply_filters( 'edd_export_earnings_supported_statuses', $supported_statuses ) ); + } + + /** + * Output the CSV columns. + * + * We make use of this function to set up the header of the earnings report. + * + * @since 2.7 + * + * @return string $col_data CSV cols. + */ + public function print_csv_cols() { + $cols = $this->get_csv_cols(); + $col_data = ''; + $column_count = count( $cols ); + for ( $i = 0; $i < $column_count; $i++ ) { + $col_data .= $cols[ $i ]; + + // We don't need an extra space after the first column. + if ( $i == 0 ) { + $col_data .= ','; + continue; + } + + if ( $i == ( $column_count - 1 ) ) { + $col_data .= "\r\n"; + } else { + $col_data .= ",,"; + } + } + + $statuses = $this->get_supported_statuses(); + $number_cols = count( $statuses ) + 2; + + $col_data .= ','; + for ( $i = 1; $i <= $number_cols; $i++ ) { + $col_data .= __( 'Order Count', 'easy-digital-downloads' ) . ','; + $col_data .= __( 'Gross Amount', 'easy-digital-downloads' ); + + if ( $number_cols !== $i ) { + $col_data .= ','; + } + } + $col_data .= "\r\n"; + + $this->stash_step_data( $col_data ); + + return $col_data; + } + + /** + * Print the CSV rows for the current step. + * + * @since 2.7 + * + * @return mixed string|false + */ + public function print_csv_rows() { + $row_data = ''; + + $data = $this->get_data(); + + if ( $data ) { + $start_date = date( 'Y-m-d', strtotime( $this->start ) ); + + if ( $this->count() == 0 ) { + $end_date = date( 'Y-m-d', strtotime( $this->end ) ); + } else { + $end_date = date( 'Y-m-d', strtotime( 'first day of +1 month', strtotime( $start_date ) ) ); + } + + if ( $this->step == 1 ) { + $row_data .= $start_date . ','; + } elseif ( $this->step > 1 ) { + $start_date = date( 'Y-m-d', strtotime( 'first day of +' . ( $this->step - 1 ) . ' month', strtotime( $start_date ) ) ); + + if ( date( 'Y-m', strtotime( $start_date ) ) == date( 'Y-m', strtotime( $this->end ) ) ) { + $end_date = date( 'Y-m-d', strtotime( $this->end ) ); + $row_data .= $end_date . ','; + } else { + $row_data .= $start_date . ','; + } + } + + $gross_count = 0; + $gross_amount = 0; + foreach ( edd_get_gross_order_statuses() as $status ) { + $gross_count += absint( $data[ $status ]['count'] ); + $gross_amount += $data[ $status ]['amount']; + } + + $row_data .= $gross_count . ','; + $row_data .= '"' . edd_format_amount( $gross_amount ) . '",'; + + foreach ( $data as $status => $status_data ) { + $row_data .= isset( $data[ $status ]['count'] ) ? $data[ $status ]['count'] . ',' : 0 . ','; + + $column_amount = isset( $data[ $status ]['amount'] ) ? edd_format_amount( $data[ $status ]['amount'] ) : 0; + if ( ! empty( $column_amount ) && 'refunded' == $status ) { + $column_amount = '-' . $column_amount; + } + + $row_data .= isset( $data[ $status ]['amount'] ) ? '"' . $column_amount . '"' . ',' : 0 . ','; + } + + // Allows extensions with other 'completed' statuses to alter net earnings, like recurring. + $completed_statuses = array_unique( apply_filters( 'edd_export_earnings_completed_statuses', edd_get_net_order_statuses() ) ); + + $net_count = 0; + $net_amount = 0; + foreach ( $completed_statuses as $status ) { + if ( ! isset( $data[ $status ] ) ) { + continue; + } + $net_count += absint( $data[ $status ]['count'] ); + $net_amount += floatval( $data[ $status ]['amount'] ); + } + if ( ! empty( $this->partial_refunds ) ) { + $net_amount += floatval( $this->partial_refunds['total'] ); + } + $row_data .= $net_count . ','; + $row_data .= '"' . edd_format_amount( $net_amount ) . '"'; + + $row_data .= "\r\n"; + + $this->stash_step_data( $row_data ); + + return $row_data; + } + + return false; + } + + /** + * Get the Export Data. + * + * @since 2.7 + * + * @return array $data The data for the CSV file + */ + public function get_data() { + global $wpdb; + + $data = array(); + + $start_date = date( 'Y-m-d 00:00:00', strtotime( $this->start ) ); + $end_date = date( 'Y-m-t 23:59:59', strtotime( $this->start ) ); + + if ( $this->step > 1 ) { + $start_timestamp = strtotime( 'first day of +' . ( $this->step - 1 ) . ' month', strtotime( $start_date ) ); + $start_date = date( 'Y-m-d 00:00:00', $start_timestamp ); + $end_date = date( 'Y-m-t 23:59:59', $start_timestamp ); + } + + if ( strtotime( $start_date ) > strtotime( $this->end ) ) { + return false; + } + + $statuses = $this->get_supported_statuses(); + $totals = $wpdb->get_results( $wpdb->prepare( + "SELECT SUM(total) AS total, COUNT(DISTINCT id) AS count, status + FROM {$wpdb->edd_orders} + WHERE type = 'sale' + AND date_created >= %s AND date_created <= %s + GROUP BY YEAR(date_created), MONTH(date_created), status + ORDER by date_created ASC", $start_date, $end_date ), ARRAY_A ); + + $total_data = array(); + foreach ( $totals as $row ) { + $total_data[ $row['status'] ] = array( + 'count' => $row['count'], + 'amount' => floatval( $row['total'] ), + ); + } + + foreach ( $statuses as $status ) { + + if ( ! isset( $total_data[ $status ] ) ) { + $data[ $status ] = array( + 'count' => 0, + 'amount' => 0, + ); + } else { + $data[ $status ] = array( + 'count' => $total_data[ $status ]['count'], + 'amount' => $total_data[ $status ]['amount'], + ); + } + } + + // Get partial refund amounts to factor into net activity amounts. + $partial_refunds = $wpdb->get_results( + $wpdb->prepare( + "SELECT SUM(total) AS total, COUNT(DISTINCT id) AS count + FROM {$wpdb->edd_orders} + WHERE type = 'refund' + AND parent IN ( + SELECT id + FROM {$wpdb->edd_orders} + WHERE status = 'partially_refunded' + AND date_created >= %s + AND date_created <= %s + GROUP BY YEAR(date_created), MONTH(date_created) + ORDER by date_created ASC + );", + $start_date, + $end_date + ), + ARRAY_A + ); + if ( ! empty( $partial_refunds ) ) { + $this->partial_refunds = reset( $partial_refunds ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data, $start_date, $end_date ); + + return $data; + } + + /** + * Count the number of months we are dealing with. + * + * @since 2.7 + * @access private + * + * @return void + */ + private function count() { + return abs( ( date( 'Y', strtotime( $this->end ) ) - date( 'Y', strtotime( $this->start ) ) ) * 12 + ( date( 'm', strtotime( $this->end ) ) - date( 'm', strtotime( $this->start ) ) ) ); + } + + /** + * Return the calculated completion percentage + * + * @since 2.7 + * + * @return int Percentage of batch processing complete. + */ + public function get_percentage_complete() { + $percentage = 100; + + $total = $this->count(); + + if ( $total > 0 ) { + $percentage = ( $this->step / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set the properties specific to the earnings report. + * + * @since 2.7 + * + * @param array $request The Form Data passed into the batch processing + * @return void + */ + public function set_properties( $request ) { + $this->start = ( isset( $request['start_month'] ) && isset( $request['start_year'] ) ) ? sanitize_text_field( $request['start_year'] ) . '-' . sanitize_text_field( $request['start_month'] ) . '-1' : ''; + $this->end = ( isset( $request['end_month'] ) && isset( $request['end_year'] ) ) ? sanitize_text_field( $request['end_year'] ) . '-' . sanitize_text_field( $request['end_month'] ) . '-' . cal_days_in_month( CAL_GREGORIAN, sanitize_text_field( $request['end_month'] ), sanitize_text_field( $request['end_year'] ) ) : ''; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-file-downloads.php b/includes/admin/reporting/export/class-batch-export-file-downloads.php new file mode 100644 index 00000000000..77fe72984b8 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-file-downloads.php @@ -0,0 +1,162 @@ + __( 'Date', 'easy-digital-downloads' ), + 'user' => __( 'Downloaded by', 'easy-digital-downloads' ), + 'ip' => __( 'IP Address', 'easy-digital-downloads' ), + 'user_agent' => __( 'User Agent', 'easy-digital-downloads' ), + 'download' => __( 'Product', 'easy-digital-downloads' ), + 'file' => __( 'File', 'easy-digital-downloads' ), + ); + + return $cols; + } + + /** + * Get the export data. + * + * @since 2.4 + * @since 3.0 Refactored to use new query methods. + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + $data = array(); + + $args = array( + 'number' => 30, + 'offset' => ( $this->step * 30 ) - 30, + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_created_query'] = $this->get_date_query(); + } + + if ( 0 !== $this->download_id ) { + $args['product_id'] = $this->download_id; + } + + $logs = edd_get_file_download_logs( $args ); + + foreach ( $logs as $log ) { + /** @var EDD\Logs\File_Download_Log $log */ + + $files = edd_get_download_files( $log->product_id ); + $file_id = $log->file_id; + $file_name = isset( $files[ $file_id ]['name'] ) ? $files[ $file_id ]['name'] : null; + $customer = edd_get_customer( $log->customer_id ); + + if ( $customer ) { + $customer = $customer->email; + if ( ! empty( $customer->name ) ) { + $customer = $customer->name; + if ( preg_match( '~^[+\-=@]~m', $customer ) ) { + $customer = "'{$customer}"; + } + } + } else { + $order = edd_get_order( $log->order_id ); + + if ( $order ) { + $customer = $order->email; + } + } + + $data[] = array( + 'date' => $log->date_created, + 'user' => $customer, + 'ip' => $log->ip, + 'user_agent' => $log->user_agent, + 'download' => get_the_title( $log->product_id ), + 'file' => $file_name, + ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return ! empty( $data ) + ? $data + : false; + } + + /** + * Return the calculated completion percentage. + * + * @since 2.4 + * @since 3.0 Updated to use new query methods. + * + * @return int Percentage complete. + */ + public function get_percentage_complete() { + $args = array( + 'fields' => 'ids', + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_created_query'] = $this->get_date_query(); + } + + if ( 0 !== $this->download_id ) { + $args['download_id'] = $this->download_id; + } + + $total = edd_count_file_download_logs( $args ); + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + public function set_properties( $request ) { + $this->start = isset( $request['file-download-export-start'] ) ? sanitize_text_field( $request['file-download-export-start'] ) : ''; + $this->end = isset( $request['file-download-export-end'] ) ? sanitize_text_field( $request['file-download-export-end'] ) : ''; + $this->download_id = isset( $request['download_id'] ) ? absint( $request['download_id'] ) : 0; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-payments.php b/includes/admin/reporting/export/class-batch-export-payments.php new file mode 100644 index 00000000000..1f2a55a600c --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-payments.php @@ -0,0 +1,281 @@ + __( 'Order ID', 'easy-digital-downloads' ), // unaltered payment ID (use for querying) + 'seq_id' => __( 'Order Number', 'easy-digital-downloads' ), // sequential payment ID + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'customer_id' => __( 'Customer ID', 'easy-digital-downloads' ), + 'name' => __( 'Customer Name', 'easy-digital-downloads' ), + 'address1' => __( 'Address', 'easy-digital-downloads' ), + 'address2' => __( 'Address (Line 2)', 'easy-digital-downloads' ), + 'city' => __( 'City', 'easy-digital-downloads' ), + 'state' => __( 'State', 'easy-digital-downloads' ), + 'country' => __( 'Country', 'easy-digital-downloads' ), + 'zip' => __( 'Zip / Postal Code', 'easy-digital-downloads' ), + 'products' => __( 'Products (Verbose)', 'easy-digital-downloads' ), + 'products_raw' => __( 'Products (Raw)', 'easy-digital-downloads' ), + 'skus' => __( 'SKUs', 'easy-digital-downloads' ), + 'currency' => __( 'Currency', 'easy-digital-downloads' ), + 'amount' => __( 'Amount', 'easy-digital-downloads' ), + 'tax' => __( 'Tax', 'easy-digital-downloads' ), + 'discount' => __( 'Discount Code', 'easy-digital-downloads' ), + 'gateway' => __( 'Payment Method', 'easy-digital-downloads' ), + 'trans_id' => __( 'Transaction ID', 'easy-digital-downloads' ), + 'key' => __( 'Purchase Key', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ), + 'user' => __( 'User', 'easy-digital-downloads' ), + 'ip' => __( 'IP Address', 'easy-digital-downloads' ), + 'mode' => __( 'Mode (Live|Test)', 'easy-digital-downloads' ), + 'status' => __( 'Status', 'easy-digital-downloads' ), + 'country_name' => __( 'Country Name', 'easy-digital-downloads' ), + 'state_name' => __( 'State Name', 'easy-digital-downloads' ), + ); + + if ( ! edd_use_skus() ){ + unset( $cols['skus'] ); + } + + if ( ! edd_get_option( 'enable_sequential' ) ) { + unset( $cols['seq_id'] ); + } + + return $cols; + } + + /** + * Get the export data. + * + * @since 2.4 + * @since 3.0 Updated to use new query methods. + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + $data = array(); + + $args = array( + 'number' => 30, + 'offset' => ( $this->step * 30 ) - 30, + 'status' => $this->status, + 'order' => 'ASC', + 'orderby' => 'date_created', + 'type' => 'sale', + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + if ( in_array( $args['status'], array( 'any', 'all' ), true ) ) { + unset( $args['status'] ); + $args['status__not_in'] = array( 'trash' ); + } + + $orders = edd_get_orders( $args ); + + foreach ( $orders as $order ) { + /** @var EDD\Orders\Order $order */ + + $items = $order->get_items(); + $address = $order->get_address(); + $total = $order->total; + $user_id = $order->id && $order->id != - 1 ? $order->id : $order->email; + $customer = edd_get_customer( $order->customer_id ); + $products = ''; + $products_raw = ''; + $skus = ''; + + $discounts = $order->get_discounts(); + $discounts = ! empty( $discounts ) + ? implode( ', ', $discounts ) + : __( 'none', 'easy-digital-downloads' ); + + foreach ( $items as $key => $item ) { + /** @var EDD\Orders\Order_Item $item */ + + // Setup item information. + $id = $item->product_id; + $qty = $item->quantity; + $price = $item->amount; + $tax = $item->tax; + $price_id = $item->price_id; + + // Set up verbose product column. + $products .= html_entity_decode( get_the_title( $id ) ); + + if ( $qty > 1 ) { + $products .= html_entity_decode( ' (' . $qty . ')' ); + } + + $products .= ' - '; + + if ( edd_use_skus() ) { + $sku = edd_get_download_sku( $id ); + + if ( ! empty( $sku ) ) { + $skus .= $sku; + } + } + + if ( 0 < $item->price_id ) { + $products .= html_entity_decode( edd_get_price_option_name( $id, $item->price_id, $order->id ) ) . ' - '; + } + + $products .= html_entity_decode( edd_currency_filter( edd_format_amount( $price ), $order->currency ) ); + + if ( $key != ( count( $items ) -1 ) ) { + $products .= ' / '; + + if ( edd_use_skus() ) { + $skus .= ' / '; + } + } + + // Set up raw products column; nothing but product names. + $products_raw .= html_entity_decode( get_the_title( $id ) ) . '|' . $price . '{' . $tax . '}'; + + // If we have a price ID, include it. + if ( false !== $price_id ) { + $products_raw .= '{' . $price_id . '}'; + } + + if ( $key != ( count( $items ) -1 ) ) { + $products_raw .= ' / '; + } + } + + $user = is_numeric( $user_id ) + ? get_userdata( $user_id ) + : false; + + $name = ! empty( $customer->name ) ? $customer->name : ''; + if ( preg_match( '~^[+\-=@]~m', $name ) ) { + $name = "'{$name}"; + } + + $data[] = array( + 'id' => $order->id, + 'seq_id' => $order->get_number(), + 'email' => $order->email, + 'customer_id' => $order->customer_id, + 'name' => $name, + 'address1' => isset( $address->address ) ? $address->address : '', + 'address2' => isset( $address->address2 ) ? $address->address2 : '', + 'city' => isset( $address->city ) ? $address->city : '', + 'state' => isset( $address->region ) ? $address->region : '', + 'country' => isset( $address->country ) ? $address->country : '', + 'zip' => isset( $address->postal_code ) ? $address->postal_code : '', + 'products' => $products, + 'products_raw' => $products_raw, + 'skus' => $skus, + 'currency' => $order->currency, + 'amount' => html_entity_decode( edd_format_amount( $total ) ), // The non-discounted item price + 'tax' => html_entity_decode( edd_format_amount( $order->tax ) ), + 'discount' => $discounts, + 'gateway' => edd_get_gateway_admin_label( $order->gateway ), + 'trans_id' => $order->get_transaction_id(), + 'key' => $order->payment_key, + 'date' => $order->date_created, + 'user' => $user ? $user->display_name : __( 'guest', 'easy-digital-downloads' ), + 'ip' => $order->ip, + 'mode' => $order->mode, + 'status' => $order->status, + 'country_name' => isset( $address->country ) ? edd_get_country_name( $address->country ) : '', + 'state_name' => isset( $address->country ) && isset( $address->region ) ? edd_get_state_name( $address->country, $address->region ) : '', + ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return ! empty( $data ) + ? $data + : false; + } + + /** + * Return the calculated completion percentage + * + * @since 2.4 + * @since 3.0 Updated to use new query methods. + * + * @return int + */ + public function get_percentage_complete() { + $args = array( + 'fields' => 'ids', + 'status' => $this->status, + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + if ( in_array( $args['status'], array( 'any', 'all' ), true ) ) { + unset( $args['status'] ); + } + + $total = edd_count_orders( $args ); + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set the properties specific to the payments export + * + * @since 2.4.2 + * + * @param array $request The Form Data passed into the batch processing + */ + public function set_properties( $request ) { + $this->start = isset( $request['orders-export-start'] ) ? sanitize_text_field( $request['orders-export-start'] ) : ''; + $this->end = isset( $request['orders-export-end'] ) ? sanitize_text_field( $request['orders-export-end'] ) : ''; + $this->status = isset( $request['status'] ) ? sanitize_text_field( $request['status'] ) : 'complete'; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-sales-and-earnings.php b/includes/admin/reporting/export/class-batch-export-sales-and-earnings.php new file mode 100644 index 00000000000..4d0ad54dff8 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-sales-and-earnings.php @@ -0,0 +1,238 @@ + __( 'Date', 'easy-digital-downloads' ), + 'sales' => __( 'Sales', 'easy-digital-downloads' ), + 'earnings' => __( 'Earnings', 'easy-digital-downloads' ), + ); + + return $cols; + } + + /** + * Get the export data. + * + * @since 3.0 + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + global $wpdb; + + $data = array(); + + $args = array( + 'number' => 30, + 'offset' => ( $this->step * 30 ) - 30, + ); + + $status = "AND {$wpdb->edd_orders}.status IN ( '" . implode( "', '", $wpdb->_escape( edd_get_complete_order_statuses() ) ) . "' )"; + $date_query_sql = ''; + + // Customer ID. + $customer_id = ! empty( $this->customer_id ) + ? $wpdb->prepare( "AND {$wpdb->edd_orders}.customer_id = %d", $this->customer_id ) + : ''; + + // Download ID. + $download_id = ! empty( $this->download_id ) + ? $wpdb->prepare( "AND {$wpdb->edd_order_items}.product_id = %d", $this->download_id ) + : ''; + + // Generate date query SQL if dates have been set. + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + + // Fetch GMT offset. + $offset = EDD()->utils->get_gmt_offset(); + + $date_query_sql = 'AND '; + + if ( ! empty( $this->start ) ) { + $this->start = date( 'Y-m-d 00:00:00', strtotime( $this->start ) ); + + $this->start = 0 < $offset + ? EDD()->utils->date( $this->start )->subSeconds( $offset )->format( 'mysql' ) + : EDD()->utils->date( $this->start )->addSeconds( $offset )->format( 'mysql' ); + + $date_query_sql .= $wpdb->prepare( "{$wpdb->edd_orders}.date_created >= %s", $this->start ); + } + + // Join dates with `AND` if start and end date set. + if ( ! empty( $this->start ) && ! empty( $this->end ) ) { + $this->end = date( 'Y-m-d 23:59:59', strtotime( $this->end ) ); + + $this->end = 0 < $offset + ? EDD()->utils->date( $this->end )->addSeconds( $offset )->format( 'mysql' ) + : EDD()->utils->date( $this->end )->subSeconds( $offset )->format( 'mysql' ); + + $date_query_sql .= ' AND '; + } + + if ( ! empty( $this->end ) ) { + $date_query_sql .= $wpdb->prepare( "{$wpdb->edd_orders}.date_created <= %s", $this->end ); + } + } + + // Look in orders table if a product ID was not passed. + if ( 0 === $this->download_id ) { + $sql = " + SELECT COUNT(id) AS sales, SUM(total) AS earnings, date_created + FROM {$wpdb->edd_orders} + WHERE 1=1 {$status} {$customer_id} {$date_query_sql} + GROUP BY YEAR(date_created), MONTH(date_created), DAY(date_created) + ORDER BY YEAR(date_created), MONTH(date_created), DAY(date_created) ASC + LIMIT {$args['offset']}, {$args['number']} + "; + + // Join orders and order items table. + } else { + $sql = " + SELECT SUM({$wpdb->edd_order_items}.quantity) AS sales, SUM({$wpdb->edd_order_items}.total) AS earnings, {$wpdb->edd_orders}.date_created + FROM {$wpdb->edd_orders} + INNER JOIN {$wpdb->edd_order_items} ON {$wpdb->edd_orders}.id = {$wpdb->edd_order_items}.order_id + WHERE 1=1 {$status} {$download_id} {$date_query_sql} + GROUP BY YEAR({$wpdb->edd_orders}.date_created), MONTH({$wpdb->edd_orders}.date_created), DAY({$wpdb->edd_orders}.date_created) + ORDER BY YEAR({$wpdb->edd_orders}.date_created), MONTH({$wpdb->edd_orders}.date_created), DAY({$wpdb->edd_orders}.date_created) ASC + LIMIT {$args['offset']}, {$args['number']} + "; + } + + $results = $wpdb->get_results( $sql ); + + foreach ( $results as $result ) { + + // Localize the returned time. + $d = EDD()->utils->date( $result->date_created, null, true )->format( 'date' ); + + $sales = isset( $result->sales ) + ? absint( $result->sales ) + : 0; + + $earnings = isset( $result->earnings ) + ? edd_format_amount( $result->earnings ) + : floatval( 0 ); + + $data[] = array( + 'date' => $d, + 'sales' => $sales, + 'earnings' => $earnings, + ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } + + /** + * Return the calculated completion percentage + * + * @since 2.4 + * @since 3.0 Updated to use new query methods. + * + * @return int + */ + public function get_percentage_complete() { + $args = array( + 'fields' => 'ids', + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + $total = edd_count_orders( $args ); + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set the properties specific to the sales and earnings export. + * + * @since 3.0 + * + * @param array $request Form data passed to the batch processor. + */ + public function set_properties( $request ) { + $this->start = isset( $request['order-export-start'] ) + ? sanitize_text_field( $request['order-export-start'] ) + : ''; + + $this->end = isset( $request['order-export-end'] ) + ? sanitize_text_field( $request['order-export-end'] ) + : ''; + + $this->download_id = isset( $request['download_id'] ) + ? absint( $request['download_id'] ) + : 0; + + $this->customer_id = isset( $request['customer_id'] ) + ? absint( $request['customer_id'] ) + : 0; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-sales.php b/includes/admin/reporting/export/class-batch-export-sales.php new file mode 100644 index 00000000000..bf6b435fb1b --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-sales.php @@ -0,0 +1,197 @@ + __( 'Product ID', 'easy-digital-downloads' ), + 'user_id' => __( 'User', 'easy-digital-downloads' ), + 'customer_id' => __( 'Customer ID', 'easy-digital-downloads' ), + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'download' => edd_get_label_singular(), + 'quantity' => __( 'Quantity', 'easy-digital-downloads' ), + 'amount' => __( 'Item Amount', 'easy-digital-downloads' ), + 'currency' => __( 'Currency', 'easy-digital-downloads' ), + 'order_id' => __( 'Order ID', 'easy-digital-downloads' ), + 'price_id' => __( 'Price ID', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ), + ); + } + + /** + * Get the Export Data + * + * @since 2.7 + * @since 3.0 Updated to use new query methods. + * + * @return array|bool The data for the CSV file, false if no data to return. + */ + public function get_data() { + $data = array(); + + $args = array_merge( + $this->get_order_item_args(), + array( + 'number' => 30, + 'offset' => ( $this->step * 30 ) - 30, + 'order' => 'ASC' + ) + ); + + $items = edd_get_order_items( $args ); + + foreach ( $items as $item ) { + + if ( 'refunded' === $item->status ) { + continue; + } + + /** @var EDD\Orders\Order_Item $item */ + $order = edd_get_order( $item->order_id ); + + // If the item has been partially refunded, we need to calculate the amount + $amount = array_reduce( + $item->get_refunded_items(), + function( $total, $refund_item ) { + return $total + $refund_item->total; + }, + $item->total + ); + + $data[] = array( + 'ID' => $item->product_id, + 'user_id' => $order->user_id, + 'customer_id' => $order->customer_id, + 'email' => $order->email, + 'name' => edd_get_customer_field( $order->customer_id, 'name' ), + 'download' => $item->product_name, + 'quantity' => $item->quantity, + 'amount' => edd_format_amount( $amount ), + 'currency' => $order->currency, + 'order_id' => $order->id, + 'price_id' => $item->price_id, + 'date' => $order->date_created, + ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return ! empty( $data ) + ? $data + : false; + } + /** + * Return the calculated completion percentage. + * + * @since 2.7 + * @since 3.0 Updated to use new query methods. + * + * @return int + */ + public function get_percentage_complete() { + $args = $this->get_order_item_args(); + $total = edd_count_order_items( $args ); + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Gets the default order item parameters based on the class properties. + * + * @since 3.1.1.4 + * @return array + */ + private function get_order_item_args() { + $args = array(); + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + if ( ! empty( $this->download_id ) ) { + $args['product_id'] = $this->download_id; + } + + if ( ! empty( $this->orders ) ) { + $args['order_id__in'] = $this->orders; + } + + return $args; + } + + public function set_properties( $request ) { + $this->start = isset( $request['sales-export-start'] ) ? sanitize_text_field( $request['sales-export-start'] ) : ''; + $this->end = isset( $request['sales-export-end'] ) ? sanitize_text_field( $request['sales-export-end'] ) : ''; + $this->download_id = isset( $request['download_id'] ) ? absint( $request['download_id'] ) : 0; + $this->orders = $this->get_orders(); + } + + /** + * Gets the array of complete order IDs for the time period. + * + * @return array + */ + private function get_orders() { + $args = array( + 'fields' => 'ids', + 'type' => 'sale', + 'number' => 999999999, + 'status__in' => edd_get_complete_order_statuses(), + ); + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + return edd_get_orders( $args ); + } +} diff --git a/includes/admin/reporting/export/class-batch-export-taxed-customers.php b/includes/admin/reporting/export/class-batch-export-taxed-customers.php new file mode 100644 index 00000000000..2ea4e1ad41e --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-taxed-customers.php @@ -0,0 +1,183 @@ + __( 'ID', 'easy-digital-downloads' ), + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'purchases' => __( 'Number of Purchases', 'easy-digital-downloads' ), + 'amount' => __( 'Customer Value', 'easy-digital-downloads' ), + ); + + return $cols; + } + + /** + * Get the export data. + * + * @since 3.0 + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + $data = array(); + + $args = array( + 'number' => 30, + 'offset' => ( $this->step * 30 ) - 30, + 'status__in' => edd_get_complete_order_statuses(), + 'order' => 'ASC', + 'orderby' => 'date_created', + 'fields' => 'customer_id', + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + add_filter( 'edd_orders_query_clauses', array( $this, 'query_clauses' ), 10, 2 ); + + $customer_ids = edd_get_orders( $args ); + + remove_filter( 'edd_orders_query_clauses', array( $this, 'query_clauses' ), 10 ); + + $customer_ids = array_unique( $customer_ids ); + + asort( $customer_ids ); + + foreach ( $customer_ids as $customer_id ) { + + // Bail if a customer ID was not set. + if ( 0 === $customer_id ) { + continue; + } + + $customer = edd_get_customer( $customer_id ); + + // Bail if a customer record does not exist. + if ( ! $customer ) { + continue; + } + + $name = ! empty( $customer->name ) ? $customer->name : ''; + if ( preg_match( '~^[+\-=@]~m', $name ) ) { + $name = "'{$name}"; + } + + $data[] = array( + 'id' => $customer->id, + 'name' => $name, + 'email' => $customer->email, + 'purchases' => $customer->purchase_count, + 'amount' => edd_format_amount( $customer->purchase_value ), + ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return $data; + } + + /** + * Return the calculated completion percentage. + * + * @since 3.0 + * + * @return int + */ + public function get_percentage_complete() { + $args = array( + 'fields' => 'ids', + 'status__in' => edd_get_complete_order_statuses(), + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_query'] = $this->get_date_query(); + } + + add_filter( 'edd_orders_query_clauses', array( $this, 'query_clauses' ), 10, 2 ); + + $total = edd_count_orders( $args ); + + remove_filter( 'edd_orders_query_clauses', array( $this, 'query_clauses' ), 10 ); + + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set the properties specific to the taxed orders export. + * + * @since 3.0 + * + * @param array $request The form data passed into the batch processing. + */ + public function set_properties( $request ) { + $this->start = isset( $request['taxed-customers-export-start'] ) ? sanitize_text_field( $request['taxed-customers-export-start'] ) : ''; + $this->end = isset( $request['taxed-customers-export-end'] ) ? sanitize_text_field( $request['taxed-customers-export-end'] ) : ''; + } + + /** + * Filter the database query to only return orders which have tax applied to them. + * + * @since 3.0 + * + * @param array $clauses A compacted array of item query clauses. + * @param \EDD\Database\Query $base Instance passed by reference. + * + * @return array + */ + public function query_clauses( $clauses, $base ) { + $clauses['where'] = ! empty( $clauses['where'] ) + ? $clauses['where'] .= ' AND tax > 0' + : 'tax > 0'; + + return $clauses; + } +} diff --git a/includes/admin/reporting/export/class-batch-export-taxed-orders.php b/includes/admin/reporting/export/class-batch-export-taxed-orders.php new file mode 100644 index 00000000000..12151951ed5 --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export-taxed-orders.php @@ -0,0 +1,307 @@ + __( 'Order ID', 'easy-digital-downloads' ), // unaltered payment ID (use for querying) + 'seq_id' => __( 'Order Number', 'easy-digital-downloads' ), // sequential payment ID + 'email' => __( 'Email', 'easy-digital-downloads' ), + 'customer_id' => __( 'Customer ID', 'easy-digital-downloads' ), + 'first' => __( 'First Name', 'easy-digital-downloads' ), + 'last' => __( 'Last Name', 'easy-digital-downloads' ), + 'address1' => __( 'Address', 'easy-digital-downloads' ), + 'address2' => __( 'Address (Line 2)', 'easy-digital-downloads' ), + 'city' => __( 'City', 'easy-digital-downloads' ), + 'state' => __( 'State', 'easy-digital-downloads' ), + 'country' => __( 'Country', 'easy-digital-downloads' ), + 'zip' => __( 'Zip / Postal Code', 'easy-digital-downloads' ), + 'products' => __( 'Products (Verbose)', 'easy-digital-downloads' ), + 'products_raw' => __( 'Products (Raw)', 'easy-digital-downloads' ), + 'skus' => __( 'SKUs', 'easy-digital-downloads' ), + 'amount' => __( 'Amount', 'easy-digital-downloads' ) . ' (' . html_entity_decode( edd_currency_filter( '' ) ) . ')', + 'tax' => __( 'Tax', 'easy-digital-downloads' ) . ' (' . html_entity_decode( edd_currency_filter( '' ) ) . ')', + 'discount' => __( 'Discount Code', 'easy-digital-downloads' ), + 'gateway' => __( 'Gateway', 'easy-digital-downloads' ), + 'trans_id' => __( 'Transaction ID', 'easy-digital-downloads' ), + 'key' => __( 'Purchase Key', 'easy-digital-downloads' ), + 'date' => __( 'Date', 'easy-digital-downloads' ), + 'user' => __( 'User', 'easy-digital-downloads' ), + 'currency' => __( 'Currency', 'easy-digital-downloads' ), + 'ip' => __( 'IP Address', 'easy-digital-downloads' ), + 'mode' => __( 'Mode (Live|Test)', 'easy-digital-downloads' ), + 'status' => __( 'Status', 'easy-digital-downloads' ), + 'country_name' => __( 'Country Name', 'easy-digital-downloads' ), + ); + + if ( ! edd_use_skus() ) { + unset( $cols['skus'] ); + } + + if ( ! edd_get_option( 'enable_sequential' ) ) { + unset( $cols['seq_id'] ); + } + + return $cols; + } + + /** + * Get the export data. + * + * @since 3.0 + * + * @return array $data The data for the CSV file. + */ + public function get_data() { + $data = array(); + + $args = array( + 'number' => 30, + 'offset' => ( $this->step * 30 ) - 30, + 'status' => $this->status, + 'order' => 'ASC', + 'orderby' => 'date_created', + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_created_query'] = $this->get_date_query(); + } + + if ( in_array( $args['status'], array( 'any', 'all' ), true ) ) { + unset( $args['status'] ); + $args['status__not_in'] = array( 'trash' ); + } + + add_filter( 'edd_orders_query_clauses', array( $this, 'query_clauses' ), 10, 2 ); + + $orders = edd_get_orders( $args ); + + remove_filter( 'edd_orders_query_clauses', array( $this, 'query_clauses' ), 10 ); + + foreach ( $orders as $order ) { + /** @var EDD\Orders\Order $order */ + + $items = $order->get_items(); + $address = $order->get_address(); + $total = $order->total; + $user_id = $order->user_id; + $products = ''; + $products_raw = ''; + $skus = ''; + + $discounts = $order->get_discounts(); + $discounts = ! empty( $discounts ) + ? implode( ', ', $discounts ) + : __( 'none', 'easy-digital-downloads' ); + + foreach ( $items as $key => $item ) { + /** @var EDD\Orders\Order_Item $item */ + + // Setup item information. + $id = $item->product_id; + $qty = $item->quantity; + $price = $item->amount; + $tax = $item->tax; + $price_id = $item->price_id; + + // Set up verbose product column. + $products .= html_entity_decode( get_the_title( $id ) ); + + if ( $qty > 1 ) { + $products .= html_entity_decode( ' (' . $qty . ')' ); + } + + $products .= ' - '; + + if ( edd_use_skus() ) { + $sku = edd_get_download_sku( $id ); + + if ( ! empty( $sku ) ) { + $skus .= $sku; + } + } + + if ( 0 < $item->price_id ) { + $products .= html_entity_decode( edd_get_price_option_name( $id, $item->price_id, $order->id ) ) . ' - '; + } + + $products .= html_entity_decode( edd_currency_filter( edd_format_amount( $price ) ) ); + + if ( ( count( $items ) - 1 ) !== $key ) { + $products .= ' / '; + + if ( edd_use_skus() ) { + $skus .= ' / '; + } + } + + // Set up raw products column; nothing but product names. + $products_raw .= html_entity_decode( get_the_title( $id ) ) . '|' . $price . '{' . $tax . '}'; + + // If we have a price ID, include it. + if ( false !== $price_id ) { + $products_raw .= '{' . $price_id . '}'; + } + + if ( ( count( $items ) - 1 ) !== $key ) { + $products_raw .= ' / '; + } + } + + $user = is_numeric( $user_id ) + ? get_userdata( $user_id ) + : false; + + $data[] = array( + 'id' => $order->id, + 'seq_id' => $order->get_number(), + 'email' => $order->email, + 'customer_id' => $order->customer_id, + 'first' => $address->first_name, + 'last' => $address->last_name, + 'address1' => $address->address, + 'address2' => $address->address2, + 'city' => $address->city, + 'state' => $address->region, + 'country' => $address->country, + 'zip' => $address->postal_code, + 'products' => $products, + 'products_raw' => $products_raw, + 'skus' => $skus, + 'amount' => html_entity_decode( edd_format_amount( $total ) ), // The non-discounted item price + 'tax' => html_entity_decode( edd_format_amount( $order->tax ) ), + 'discount' => $discounts, + 'gateway' => edd_get_gateway_admin_label( $order->gateway ), + 'trans_id' => $order->get_transaction_id(), + 'key' => $order->payment_key, + 'date' => $order->date_created, + 'user' => $user ? $user->display_name : __( 'guest', 'easy-digital-downloads' ), + 'currency' => $order->currency, + 'ip' => $order->ip, + 'mode' => $order->mode, + 'status' => $order->status, + 'country_name' => isset( $user_info['address']['country'] ) ? edd_get_country_name( $user_info['address']['country'] ) : '', + ); + } + + $data = apply_filters( 'edd_export_get_data', $data ); + $data = apply_filters( 'edd_export_get_data_' . $this->export_type, $data ); + + return ! empty( $data ) + ? $data + : false; + } + + /** + * Return the calculated completion percentage. + * + * @since 3.0 + * + * @return int + */ + public function get_percentage_complete() { + $args = array( + 'fields' => 'ids', + 'status' => $this->status, + ); + + if ( ! empty( $this->start ) || ! empty( $this->end ) ) { + $args['date_created_query'] = $this->get_date_query(); + } + + if ( in_array( $args['status'], array( 'any', 'all' ), true ) ) { + unset( $args['status'] ); + } + + $total = edd_count_orders( $args ); + $percentage = 100; + + if ( $total > 0 ) { + $percentage = ( ( 30 * $this->step ) / $total ) * 100; + } + + if ( $percentage > 100 ) { + $percentage = 100; + } + + return $percentage; + } + + /** + * Set the properties specific to the taxed orders export. + * + * @since 3.0 + * + * @param array $request The form data passed into the batch processing. + */ + public function set_properties( $request ) { + $this->start = isset( $request['taxed-orders-export-start'] ) ? sanitize_text_field( $request['taxed-orders-export-start'] ) : ''; + $this->end = isset( $request['taxed-orders-export-end'] ) ? sanitize_text_field( $request['taxed-orders-export-end'] ) : ''; + $this->status = isset( $request['status'] ) ? sanitize_text_field( $request['status'] ) : 'complete'; + $this->country = isset( $request['country'] ) ? sanitize_text_field( $request['country'] ) : ''; + $this->region = isset( $request['region'] ) ? sanitize_text_field( $request['region'] ) : ''; + } + + /** + * Filter the database query to only return orders which have tax applied to them. + * + * @since 3.0 + * + * @param array $clauses A compacted array of item query clauses. + * @param \EDD\Database\Query $base Instance passed by reference. + * + * @return array + */ + public function query_clauses( $clauses, $base ) { + global $wpdb; + + $clauses['where'] = ! empty( $clauses['where'] ) + ? $clauses['where'] .= ' AND edd_o.tax > 0' + : 'edd_o.tax > 0'; + + if ( ! empty( $this->country ) ) { + $clauses['join'] = " INNER JOIN {$wpdb->edd_order_addresses} edd_oa ON edd_o.id = edd_oa.order_id"; + $clauses['where'] .= $wpdb->prepare( ' AND edd_oa.country = %s', $this->country ); + } + + if ( ! empty( $this->region ) ) { + $clauses['where'] .= $wpdb->prepare( ' AND edd_oa.region = %s', $this->region ); + } + + return $clauses; + } +} diff --git a/includes/admin/reporting/export/class-batch-export.php b/includes/admin/reporting/export/class-batch-export.php new file mode 100644 index 00000000000..9491cfb8f9a --- /dev/null +++ b/includes/admin/reporting/export/class-batch-export.php @@ -0,0 +1,349 @@ +filetype = '.csv'; + $this->filename = 'edd-' . $this->export_type . $this->filetype; + $this->file = trailingslashit( $upload_dir['basedir'] ) . $this->filename; + + if ( ! is_writeable( $upload_dir['basedir'] ) ) { + $this->is_writable = false; + } + + $this->step = $_step; + $this->done = false; + } + + /** + * Process a step + * + * @since 2.4 + * @return bool + */ + public function process_step() { + + if ( ! $this->can_export() ) { + wp_die( __( 'You do not have permission to export data.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + if( $this->step < 2 ) { + + // Make sure we start with a fresh file on step 1 + if ( file_exists( $this->file ) ) { + unlink( $this->file ); + } + $this->print_csv_cols(); + } + + $rows = $this->print_csv_rows(); + + if( $rows ) { + return true; + } else { + return false; + } + } + + /** + * Output the CSV columns + * + * @since 2.4 + * @uses EDD_Export::get_csv_cols() + * @return string + */ + public function print_csv_cols() { + + $col_data = ''; + $cols = $this->get_csv_cols(); + $i = 1; + foreach( $cols as $col_id => $column ) { + $col_data .= '"' . addslashes( $column ) . '"'; + $col_data .= $i == count( $cols ) ? '' : ','; + $i++; + } + $col_data .= "\r\n"; + + $this->stash_step_data( $col_data ); + + return $col_data; + + } + + /** + * Print the CSV rows for the current step + * + * @since 2.4 + * @return string|false + */ + public function print_csv_rows() { + + $row_data = ''; + $data = $this->get_data(); + $cols = $this->get_csv_cols(); + + if( $data ) { + + // Output each row + foreach ( $data as $row ) { + $i = 1; + foreach ( $row as $col_id => $column ) { + // Make sure the column is valid + if ( array_key_exists( $col_id, $cols ) ) { + $row_data .= '"' . addslashes( preg_replace( "/\"/","'", $column ) ) . '"'; + $row_data .= $i == count( $cols ) ? '' : ','; + $i++; + } + } + $row_data .= "\r\n"; + } + + $this->stash_step_data( $row_data ); + + return $row_data; + } + + return false; + } + + /** + * Return the calculated completion percentage + * + * @since 2.4 + * @return int + */ + public function get_percentage_complete() { + return 100; + } + + /** + * Retrieve the file data is written to + * + * @since 2.4 + * @return string + */ + protected function get_file() { + + $file = ''; + + if ( @file_exists( $this->file ) ) { + + if ( ! is_writeable( $this->file ) ) { + $this->is_writable = false; + } + + $file = @file_get_contents( $this->file ); + + } else { + + @file_put_contents( $this->file, '' ); + @chmod( $this->file, 0664 ); + + } + + return $file; + } + + /** + * Append data to export file + * + * @since 2.4 + * @param $data string The data to add to the file + * @return void + */ + protected function stash_step_data( $data = '' ) { + + $file = $this->get_file(); + $file .= $data; + @file_put_contents( $this->file, $file ); + + // If we have no rows after this step, mark it as an empty export + $file_rows = file( $this->file, FILE_SKIP_EMPTY_LINES); + $default_cols = $this->get_csv_cols(); + $default_cols = empty( $default_cols ) ? 0 : 1; + + $this->is_empty = count( $file_rows ) == $default_cols ? true : false; + + } + + /** + * Perform the export + * + * @since 2.4 + * @return void + */ + public function export() { + + // Set headers + $this->headers(); + + $file = $this->get_file(); + + @unlink( $this->file ); + + echo $file; + + die(); + } + + /* + * Set the properties specific to the export + * + * @since 2.4.2 + * @param array $request The Form Data passed into the batch processing + */ + public function set_properties( $request ) {} + + /** + * Allow for prefetching of data for the remainder of the exporter + * + * @since 2.5 + * @return void + */ + public function pre_fetch() {} + + /** + * Gets the date query. + * + * @since 3.0 + * @return array + */ + protected function get_date_query() { + $date_query = array( + 'after' => '', + 'before' => '', + 'inclusive' => true, + ); + + if ( $this->start ) { + $start_date_string = EDD()->utils->get_date_string( $this->start ); + $date_query['after'] = edd_get_utc_date_string( $start_date_string ); + } + + if ( $this->end ) { + $end_date_string = EDD()->utils->get_date_string( $this->end, 23, 59, 59 ); + $date_query['before'] = edd_get_utc_date_string( $end_date_string ); + } + + return array( $date_query ); + } +} diff --git a/includes/admin/reporting/export/export-actions.php b/includes/admin/reporting/export/export-actions.php new file mode 100644 index 00000000000..26149fac86f --- /dev/null +++ b/includes/admin/reporting/export/export-actions.php @@ -0,0 +1,309 @@ + 403 ) ); + } + + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export.php'; + do_action( 'edd_batch_export_class_include', $_REQUEST['class'] ); + + if ( class_exists( $_REQUEST['class'] ) && 'EDD_Batch_Export' === get_parent_class( $_REQUEST['class'] ) ) { + $export = new $_REQUEST['class'](); + $export->export(); + } +} +add_action( 'edd_download_batch_export', 'edd_process_batch_export_download' ); + +/** + * Export all the customers to a CSV file. + * + * Note: The WordPress Database API is being used directly for performance + * reasons (workaround of calling all posts and fetch data respectively) + * + * @since 1.4.4 + * @return void + */ +function edd_export_all_customers() { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/class-export-customers.php'; + + $customer_export = new EDD_Customers_Export(); + + $customer_export->export(); +} +add_action( 'edd_email_export', 'edd_export_all_customers' ); + +/** + * Exports all the downloads to a CSV file using the EDD_Export class. + * + * @since 1.4.4 + * @return void + */ +function edd_export_all_downloads_history() { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/class-export-download-history.php'; + + $file_download_export = new EDD_Download_History_Export(); + + $file_download_export->export(); +} +add_action( 'edd_downloads_history_export', 'edd_export_all_downloads_history' ); + +/** + * Add a hook allowing extensions to register a hook on the batch export process + * + * @since 2.4.2 + * @return void + */ +function edd_register_batch_exporters() { + if ( is_admin() ) { + do_action( 'edd_register_batch_exporter' ); + } +} +add_action( 'plugins_loaded', 'edd_register_batch_exporters', 99 ); + +/** + * Register the payments batch exporter + * @since 2.4.2 + */ +function edd_register_payments_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_payments_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_payments_batch_export', 10 ); + +/** + * Loads the payments batch processor if needed. + * + * @since 2.4.2 + * + * @param string $class The class being requested to run for the batch export + */ +function edd_include_payments_batch_processor( $class ) { + if ( 'EDD_Batch_Payments_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-payments.php'; + } +} + +/** + * Register the customers batch exporter. + * + * @since 2.4.2 + */ +function edd_register_customers_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_customers_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_customers_batch_export', 10 ); + +/** + * Loads the customers batch processor if needed. + * + * @since 2.4.2 + * + * @param string $class The class being requested to run for the batch export. + */ +function edd_include_customers_batch_processor( $class ) { + if ( 'EDD_Batch_Customers_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-customers.php'; + } +} + +/** + * Register the download products batch exporter + * + * @since 2.5 + */ +function edd_register_downloads_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_downloads_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_downloads_batch_export', 10 ); + +/** + * Loads the file downloads batch process if needed + * + * @since 2.5 + * @param string $class The class being requested to run for the batch export + * @return void + */ +function edd_include_downloads_batch_processor( $class ) { + if ( 'EDD_Batch_Downloads_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-downloads.php'; + } +} + +/** + * Register the file downloads batch exporter + * @since 2.4.2 + */ +function edd_register_file_downloads_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_file_downloads_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_file_downloads_batch_export', 10 ); + +/** + * Loads the file downloads batch process if needed + * + * @since 2.4.2 + * @param string $class The class being requested to run for the batch export + * @return void + */ +function edd_include_file_downloads_batch_processor( $class ) { + if ( 'EDD_Batch_File_Downloads_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-file-downloads.php'; + } +} + +/** + * Register the sales batch exporter. + * + * @since 2.7 + */ +function edd_register_sales_export_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_sales_export_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_sales_export_batch_export', 10 ); + +/** + * Loads the sales export batch process if needed + * + * @since 2.7 + * @param string $class The class being requested to run for the batch export + * @return void + */ +function edd_include_sales_export_batch_processor( $class ) { + if ( 'EDD_Batch_Sales_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-sales.php'; + } +} + +/** + * Register the earnings report batch exporter + * + * @since 2.7 + */ +function edd_register_earnings_report_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_earnings_report_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_earnings_report_batch_export', 10 ); + +/** + * Loads the earnings report batch process if needed + * + * @since 2.7 + * @param string $class The class being requested to run for the batch export + * @return void + */ +function edd_include_earnings_report_batch_processor( $class ) { + if ( 'EDD_Batch_Earnings_Report_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-earnings-report.php'; + } +} + +/** + * Register the API requests batch exporter + * + * @since 2.7 + */ +function edd_register_api_requests_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_api_requests_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_api_requests_batch_export', 10 ); + +/** + * Loads the API requests batch process if needed + * + * @since 2.7 + * @param string $class The class being requested to run for the batch export + * @return void + */ +function edd_include_api_requests_batch_processor( $class ) { + if ( 'EDD_Batch_API_Requests_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-api-requests.php'; + } +} + +/** + * Register the taxed orders report batch exporter. + * + * @since 3.0 + */ +function edd_register_taxed_orders_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_taxed_orders_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_taxed_orders_batch_export', 10 ); + +/** + * Loads the taxed orders report batch process if needed. + * + * @since 3.0 + * + * @param string $class The class being requested to run for the batch export + */ +function edd_include_taxed_orders_batch_processor( $class ) { + if ( 'EDD_Batch_Taxed_Orders_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-taxed-orders.php'; + } +} + +/** + * Register the taxed orders report batch exporter. + * + * @since 3.0 + */ +function edd_register_taxed_customers_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_taxed_customers_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_taxed_customers_batch_export', 10 ); + +/** + * Loads the taxed customers report batch process if needed. + * + * @since 3.0 + * + * @param string $class The class being requested to run for the batch export + */ +function edd_include_taxed_customers_batch_processor( $class ) { + if ( 'EDD_Batch_Taxed_Customers_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-taxed-customers.php'; + } +} + +/** + * Register the sales and earnings report batch exporter. + * + * @since 3.0 + */ +function edd_register_sales_and_earnings_batch_export() { + add_action( 'edd_batch_export_class_include', 'edd_include_sales_and_earnings_batch_processor', 10, 1 ); +} +add_action( 'edd_register_batch_exporter', 'edd_register_sales_and_earnings_batch_export', 10 ); + +/** + * Loads the sales and earnings batch process if needed. + * + * @since 3.0 + * + * @param string $class The class being requested to run for the batch export + */ +function edd_include_sales_and_earnings_batch_processor( $class ) { + if ( 'EDD_Batch_Sales_And_Earnings_Export' === $class ) { + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/class-batch-export-sales-and-earnings.php'; + } +} diff --git a/includes/admin/reporting/export/export-functions.php b/includes/admin/reporting/export/export-functions.php new file mode 100755 index 00000000000..61f9489b934 --- /dev/null +++ b/includes/admin/reporting/export/export-functions.php @@ -0,0 +1,112 @@ +can_export() ) { + die( '-1' ); + } + + if ( ! $export->is_writable ) { + echo wp_json_encode( array( + 'error' => true, + 'message' => __( 'Export location or file not writable', 'easy-digital-downloads' ), + )); + + exit; + } + + $export->set_properties( $_REQUEST ); + + // Added in 2.5 to allow a bulk processor to pre-fetch some data to speed up the remaining steps and cache data. + $export->pre_fetch(); + + $ret = $export->process_step(); + + $percentage = $export->get_percentage_complete(); + + if ( $ret ) { + $step++; + + echo wp_json_encode( array( + 'step' => absint( $step ), + 'percentage' => esc_attr( $percentage ), + ) ); + + exit; + } elseif ( true === $export->is_empty ) { + echo wp_json_encode( array( + 'error' => true, + 'message' => __( 'No data found for export parameters', 'easy-digital-downloads' ), + ) ); + + exit; + } elseif ( true === $export->done && true === $export->is_void ) { + $message = ! empty( $export->message ) + ? $export->message + : __( 'Batch Processing Complete', 'easy-digital-downloads' ); + + echo wp_json_encode( array( + 'success' => true, + 'message' => $message, + 'data' => $export->result_data, + ) ); + + exit; + } else { + $args = array_merge( $_REQUEST, array( + 'step' => absint( $step ), + 'class' => urlencode( $class ), + 'nonce' => wp_create_nonce( 'edd-batch-export' ), + 'edd_action' => 'download_batch_export', + ) ); + + $download_url = add_query_arg( $args, admin_url() ); + + echo wp_json_encode( array( + 'step' => 'done', + 'url' => esc_url_raw( $download_url ), + ) ); + + exit; + } +} +add_action( 'wp_ajax_edd_do_ajax_export', 'edd_do_ajax_export' ); diff --git a/includes/admin/reporting/graphing.php b/includes/admin/reporting/graphing.php index 46b418a7169..9fe273822e4 100755 --- a/includes/admin/reporting/graphing.php +++ b/includes/admin/reporting/graphing.php @@ -2,248 +2,774 @@ /** * Graphing Functions * - * @package Easy Digital Downloads - * @subpackage Graphing Functions - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Admin/Reports + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 + * @since 1.0 */ +use EDD\Reports; + +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** - * Show Download Sales Graph + * Show report graphs * - * @access public - * @since 1.0 - * @return void + * @since 1.3 + * @return void */ +function edd_reports_graph() { + // Retrieve the queried dates + $dates = Reports\get_dates_filter( 'objects' ); + + $day_by_day = Reports\get_dates_filter_day_by_day(); + + $earnings_totals = 0.00; // Total earnings for time period shown + $sales_totals = 0; // Total sales for time period shown + + $include_taxes = empty( $_GET['exclude_taxes'] ) ? true : false; + + if ( $dates['range'] == 'today' || $dates['range'] == 'yesterday' ) { + // Hour by hour + $hour = 0; + $month = $dates['start']->month; + + $i = 0; + $j = 0; + + $start = $dates['start']->format( 'Y-m-d' ); + $end = $dates['end']->format( 'Y-m-d' ); + + $sales = EDD()->payment_stats->get_sales_by_range( $dates['range'], true, $start, $end ); + $earnings = EDD()->payment_stats->get_earnings_by_range( $dates['range'], true, $start, $end, $include_taxes ); + + while ( $hour <= 23 ) { + $date = mktime( $hour, 0, 0, $month, $dates['start']->day, $dates['start']->year ) * 1000; + + if ( isset( $earnings[ $i ] ) && $earnings[ $i ]['h'] == $hour ) { + $earnings_data[] = array( $date, $earnings[ $i ]['total'] ); + $earnings_totals += $earnings[ $i ]['total']; + $i++; + } else { + $earnings_data[] = array( $date, 0 ); + } + + if ( isset( $sales[ $j ] ) && $sales[ $j ]['h'] == $hour ) { + $sales_data[] = array( $date, $sales[ $j ]['count'] ); + $sales_totals += $sales[ $j ]['count']; + $j++; + } else { + $sales_data[] = array( $date, 0 ); + } + + $hour++; + } + } elseif ( $dates['range'] == 'this_week' || $dates['range'] == 'last_week' ) { + $report_dates = array(); + $i = 0; + while ( $i <= 6 ) { + if ( ( $dates['start']->day + $i ) <= $dates['end']->day ) { + $report_dates[ $i ] = array( + 'day' => (string) $dates['start']->day + $i, + 'month' => $dates['start']->month, + 'year' => $dates['start']->year, + ); + } else { + $report_dates[ $i ] = array( + 'day' => (string) $i, + 'month' => $dates['end']->month, + 'year' => $dates['end']->year, + ); + } + + $i++; + } + + $start_date = $report_dates[0]; + $end_date = end( $report_dates ); + + $sales = EDD()->payment_stats->get_sales_by_range( $dates['range'], true, $start_date['year'] . '-' . $start_date['month'] . '-' . $start_date['day'], $end_date['year'] . '-' . $end_date['month'] . '-' . $end_date['day'] ); + $earnings = EDD()->payment_stats->get_earnings_by_range( $dates['range'], true, $start_date['year'] . '-' . $start_date['month'] . '-' . $start_date['day'], $end_date['year'] . '-' . $end_date['month'] . '-' . $end_date['day'], $include_taxes ); + + $i = 0; + $j = 0; + foreach ( $report_dates as $report_date ) { + $date = mktime( 0, 0, 0, $report_date['month'], $report_date['day'], $report_date['year'] ) * 1000; + + if ( array_key_exists( $i, $sales ) && $report_date['day'] == $sales[ $i ]['d'] && $report_date['month'] == $sales[ $i ]['m'] && $report_date['year'] == $sales[ $i ]['y'] ) { + $sales_data[] = array( $date, $sales[ $i ]['count'] ); + $sales_totals += $sales[ $i ]['count']; + $i++; + } else { + $sales_data[] = array( $date, 0 ); + } + + if ( array_key_exists( $j, $earnings ) && $report_date['day'] == $earnings[ $j ]['d'] && $report_date['month'] == $earnings[ $j ]['m'] && $report_date['year'] == $earnings[ $j ]['y'] ) { + $earnings_data[] = array( $date, $earnings[ $j ]['total'] ); + $earnings_totals += $earnings[ $j ]['total']; + $j++; + } else { + $earnings_data[] = array( $date, 0 ); + } + } + + } else { + $date_start = $dates['start']->format( 'Y-m-d' ); + $date_end = $dates['end']->format( 'Y-m-d' ); + + $sales = EDD()->payment_stats->get_sales_by_range( $dates['range'], $day_by_day, $date_start, $date_end ); + $earnings = EDD()->payment_stats->get_earnings_by_range( $dates['range'], $day_by_day, $date_start, $date_end, $include_taxes ); + + $temp_data = array( + 'sales' => array(), + 'earnings' => array(), + ); + + foreach ( $sales as $sale ) { + if ( $day_by_day ) { + $temp_data['sales'][ $sale['y'] ][ $sale['m'] ][ $sale['d'] ] = $sale['count']; + } else { + $temp_data['sales'][ $sale['y'] ][ $sale['m'] ] = $sale['count']; + } + $sales_totals += $sale['count']; + } + + foreach ( $earnings as $earning ) { + if ( $day_by_day ) { + $temp_data['earnings'][ $earning['y'] ][ $earning['m'] ][ $earning['d'] ] = $earning['total']; + } else { + $temp_data['earnings'][ $earning['y'] ][ $earning['m'] ] = $earning['total']; + } + $earnings_totals += $earning['total']; + } + + while ( $day_by_day && ( strtotime( $date_start ) <= strtotime( $date_end ) ) ) { + $d = $dates['start']->day; + $m = $dates['start']->month; + $y = $dates['start']->year; + + if ( ! isset( $temp_data['sales'][ $y ][ $m ][ $d ] ) ) { + $temp_data['sales'][ $y ][ $m ][ $d ] = 0; + } + + if ( ! isset( $temp_data['earnings'][ $y ][ $m ][ $d ] ) ) { + $temp_data['earnings'][ $y ][ $m ][ $d ] = 0; + } + + $date_start = $dates['start']->addDays( 1 )->format( 'Y-m-d' ); + } + + while ( ! $day_by_day && ( strtotime( $date_start ) <= strtotime( $date_end ) ) ) { + $m = $dates['start']->month; + $y = $dates['start']->year; + + if ( ! isset( $temp_data['sales'][ $y ][ $m ] ) ) { + $temp_data['sales'][ $y ][ $m ] = 0; + } + + if ( ! isset( $temp_data['earnings'][ $y ][ $m ] ) ) { + $temp_data['earnings'][ $y ][ $m ] = 0; + } + + $date_start = $dates['start']->addMonths( 1 )->format( 'Y-m' ); + } + + $sales_data = array(); + $earnings_data = array(); + + // When using 3 months or smaller as the custom range, show each day individually on the graph + if ( $day_by_day ) { + foreach ( $temp_data['sales'] as $year => $months ) { + foreach ( $months as $month => $days ) { + foreach ( $days as $day => $count ) { + $date = mktime( 0, 0, 0, $month, $day, $year ) * 1000; + $sales_data[] = array( $date, $count ); + } + } + } + + foreach ( $temp_data['earnings'] as $year => $months ) { + foreach ( $months as $month => $days ) { + foreach ( $days as $day => $total ) { + $date = mktime( 0, 0, 0, $month, $day, $year ) * 1000; + $earnings_data[] = array( $date, $total ); + } + } + } + + // Sort dates in ascending order + foreach ( $sales_data as $key => $value ) { + $timestamps[ $key ] = $value[0]; + } + if ( ! empty( $timestamps ) ) { + array_multisort( $timestamps, SORT_ASC, $sales_data ); + } + + foreach ( $earnings_data as $key => $value ) { + $earnings_timestamps[ $key ] = $value[0]; + } + if ( ! empty( $earnings_timestamps ) ) { + array_multisort( $earnings_timestamps, SORT_ASC, $earnings_data ); + } + + // When showing more than 3 months of results, group them by month, by the first (except for the last month, group on the last day of the month selected) + } else { -function edd_show_download_sales_graph($bgcolor = 'white') { - $downloads = get_posts(array('post_type' => 'download', 'posts_per_page' => -1)); - if($downloads) { - ob_start(); ?> - -
    - $months ) { + $month_keys = array_keys( $months ); + $last_month = end( $month_keys ); + + if ( $day_by_day ) { + foreach ( $months as $month => $days ) { + $day_keys = array_keys( $days ); + $last_day = end( $day_keys ); + + $month_keys = array_keys( $months ); + + $consolidated_date = $month === end( $month_keys ) ? cal_days_in_month( CAL_GREGORIAN, $month, $year ) : 1; + + $sales = array_sum( $days ); + $date = mktime( 0, 0, 0, $month, $consolidated_date, $year ) * 1000; + $sales_data[] = array( $date, $sales ); + } + } else { + foreach ( $months as $month => $count ) { + $month_keys = array_keys( $months ); + $consolidated_date = $month === end( $month_keys ) ? cal_days_in_month( CAL_GREGORIAN, $month, $year ) : 1; + + $date = mktime( 0, 0, 0, $month, $consolidated_date, $year ) * 1000; + $sales_data[] = array( $date, $count ); + } + } + } + + // Sort dates in ascending order + foreach ( $sales_data as $key => $value ) { + $timestamps[ $key ] = $value[0]; + } + if ( ! empty( $timestamps ) ) { + array_multisort( $timestamps, SORT_ASC, $sales_data ); + } + + foreach ( $temp_data['earnings'] as $year => $months ) { + $month_keys = array_keys( $months ); + $last_month = end( $month_keys ); + + if ( $day_by_day ) { + foreach ( $months as $month => $days ) { + $day_keys = array_keys( $days ); + $last_day = end( $day_keys ); + + $month_keys = array_keys( $months ); + + $consolidated_date = $month === end( $month_keys ) + ? cal_days_in_month( CAL_GREGORIAN, $month, $year ) + : 1; + + $earnings = array_sum( $days ); + $date = mktime( 0, 0, 0, $month, $consolidated_date, $year ) * 1000; + $earnings_data[] = array( $date, $earnings ); + } + } else { + foreach ( $months as $month => $count ) { + $month_keys = array_keys( $months ); + $consolidated_date = $month === end( $month_keys ) + ? cal_days_in_month( CAL_GREGORIAN, $month, $year ) + : 1; + + $date = mktime( 0, 0, 0, $month, $consolidated_date, $year ) * 1000; + $earnings_data[] = array( $date, $count ); + } + } + } + + // Sort dates in ascending order + foreach ( $earnings_data as $key => $value ) { + $earnings_timestamps[ $key ] = $value[0]; + } + if ( ! empty( $earnings_timestamps ) ) { + array_multisort( $earnings_timestamps, SORT_ASC, $earnings_data ); + } + } } -} + $data = array( + __( 'Earnings', 'easy-digital-downloads' ) => $earnings_data, + __( 'Sales', 'easy-digital-downloads' ) => $sales_data + ); + + // start our own output buffer + ob_start(); + + do_action( 'edd_reports_graph_before' ); ?> + +
    +
    +
    +

    + +
    + set( 'x_mode', 'time' ); + $graph->set( 'multiple_y_axes', true ); + $graph->display(); + + if( ! empty( $dates['range'] ) && 'this_month' == $dates['range'] ) { + $estimated = edd_estimated_monthly_stats( $include_taxes ); + } + ?> + +

    + + + + + † + +

    +

    + + +

    + + + + + † + +

    +

    + + + + +

    + + † + +

    +
    +
    +
    +
    + + 'download', 'posts_per_page' => -1)); - if($downloads) { - ob_start(); ?> - -
    - = 3 || ( $dates['year_end'] > $dates['year'] && ( $dates['m_start'] - $dates['m_end'] ) != 10 ) ) { + $day_by_day = false; + } else { + $day_by_day = true; + } + break; + default: + $day_by_day = true; + break; } -} + $earnings_totals = (float) 0.00; // Total earnings for time period shown + $sales_totals = 0; // Total sales for time period shown -/** - * Show Monthly Earnings Graph - * - * @access public - * @since 1.0 - * @return void -*/ + $include_taxes = empty( $_GET['exclude_taxes'] ) ? true : false; + $earnings_data = array(); + $sales_data = array(); + + if ( $dates['range'] == 'today' || $dates['range'] == 'yesterday' ) { + // Hour by hour + $month = $dates['m_start']; + $hour = 0; + $minute = 0; + $second = 0; + while ( $hour <= 23 ) : + if ( $hour == 23 ) { + $minute = $second = 59; + } + + $date = mktime( $hour, $minute, $second, $month, $dates['day'], $dates['year'] ); + $date_end = mktime( $hour + 1, $minute, $second, $month, $dates['day'], $dates['year'] ); + + $sales = EDD()->payment_stats->get_sales( $download_id, $date, $date_end ); + $sales_totals += $sales; + + $earnings = EDD()->payment_stats->get_earnings( $download_id, $date, $date_end, $include_taxes ); + $earnings_totals += $earnings; + + $sales_data[] = array( $date * 1000, $sales ); + $earnings_data[] = array( $date * 1000, $earnings ); -function edd_show_monthly_eanings_graph($bgcolor = 'white') { - ob_start(); ?> - -
    +

    +

    +

    +

    +

    + + - -
    - 'edd-reports', + ) ); + + $filter_args = array(); + + // Parse and validate filters. + foreach ( $filters as $filter => $attributes ) { + + switch ( $filter ) { + + case 'dates': + if ( ! empty( $form_data['range'] ) ) { + $range = sanitize_key( $form_data['range'] ); + $relative_range = sanitize_key( $form_data['relative_range'] ); + } else { + $range = Reports\get_dates_filter_range(); + $relative_range = Reports\get_relative_dates_filter_range(); + } + + if ( 'other' === $range ) { + try { + /* + * This validates the input dates before saving. If they're not valid, an exception + * will be thrown. + */ + EDD()->utils->date( $form_data['filter_from'] ); + EDD()->utils->date( $form_data['filter_to'] ); + } catch ( \Exception $e ) { + wp_die( + esc_html__( 'Invalid date format. Please enter a date in the format: YYYY-mm-dd.', 'easy-digital-downloads' ), + esc_html__( 'Invalid Date Error', 'easy-digital-downloads' ), + array( 'response' => 400, 'back_link' => true ) + ); + } + + $filter_args = array_merge( + array( + 'filter_from' => ! empty( $form_data['filter_from'] ) + ? sanitize_text_field( $form_data['filter_from'] ) + : '', + 'filter_to' => ! empty( $form_data['filter_to'] ) + ? sanitize_text_field( $form_data['filter_to'] ) + : '', + 'range' => 'other', + 'relative_range' => 'previous_period', + ), + $filter_args + ); + + } else { + + $dates = Reports\parse_dates_for_range( $range ); + + $filter_args = array_merge( + array( + 'filter_from' => $dates['start']->format( 'date-mysql' ), + 'filter_to' => $dates['end']->format( 'date-mysql' ), + 'range' => $range, + 'relative_range' => $relative_range, + ), + $filter_args + ); + + } + break; + + case 'taxes': + $filter_args = array_merge( + array( + 'exclude_taxes' => isset( $form_data['exclude_taxes'] ), + ), + $filter_args + ); + + break; + + default: + $filter_arg = isset( $form_data[ $filter ] ) + ? $form_data[ $filter ] + : array(); + + if ( ! empty( $filter_arg ) ) { + $filter_args[ $filter ] = $filter_arg; + } + + break; + } + } + + // Redirect back to report. + $redirect = add_query_arg( $filter_args, $redirect ); + + edd_redirect( $redirect ); +} +add_action( 'edd_filter_reports', 'edd_parse_report_dates' ); + +/** + * EDD Reports Refresh Button + * @since 2.7 + * @description: Outputs a "Refresh Reports" button for graphs + */ +function edd_reports_refresh_button() { + + $url = wp_nonce_url( add_query_arg( array( + 'edd_action' => 'refresh_reports_transients', + 'edd-message' => 'refreshed-reports' + ) ), 'edd-refresh-reports' ); + + echo '' . esc_html__( 'Refresh Reports', 'easy-digital-downloads' ) . ''; } +add_action( 'edd_reports_graph_after', 'edd_reports_refresh_button' ); /** - * Show Sales Per Month Graph + * EDD trigger the refresh of reports transients * - * @access public - * @since 1.0 - * @return void -*/ + * @since 2.7 + * + * @param array $data Parameters sent from Settings page + * @return void + */ +function edd_run_refresh_reports_transients( $data ) { + if ( ! wp_verify_nonce( $data['_wpnonce'], 'edd-refresh-reports' ) ) { + return; + } -function edd_show_monthly_sales_graph($bgcolor = 'white') { - ob_start(); ?> - -
    - AddPage('L', 'A4'); - - $pdf->SetTitle( __('Sales and earnings reports for the current year for all products', 'edd') ); - $pdf->SetAuthor( __('Easy Digital Downloads', 'edd') ); - $pdf->SetCreator( __('Easy Digital Downloads', 'edd') ); - - $pdf->Image(EDD_PLUGIN_URL . 'includes/images/edd-logo.png', 205, 10); - - $pdf->SetMargins( 8, 8, 8 ); - $pdf->SetX( 8 ); - - $pdf->SetFont( 'Helvetica', '', 16 ); - $pdf->SetTextColor( 50, 50, 50 ); - $pdf->Cell( 0, 3, __('Sales and earnings reports for the current year for all products', 'edd'), 0, 2, 'L', false ); - - $pdf->SetFont( 'Helvetica', '', 13 ); - $pdf->Ln(); - $pdf->SetTextColor( 150, 150, 150 ); - $pdf->Cell( 0, 6, __('Date Range: ', 'edd') . $daterange, 0, 2, 'L', false ); - $pdf->Ln(); - $pdf->SetTextColor( 50, 50, 50 ); - $pdf->SetFont( 'Helvetica', '', 14 ); - $pdf->Cell( 0, 10, __('Table View', 'edd'), 0, 2, 'L', false ); - $pdf->SetFont( 'Helvetica', '', 12 ); - - $pdf->SetFillColor( 238, 238, 238 ); - $pdf->Cell( 70, 6, __('Product Name', 'edd'), 1, 0, 'L', true ); - $pdf->Cell( 30, 6, __('Price', 'edd'), 1, 0, 'L', true ); - $pdf->Cell( 50, 6, __('Categories', 'edd'), 1, 0, 'L', true ); - $pdf->Cell( 50, 6, __('Tags', 'edd'), 1, 0, 'L', true ); - $pdf->Cell( 45, 6, __('Number of Sales', 'edd'), 1, 0, 'L', true ); - $pdf->Cell( 35, 6, __('Earnings to Date', 'edd'), 1, 1, 'L', true ); - - $year = date('Y'); - $downloads = get_posts( array( 'post_type' => 'download', 'year' => $year, 'posts_per_page' => -1 ) ); - if ( $downloads ) : - $pdf->SetWidths( array( 70, 30, 50, 50, 45, 35 ) ); - - foreach ( $downloads as $download ) : - - $pdf->SetFillColor( 255, 255, 255 ); - - $title = utf8_decode( get_the_title( $download->ID ) ); - - if ( edd_has_variable_prices( $download->ID ) ) { - $prices = edd_get_variable_prices( $download->ID ); - $total = count( $prices ) - 1; - if ( $prices[0]['amount'] < $prices[$total]['amount'] ) { - $min = $prices[0]['amount']; - $max = $prices[$total]['amount']; - } else { - $min = $prices[$total]['amount']; - $max = $prices[0]['amount']; - } - $price = html_entity_decode( sprintf( '%s - %s', edd_currency_filter( $min ), edd_currency_filter( $max ) ) ); - } else { - $price = html_entity_decode( edd_currency_filter( edd_get_download_price( $download->ID ) ) ); - } - - $categories = get_the_term_list( $download->ID, 'download_category', '', ', ', '' ); - $categories = $categories ? strip_tags( $categories ) : ''; - - $tags = get_the_term_list( $download->ID, 'download_tag', '', ', ', '' ); - $tags = $tags ? strip_tags( $tags ) : ''; - - $sales = edd_get_download_sales_stats( $download->ID ); - $link = get_permalink( $download->ID ); - $earnings = html_entity_decode ( edd_currency_filter( edd_get_download_earnings_stats( $download->ID ) ) ); - - $pdf->Row( array( $title, $price, $categories, $tags, $sales, $earnings ) ); - endforeach; - else: - $pdf->SetWidths( array( 280 ) ); - $title = __('No Downloads found.', 'edd'); - $pdf->Row( array($title) ); - endif; - - $pdf->Ln(); - $pdf->SetTextColor( 50, 50, 50 ); - $pdf->SetFont( 'Helvetica', '', 14 ); - $pdf->Cell( 0, 10, __('Graph View', 'edd'), 0, 2, 'L', false ); - $pdf->SetFont( 'Helvetica', '', 12 ); - - $image = html_entity_decode( urldecode( edd_draw_chart_image() ) ); - $image = str_replace( ' ', '%20', $image ); - - $pdf->SetX( 25 ); - $pdf->Image( $image .'&file=.png' ); - $pdf->Ln( 7 ); - $pdf->Output( 'edd-report-' . date('Y-m-d') . '.pdf', 'D' ); - - } -} -add_action('edd_generate_pdf', 'edd_generate_pdf'); - - -/** - * Draws Chart for PDF Report - * - * Draws the sales and earnings chart for the PDF report. - * - * @access public - * @since 1.1.4.0 - * @author Sunny Ratilal - * @return string -*/ - -function edd_draw_chart_image() { - include_once(EDD_PLUGIN_DIR . '/includes/libraries/googlechartlib/GoogleChart.php'); - include_once(EDD_PLUGIN_DIR . '/includes/libraries/googlechartlib/markers/GoogleChartShapeMarker.php'); - include_once(EDD_PLUGIN_DIR . '/includes/libraries/googlechartlib/markers/GoogleChartTextMarker.php'); - - $chart = new GoogleChart( 'lc', 900, 330 ); - - $i = 1; - $earnings = ""; - $sales = ""; - while( $i <= 12 ) : - $earnings .= edd_get_earnings_by_date( null, $i, date('Y') ) . ","; - $sales .= edd_get_sales_by_date( $i, date('Y') ) . ","; - $i++; - endwhile; - - $earnings_array = explode( ",", $earnings ); - $sales_array = explode( ",", $sales ); - - $i = 0; - while( $i <= 11 ) { - if( empty( $sales_array[$i] ) ) - $sales_array[$i] = 0; - $i++; - } - - $min_earnings = 0; - $max_earnings = max( $earnings_array ); - $earnings_scale = round( $max_earnings, -1 ); - - $data = new GoogleChartData( array( - $earnings_array[0], - $earnings_array[1], - $earnings_array[2], - $earnings_array[3], - $earnings_array[4], - $earnings_array[5], - $earnings_array[6], - $earnings_array[7], - $earnings_array[8], - $earnings_array[9], - $earnings_array[10], - $earnings_array[11] - ) ); - - $data->setLegend( __('Earnings', 'edd') ); - $data->setColor( '1b58a3' ); - $chart->addData( $data ); - - $shape_marker = new GoogleChartShapeMarker( GoogleChartShapeMarker::CIRCLE ); - $shape_marker->setColor( '000000' ); - $shape_marker->setSize( 7 ); - $shape_marker->setBorder( 2 ); - $shape_marker->setData( $data ); - $chart->addMarker( $shape_marker ); - - $value_marker = new GoogleChartTextMarker( GoogleChartTextMarker::VALUE ); - $value_marker->setColor( '000000' ); - $value_marker->setData( $data ); - $chart->addMarker( $value_marker ); - - $data = new GoogleChartData( array( $sales_array[0],$sales_array[1], $sales_array[2],$sales_array[3], $sales_array[4],$sales_array[5],$sales_array[6],$sales_array[7],$sales_array[8],$sales_array[9],$sales_array[10],$sales_array[11] ) ); - $data->setLegend( __('Sales', 'edd') ); - $data->setColor( 'ff6c1c' ); - $chart->addData( $data ); - - $chart->setTitle( __('Sales and Earnings by Month for all Products', 'edd'), '336699', 18 ); - - $chart->setScale( 0, $max_earnings ); - - $y_axis = new GoogleChartAxis( 'y' ); - $y_axis->setDrawTickMarks( true )->setLabels( array( - 0, - $max_earnings - ) ); - $chart->addAxis( $y_axis ); - - $x_axis = new GoogleChartAxis( 'x' ); - $x_axis->setTickMarks( 5 ); - $x_axis->setLabels( array( - __('Jan', 'edd'), - __('Feb', 'edd'), - __('Mar', 'edd'), - __('Apr', 'edd'), - __('May', 'edd'), - __('June', 'edd'), - __('July', 'edd'), - __('Aug', 'edd'), - __('Sept', 'edd'), - __('Oct', 'edd'), - __('Nov', 'edd'), - __('Dec', 'edd') - ) ); - $chart->addAxis( $x_axis ); - - $shape_marker = new GoogleChartShapeMarker( GoogleChartShapeMarker::CIRCLE ); - $shape_marker->setSize( 6 ); - $shape_marker->setBorder( 2 ); - $shape_marker->setData( $data ); - $chart->addMarker( $shape_marker ); - - $value_marker = new GoogleChartTextMarker( GoogleChartTextMarker::VALUE ); - $value_marker->setData( $data ); - $chart->addMarker( $value_marker ); - - return $chart->getUrl(); -} \ No newline at end of file diff --git a/includes/admin/reporting/reports-callbacks.php b/includes/admin/reporting/reports-callbacks.php new file mode 100755 index 00000000000..af571be690b --- /dev/null +++ b/includes/admin/reporting/reports-callbacks.php @@ -0,0 +1,259 @@ +prepare( " AND currency = %s ", strtoupper( $currency ) ); + } + + // Revenue calculations should include gross statuses to negate refunds properly. + $statuses = edd_get_gross_order_statuses(); + $statuses = apply_filters( 'edd_payment_stats_post_statuses', $statuses ); + $statuses = "'" . implode( "', '", $statuses ) . "'"; + + $earnings_results = $wpdb->get_results( + $wpdb->prepare( + "SELECT SUM({$column}) AS earnings, {$sql_clauses['select']} + FROM {$wpdb->edd_orders} edd_o + WHERE date_created >= %s AND date_created <= %s + AND status IN( {$statuses} ) + AND type IN ( 'sale', 'refund' ) + {$sql_clauses['where']} + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + $dates['start']->copy()->format( 'mysql' ), + $dates['end']->copy()->format( 'mysql' ) + ) + ); + + // Sales counts should count by 'net' statuses, which excludes refunds. + $statuses = edd_get_net_order_statuses(); + $statuses = apply_filters( 'edd_payment_stats_post_statuses', $statuses ); + $statuses = "'" . implode( "', '", $statuses ) . "'"; + + $sales_results = $wpdb->get_results( + $wpdb->prepare( + "SELECT COUNT(*) AS sales, {$sql_clauses['select']} + FROM {$wpdb->edd_orders} edd_o + WHERE date_created >= %s AND date_created <= %s + AND status IN( {$statuses} ) + AND type = 'sale' + {$sql_clauses['where']} + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + $dates['start']->copy()->format( 'mysql' ), + $dates['end']->copy()->format( 'mysql' ) + ) + ); + + $sales = array(); + $earnings = array(); + + /** + * Initialise all arrays with timestamps and set values to 0. + * + * We use the Chart based dates for this loop, so the graph shows in the proper date ranges while the actual DB queries are all UTC based. + */ + while ( strtotime( $chart_dates['start']->copy()->format( 'mysql' ) ) <= strtotime( $chart_dates['end']->copy()->format( 'mysql' ) ) ) { + $timestamp = $chart_dates['start']->copy()->format( 'U' ); + $date_on_chart = $chart_dates['start']; + + $sales[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $sales[ $timestamp ][1] = 0; + + $earnings[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $earnings[ $timestamp ][1] = 0.00; + + // Loop through each date there were sales/earnings, which we queried from the database. + foreach ( $earnings_results as $earnings_result ) { + $date_of_db_value = EDD()->utils->date( $earnings_result->date ); + + // Add any sales/earnings that happened during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $earnings[ $timestamp ][1] += $earnings_result->earnings; + } + // Add any sales/earnings that happened during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $earnings[ $timestamp ][1] += $earnings_result->earnings; + } + // Add any sales/earnings that happened during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $earnings[ $timestamp ][1] += $earnings_result->earnings; + } + } + } + + // Loop through each date there were sales/earnings, which we queried from the database. + foreach ( $sales_results as $sales_result ) { + $date_of_db_value = EDD()->utils->date( $sales_result->date ); + + // Add any sales/earnings that happened during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $sales[ $timestamp ][1] += $sales_result->sales; + } + // Add any sales/earnings that happened during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $sales[ $timestamp ][1] += $sales_result->sales; + } + // Add any sales/earnings that happened during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $sales[ $timestamp ][1] += $sales_result->sales; + } + } + } + + // Move the chart along to the next hour/day/month to get ready for the next loop. + if ( 'hour' === $period ) { + $chart_dates['start']->addHour( 1 ); + } elseif ( 'day' === $period ) { + $chart_dates['start']->addDays( 1 ); + } else { + $chart_dates['start']->addMonth( 1 ); + } + } + + return array( + 'sales' => array_values( $sales ), + 'earnings' => array_values( $earnings ), + ); + +} + +/** + * The callback function which fetches the data for the edd_overview_refunds_chart reports endpoint. + * + * @since 3.0 + */ +function edd_overview_refunds_chart() { + global $wpdb; + + $dates = Reports\get_dates_filter( 'objects' ); + $chart_dates = Reports\parse_dates_for_range( null, 'now', false ); + $column = Reports\get_taxes_excluded_filter() ? 'total - tax' : 'total'; + $currency = Reports\get_filter_value( 'currencies' ); + $period = Reports\get_graph_period(); + $sql_clauses = Reports\get_sql_clauses( $period ); + + if ( empty( $currency ) || 'convert' === $currency ) { + $column = sprintf( '(%s) / rate', $column ); + } else { + $sql_clauses['where'] = $wpdb->prepare( " AND currency = %s ", strtoupper( $currency ) ); + } + + $results = $wpdb->get_results( + $wpdb->prepare( + "SELECT COUNT(*) AS number, SUM({$column}) AS amount, {$sql_clauses['select']} + FROM {$wpdb->edd_orders} edd_o + WHERE status IN (%s, %s) AND date_created >= %s AND date_created <= %s AND type = 'refund' + {$sql_clauses['where']} + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + esc_sql( 'complete' ), + esc_sql( 'partially_refunded' ), + $dates['start']->copy()->format( 'mysql' ), + $dates['end']->copy()->format( 'mysql' ) + ) + ); + + $number = array(); + $amount = array(); + + // Initialise all arrays with timestamps and set values to 0. + while ( strtotime( $chart_dates['start']->copy()->format( 'mysql' ) ) <= strtotime( $chart_dates['end']->copy()->format( 'mysql' ) ) ) { + $timestamp = $chart_dates['start']->copy()->format( 'U' ); + $date_on_chart = $chart_dates['start']; + + $number[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $number[ $timestamp ][1] = 0; + + $amount[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $amount[ $timestamp ][1] = 0.00; + + // Loop through each date there were refunds, which we queried from the database. + foreach ( $results as $result ) { + $date_of_db_value = EDD()->utils->date( $result->date ); + + // Add any refunds that happened during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $number[ $timestamp ][1] += $result->number; + $amount[ $timestamp ][1] += abs( $result->amount ); + } + // Add any refunds that happened during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $number[ $timestamp ][1] += $result->number; + $amount[ $timestamp ][1] += abs( $result->amount ); + } + // Add any refunds that happened during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $number[ $timestamp ][1] += $result->number; + $amount[ $timestamp ][1] += abs( $result->amount ); + } + } + } + + // Move the chart along to the next hour/day/month to get ready for the next loop. + if ( 'hour' === $period ) { + $chart_dates['start']->addHour( 1 ); + } elseif ( 'day' === $period ) { + $chart_dates['start']->addDays( 1 ); + } else { + $chart_dates['start']->addMonth( 1 ); + } + } + + return array( + 'number' => array_values( $number ), + 'amount' => array_values( $amount ), + ); +} diff --git a/includes/admin/reporting/reports.php b/includes/admin/reporting/reports.php index f9cc681b8be..f5df001787c 100755 --- a/includes/admin/reporting/reports.php +++ b/includes/admin/reporting/reports.php @@ -1,47 +1,2814 @@ - -
    -

    - -

    - - -

    -

    -
    - 'edd-reports' + ) ); + + // Redirect if user cannot view reports + if ( ! current_user_can( 'view_shop_reports' ) ) { + edd_redirect( $redirect_url ); + } + + // Start the Reports API. + new Reports\Init(); + + add_filter( 'edd_admin_is_single_view', '__return_false' ); + + // Get the section and report + $section = Reports\get_current_report(); + $report = Reports\get_report( $section ); + + // Redirect if report is invalid + if ( empty( $report ) || is_wp_error( $report ) ) { + edd_redirect( $redirect_url ); + } + + // Stash the report in the EDD global for future reference + EDD()->report = $report; +} +add_action( 'load-download_page_edd-reports', 'edd_admin_load_report' ); + +/** + * Contains backwards compat code to shim tabs & views to EDD_Sections() + * + * @since 3.0 + */ +function edd_reports_sections() { + + // Instantiate the Sections class and sections array + $sections = new EDD\Admin\Reports_Sections(); + $c_sections = array(); + + // Setup sections variables + $sections->use_js = false; + $sections->current_section = Reports\get_current_report(); + $sections->item = null; + + // Find persisted filters to append to the base URL. + $persisted_filters = Reports\get_persisted_filters(); + $persisted_filter_args = array(); + + foreach ( $persisted_filters as $filter ) { + if ( isset( $_GET[ $filter ] ) ) { + $persisted_filter_args[ $filter ] = sanitize_text_field( $_GET[ $filter ] ); + } + } + + // Build the section base URL. + $sections->base_url = edd_get_admin_url( + array_merge( + array( + 'page' => 'edd-reports', + ), + $persisted_filter_args + ) + ); + + // Get all registered tabs & views + $tabs = Reports\get_reports(); + + // Loop through tabs & setup sections + if ( ! empty( $tabs ) ) { + foreach ( $tabs as $id => $tab ) { + + // Add to sections array + $c_sections[] = array( + 'id' => $id, + 'label' => $tab['label'], + 'icon' => $tab['icon'], + 'callback' => array( 'edd_output_report_callback', array( $id ) ) + ); + } + } + + // Set the customer sections + $sections->set_sections( $c_sections ); + + // Display the sections + $sections->display(); +} + +/** + * Output a report via a callback + * + * @since 3.0 + * + * @param string $report_id + */ +function edd_output_report_callback( $report_id = '' ) { + + // Maybe use the already loaded report + $report = EDD()->report + ? EDD()->report + : EDD\Reports\get_report( $report_id ); + + /** + * Fires at the top of the content area of a Reports tab. + * + * @since 1.0 + * @since 3.0 Added the `$report` parameter. + * + * @param \EDD\Reports\Data\Report|\WP_Error $report The current report object, + * or WP_Error if invalid. + */ + do_action( 'edd_reports_page_top', $report ); + + if ( ! is_wp_error( $report ) ) { + printf( '

    %s

    ', esc_html( $report->label ) ); + $report->display(); + } else { + Reports\default_display_report( $report ); + } + + /** + * Fires at the bottom of the content area of a Reports tab. + * + * @since 1.0 + * @since 3.0 Added the `$report` parameter. + * + * @param \EDD\Reports\Data\Report|\WP_Error $report The current report object, + * or WP_Error if invalid. + */ + do_action( 'edd_reports_page_bottom', $report ); +} + +/** + * Reports Page + * + * Renders the reports page contents. + * + * @since 1.0 + * @return void + */ +function edd_reports_page() { + // Enqueue scripts. + wp_enqueue_script( 'edd-admin-reports' ); + ?> + +
    +

    + + report ); ?> + +
    + +
    +
    + + add_report( 'overview', array( + 'label' => __( 'Overview', 'easy-digital-downloads' ), + 'icon' => 'dashboard', + 'priority' => 5, + 'endpoints' => array( + 'tiles' => array( + 'overview_sales', + 'overview_earnings', + 'overview_average_order_value', + 'overview_new_customers', + 'overview_refunded_amount', + ), + 'charts' => array( + 'overview_sales_earnings_chart', + ), + ), + 'filters' => array( + 'dates', + 'taxes', + 'currencies', + ) + ) ); + + $reports->register_endpoint( 'overview_sales', array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $stats = new EDD\Stats( + array( + 'range' => $dates['range'], + 'relative' => true, + 'currency' => $currency, + 'revenue_type' => 'net', + ) + ); + return $stats->get_order_count(); + }, + 'display_args' => array( + 'comparison_label' => $label . ' — ' . __( 'Net', 'easy-digital-downloads' ), + ), + ), + ), + ) ); + + $reports->register_endpoint( 'overview_earnings', array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $stats = new EDD\Stats( + array( + 'range' => $dates['range'], + 'function' => 'SUM', + 'exclude_taxes' => $exclude_taxes, + 'currency' => $currency, + 'relative' => true, + 'output' => 'formatted', + 'revenue_type' => 'net', + ) + ); + return $stats->get_order_earnings(); + }, + 'display_args' => array( + 'comparison_label' => $label . ' — ' . __( 'Net', 'easy-digital-downloads' ), + ), + ), + ), + ) ); + + $reports->register_endpoint( 'overview_average_order_value', array( + 'label' => __( 'Average Order Value', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $stats = new EDD\Stats( + array( + 'function' => 'AVG', + 'output' => 'formatted', + 'relative' => true, + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'currency' => $currency, + ) + ); + return $stats->get_order_earnings(); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'overview_new_customers', array( + 'label' => __( 'New Customers', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates ) { + $stats = new EDD\Stats(); + return $stats->get_customer_count( array( + 'range' => $dates['range'], + 'relative' => true, + 'purchase_count' => true, + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'overview_refunded_amount', array( + 'label' => __( 'Total Refund Amount', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $stats = new EDD\Stats(); + return $stats->get_order_refund_amount( array( + 'range' => $dates['range'], + 'function' => 'SUM', + 'exclude_taxes' => $exclude_taxes, + 'currency' => $currency, + 'relative' => true, + 'output' => 'formatted', + ) ); + + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'overview_sales_earnings_chart', array( + 'label' => __( 'Sales and Earnings', 'easy-digital-downloads' ) . ' — ' . $label . ' — ' . __( 'Net', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => 'edd_overview_sales_earnings_chart', + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'earnings' => array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'fill' => true, + 'borderWidth' => 2, + 'type' => 'currency', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'earnings-y', + ), + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'fill' => true, + 'borderWidth' => 2, + 'borderCapStyle' => 'round', + 'borderJoinStyle' => 'round', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'sales-y', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'id' => 'earnings-y', + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'formattingType' => 'format', + 'beginAtZero' => true, + 'precision' => 0, + ), + 'gridLines' => array( + 'display' => true, + ), + ), + array( + 'id' => 'sales-y', + 'type' => 'linear', + 'position' => 'right', + 'display' => true, + 'ticks' => array( + 'formattingType' => 'integer', + 'beginAtZero' => true, + 'hideNegativeTicks' => true, + 'precision' => 0, + ), + 'gridLines' => array( + 'display' => true, + 'color' => 'rgba(0,0,0,0.03)', + ), + ), + ), + ), + ), + ), + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_overview_report' ); + +/** + * Register downloads report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_downloads_report( $reports ) { + try { + $options = Reports\get_dates_filter_options(); + $dates = Reports\get_filter_value( 'dates' ); + $exclude_taxes = Reports\get_taxes_excluded_filter(); + $currency = ''; + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $dates['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $download_data = Reports\get_filter_value( 'products' ); + $download_data = ! empty( $download_data ) && 'all' !== Reports\get_filter_value( 'products' ) + ? edd_parse_product_dropdown_value( Reports\get_filter_value( 'products' ) ) + : false; + + $endpoint_label = __( 'Sales / Earnings', 'easy-digital-downloads' ); + + // Mock downloads and prices in case they cannot be found later. + $download = edd_get_download(); + $prices = array(); + $download_label = ''; + if ( $download_data ) { + $download = edd_get_download( $download_data['download_id'] ); + $prices = $download->get_prices(); + if ( isset( $download_data['price_id'] ) && is_numeric( $download_data['price_id'] ) ) { + $args = array( 'price_id' => $download_data['price_id'] ); + $price_name = edd_get_price_name( $download->ID, $args ); + if ( $price_name ) { + $download->post_title .= ': ' . $price_name; + } + } + $download_label = esc_html( ' (' . $download->post_title . ')' ); + } + + $tiles = array_filter( array( + 'most_valuable_download', + 'average_download_sales_earnings', + 'download_sales_earnings', + ), function( $endpoint ) use ( $download_data ) { + switch( $endpoint ) { + case 'download_sales_earnings': + return false !== $download_data; + break; + default: + return false === $download_data; + } + } ); + + $charts = array_filter( array( + 'download_sales_by_variations', + 'download_earnings_by_variations', + 'download_sales_earnings_chart' + ), function( $endpoint ) use ( $download_data, $download ) { + switch( $endpoint ) { + case 'download_sales_by_variations': + case 'download_earnings_by_variations': + return ( + false !== $download_data && + false === $download_data['price_id'] && + true === $download->has_variable_prices() + ); + + break; + + default: + return false !== $download_data; + } + } ); + + $tables = array_filter( array( + 'top_selling_downloads', + 'earnings_by_taxonomy', + ), function( $endpoint ) use ( $download_data ) { + return false === $download_data; + } ); + + $reports->add_report( 'downloads', array( + 'label' => edd_get_label_plural(), + 'priority' => 10, + 'icon' => 'download', + 'endpoints' => array( + 'tiles' => $tiles, + 'charts' => $charts, + 'tables' => $tables, + ), + 'filters' => array( 'dates', 'products', 'taxes' ), + ) ); + + $reports->register_endpoint( 'most_valuable_download', array( + 'label' => sprintf( __( 'Most Valuable %s', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $stats = new EDD\Stats(); + $d = $stats->get_most_valuable_order_items( array( + 'range' => $dates['range'], + 'currency' => $currency, + 'function' => 'SUM' + ) ); + + if ( ! empty( $d ) && isset( $d[0] ) ) { + $d = $d[0]; + + if ( $d->object instanceof EDD_Download ) { + $title = $d->object->post_title; + + if ( $d->object->has_variable_prices() ) { + $prices = array_values( wp_filter_object_list( $d->object->get_prices(), array( 'index' => absint( $d->price_id ) ) ) ); + + $title .= ( is_array( $prices ) && isset( $prices[0] ) ) + ? ': ' . $prices[0]['name'] + : ''; + } + + return esc_html( $title ); + } + } + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_download_sales_earnings', array( + 'label' => __( 'Average Sales / Earnings', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $stats = new EDD\Stats( array( + 'function' => 'AVG', + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'currency' => $currency, + 'output' => 'formatted', + ) ); + + return $stats->get_order_item_count() . ' / ' . $stats->get_order_item_earnings(); + }, + 'display_args' => array( + 'comparison_label' => $label . ' — ' . __( 'Net ', 'easy-digital-downloads' ), + ), + ), + ), + ) ); + + $reports->register_endpoint( 'download_sales_earnings', array( + 'label' => $endpoint_label, + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $download_data, $dates, $currency ) { + $price_id = isset( $download_data['price_id'] ) && is_numeric( $download_data['price_id'] ) ? absint( $download_data['price_id'] ) : null; + $stats = new EDD\Stats( array( + 'product_id' => absint( $download_data['download_id'] ), + 'price_id' => $price_id, + 'currency' => $currency, + 'range' => $dates['range'], + 'output' => 'formatted', + ) ); + + $earnings = $stats->get_order_item_earnings( array( + 'function' => 'SUM' + ) ); + $sales = $stats->get_order_item_count( array( + 'function' => 'COUNT' + ) ); + + return esc_html( $sales . ' / ' . $earnings ); + }, + 'display_args' => array( + 'comparison_label' => $label . $download_label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'earnings_by_taxonomy', array( + 'label' => __( 'Earnings By Taxonomy', 'easy-digital-downloads' ) . ' — ' . $label, + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Downloads\\Earnings_By_Taxonomy_List_Table', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php', + ), + ), + ), + ) ); + + $reports->register_endpoint( 'top_selling_downloads', array( + 'label' => sprintf( __( 'Top Selling %s', 'easy-digital-downloads' ), edd_get_label_plural() ) . ' — ' . $label, + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Downloads\\Top_Selling_Downloads_List_Table', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/downloads/class-top-selling-downloads-list-table.php', + ), + ), + ), + ) ); + + $reports->register_endpoint( 'download_sales_by_variations', array( + 'label' => __( 'Sales by Variation', 'easy-digital-downloads' ) . $download_label, + 'views' => array( + 'chart' => array( + 'data_callback' => function() use ( $download_data, $download, $dates, $prices, $currency ) { + $stats = new EDD\Stats(); + $sales = $stats->get_order_item_count( array( + 'product_id' => absint( $download_data['download_id'] ), + 'range' => $dates['range'], + 'grouped' => true, + 'currency' => $currency + ) ); + + // Set all values to 0. + foreach ( $prices as $key => $price ) { + $prices[ $key ]['sales'] = 0; + } + + // Parse results from the database. + foreach ( $sales as $data ) { + $prices[ $data->price_id ]['sales'] = absint( $data->total ); + } + + $sales = array_values( wp_list_pluck( $prices, 'sales' ) ); + + return array( + 'sales' => $sales, + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 0, + 'datasets' => array( + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(133,175,91)', + 'rgb(9,149,199)', + 'rgb(8,189,231)', + 'rgb(137,163,87)', + 'rgb(27,98,122)', + ), + ), + ), + 'labels' => array_values( wp_list_pluck( $prices, 'name' ) ) + ), + ), + ) + ) ); + + $reports->register_endpoint( 'download_earnings_by_variations', array( + 'label' => __( 'Earnings by Variation', 'easy-digital-downloads' ) . $download_label, + 'views' => array( + 'chart' => array( + 'data_callback' => function() use ( $download_data, $prices, $dates, $currency ) { + $stats = new EDD\Stats(); + $earnings = $stats->get_order_item_earnings( array( + 'product_id' => absint( $download_data['download_id'] ), + 'range' => $dates['range'], + 'grouped' => true, + 'currency' => $currency + ) ); + + // Set all values to 0. + foreach ( $prices as $key => $price ) { + $prices[ $key ]['earnings'] = floatval( 0 ); + } + + // Parse results from the database. + foreach ( $earnings as $data ) { + $prices[ $data->price_id ]['earnings'] = floatval( $data->total ); + } + + $earnings = array_values( wp_list_pluck( $prices, 'earnings' ) ); + + return array( + 'earnings' => $earnings, + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 0, + 'datasets' => array( + 'earnings' => array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ), + 'type' => 'currency', + 'backgroundColor' => array( + 'rgb(133,175,91)', + 'rgb(9,149,199)', + 'rgb(8,189,231)', + 'rgb(137,163,87)', + 'rgb(27,98,122)', + ), + ), + ), + 'labels' => array_values( wp_list_pluck( $prices, 'name' ) ) + ), + ), + ) + ) ); + + $reports->register_endpoint( 'download_sales_earnings_chart', array( + 'label' => __( 'Sales and Earnings', 'easy-digital-downloads' ) . esc_html( $download_label ), + 'views' => array( + 'chart' => array( + 'data_callback' => function () use ( $download_data, $currency ) { + global $wpdb; + + $dates = Reports\get_dates_filter( 'objects' ); + $chart_dates = Reports\parse_dates_for_range( null, 'now', false ); + $period = Reports\get_graph_period(); + $sql_clauses = Reports\get_sql_clauses( $period, 'edd_oi.date_created' ); + + $union_clauses = array( + 'select' => 'date', + 'groupby' => 'date', + 'orderby' => 'date', + ); + + $price_id = isset( $download_data['price_id'] ) && is_numeric( $download_data['price_id'] ) + ? sprintf( 'AND price_id = %d', absint( $download_data['price_id'] ) ) + : ''; + + $earnings_statuses = edd_get_gross_order_statuses(); + $earnings_status_string = implode( ', ', array_fill( 0, count( $earnings_statuses ), '%s' ) ); + + $order_item_earnings = $wpdb->prepare( + "SELECT SUM(edd_oi.total / edd_oi.rate) AS earnings, {$sql_clauses['select']} + FROM {$wpdb->edd_order_items} edd_oi + INNER JOIN {$wpdb->edd_orders} edd_o ON edd_oi.order_id = edd_o.id + WHERE edd_oi.product_id = %d {$price_id} AND edd_oi.date_created >= %s AND edd_oi.date_created <= %s AND edd_o.status IN ({$earnings_status_string}) + GROUP BY {$sql_clauses['groupby']}", + $download_data['download_id'], + $dates['start']->copy()->format( 'mysql' ), + $dates['end']->copy()->format( 'mysql' ), + ...$earnings_statuses + ); + + /** + * The adjustments query needs a different order status check than the order items. This is due to the fact that + * adjustments refunded would end up being double counted, and therefore create an inaccurate revenue report. + */ + $adjustments_statuses = edd_get_net_order_statuses(); + $adjustments_status_string = implode( ', ', array_fill( 0, count( $adjustments_statuses ), '%s' ) ); + + $order_adjustments = $wpdb->prepare( + "SELECT SUM(edd_oa.total / edd_oa.rate) AS earnings, {$sql_clauses['select']} + FROM {$wpdb->edd_order_adjustments} edd_oa + INNER JOIN {$wpdb->edd_order_items} edd_oi ON + edd_oi.id = edd_oa.object_id + AND edd_oi.product_id = %d + {$price_id} + AND edd_oi.date_created >= %s AND edd_oi.date_created <= %s + INNER JOIN {$wpdb->edd_orders} edd_o ON edd_oi.order_id = edd_o.id AND edd_o.type = 'sale' AND edd_o.status IN ({$adjustments_status_string}) + WHERE edd_oa.object_type = 'order_item' + AND edd_oa.type != 'discount' + GROUP BY {$sql_clauses['groupby']}", + $download_data['download_id'], + $dates['start']->copy()->format( 'mysql' ), + $dates['end']->copy()->format( 'mysql' ), + ...$adjustments_statuses + ); + + $earnings_sql = "SELECT SUM(earnings) as earnings, {$union_clauses['select']} FROM ({$order_item_earnings} UNION {$order_adjustments})a GROUP BY {$union_clauses['groupby']} ORDER BY {$union_clauses['orderby']}"; + $earnings_results = $wpdb->get_results( $earnings_sql ); + + $statuses = edd_get_net_order_statuses(); + $status_string = implode( ', ', array_fill( 0, count( $statuses ), '%s' ) ); + + $join = $wpdb->prepare( + "INNER JOIN {$wpdb->edd_orders} edd_o ON (edd_oi.order_id = edd_o.id) AND edd_o.status IN({$status_string}) AND edd_o.type = 'sale' ", + ...$statuses + ); + + $sales_sql = $wpdb->prepare( + "SELECT COUNT(edd_oi.total) AS sales, {$sql_clauses['select']} + FROM {$wpdb->edd_order_items} edd_oi + {$join} + WHERE edd_oi.product_id = %d %1s AND edd_oi.date_created >= %s AND edd_oi.date_created <= %s AND edd_oi.status IN ({$status_string}) + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + $download_data['download_id'], + $price_id, + $dates['start']->copy()->format( 'mysql' ), + $dates['end']->copy()->format( 'mysql' ), + ...$statuses + ); + + $sales_results = $wpdb->get_results( $sales_sql ); + + $sales = array(); + $earnings = array(); + + // Initialise all arrays with timestamps and set values to 0. + while ( strtotime( $chart_dates['start']->copy()->format( 'mysql' ) ) <= strtotime( $chart_dates['end']->copy()->format( 'mysql' ) ) ) { + $timestamp = $chart_dates['start']->copy()->format( 'U' ); + $date_on_chart = $chart_dates['start']; + + $sales[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $sales[ $timestamp ][1] = 0; + + $earnings[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $earnings[ $timestamp ][1] = 0.00; + + // Loop through each date there were sales/earnings, which we queried from the database. + foreach ( $earnings_results as $earnings_result ) { + $date_of_db_value = EDD()->utils->date( $earnings_result->date ); + + // Add any sales/earnings that happened during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $earnings[ $timestamp ][1] += $earnings_result->earnings; + } + // Add any sales/earnings that happened during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $earnings[ $timestamp ][1] += $earnings_result->earnings; + } + // Add any sales/earnings that happened during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $earnings[ $timestamp ][1] += $earnings_result->earnings; + } + } + } + + // Loop through each date there were sales/earnings, which we queried from the database. + foreach ( $sales_results as $sales_result ) { + $date_of_db_value = EDD()->utils->date( $sales_result->date ); + + // Add any sales/earnings that happened during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $sales[ $timestamp ][1] += $sales_result->sales; + } + // Add any sales/earnings that happened during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $sales[ $timestamp ][1] += $sales_result->sales; + } + // Add any sales/earnings that happened during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $sales[ $timestamp ][1] += $sales_result->sales; + } + } + } + + // Move the chart along to the next hour/day/month to get ready for the next loop. + if ( 'hour' === $period ) { + $chart_dates['start']->addHour( 1 ); + } elseif ( 'day' === $period ) { + $chart_dates['start']->addDays( 1 ); + } else { + $chart_dates['start']->addMonth( 1 ); + } + } + + return array( + 'earnings' => array_values( $earnings ), + 'sales' => array_values( $sales ), + ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'earnings' => array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'fill' => true, + 'borderWidth' => 2, + 'type' => 'currency', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'earnings-y', + ), + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'fill' => true, + 'borderWidth' => 2, + 'borderCapStyle' => 'round', + 'borderJoinStyle' => 'round', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'sales-y', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'id' => 'earnings-y', + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'format', + 'suggestedMin' => 0, + 'beginAtZero' => true, + ), + 'gridLines' => array( + 'display' => true, + ), + ), + array( + 'id' => 'sales-y', + 'type' => 'linear', + 'position' => 'right', + 'display' => true, + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'integer', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'hideNegativeTicks' => true, + ), + 'gridLines' => array( + 'display' => true, + 'color' => 'rgba(0,0,0,0.03)', + ), + ), + ), + ), + ), + ), + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_downloads_report' ); + + +/** + * Register refunds report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_refunds_report( $reports ) { + try { + + // Variables to hold date filter values. + $options = Reports\get_dates_filter_options(); + $dates = Reports\get_filter_value( 'dates' ); + $exclude_taxes = Reports\get_taxes_excluded_filter(); + $currency = Reports\get_filter_value( 'currencies' ); + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $dates['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $reports->add_report( 'refunds', array( + 'label' => __( 'Refunds', 'easy-digital-downloads' ), + 'icon' => 'image-rotate', + 'priority' => 15, + 'endpoints' => array( + 'tiles' => array( + 'refund_count', + 'fully_refunded_order_count', + 'fully_refunded_order_item_count', + 'refund_amount', + 'average_refund_amount', + 'average_time_to_refund', + 'refund_rate', + ), + 'charts' => array( + 'refunds_chart', + ), + ), + 'filters' => array( + 'dates', + 'taxes', + 'currencies' + ) + ) ); + + $reports->register_endpoint( 'refund_count', array( + 'label' => __( 'Number of Refunds', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $stats = new EDD\Stats(); + $number = $stats->get_order_refund_count( array( + 'range' => $dates['range'], + 'currency' => $currency + ) ); + return esc_html( $number ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'fully_refunded_order_count', array( + 'label' => __( 'Number of Fully Refunded Orders', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $stats = new EDD\Stats(); + $number = $stats->get_order_refund_count( array( + 'range' => $dates['range'], + 'status' => array( 'complete' ), + 'currency' => $currency + ) ); + return esc_html( $number ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'fully_refunded_order_item_count', array( + 'label' => __( 'Number of Fully Refunded Items', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $stats = new EDD\Stats(); + $number = $stats->get_order_item_refund_count( array( + 'range' => $dates['range'], + 'status' => array( 'refunded' ), + 'currency' => $currency + ) ); + return esc_html( $number ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'refund_amount', array( + 'label' => __( 'Total Refund Amount', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $stats = new EDD\Stats(); + return $stats->get_order_refund_amount( array( + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'output' => 'formatted', + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_refund_amount', array( + 'label' => __( 'Average Refund Amount', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $stats = new EDD\Stats(); + return $stats->get_order_refund_amount( array( + 'function' => 'AVG', + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'output' => 'formatted', + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_time_to_refund', array( + 'label' => __( 'Average Time to Refund', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $stats = new EDD\Stats(); + return $stats->get_average_refund_time( array( + 'range' => $dates['range'], + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'refund_rate', array( + 'label' => __( 'Refund Rate', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $stats = new EDD\Stats(); + return $stats->get_refund_rate( array( + 'range' => $dates['range'], + 'output' => 'formatted', + 'status' => edd_get_gross_order_statuses(), + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'refunds_chart', array( + 'label' => __( 'Refunds', 'easy-digital-downloads' ) . ' — ' . $label, + 'views' => array( + 'chart' => array( + 'data_callback' => 'edd_overview_refunds_chart', + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'amount' => array( + 'label' => __( 'Amount', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'fill' => true, + 'borderWidth' => 2, + 'type' => 'currency', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'amount-y', + ), + 'number' => array( + 'label' => __( 'Number', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'fill' => true, + 'borderWidth' => 2, + 'borderCapStyle' => 'round', + 'borderJoinStyle' => 'round', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'number-y', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'id' => 'amount-y', + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'format', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'precision' => 0, + ), + 'gridLines' => array( + 'display' => true, + ), + ), + array( + 'id' => 'number-y', + 'type' => 'linear', + 'position' => 'right', + 'display' => true, + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'integer', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'precision' => 0, + ), + 'gridLines' => array( + 'display' => true, + 'color' => 'rgba(0,0,0,0.03)', + ), + ), + ), + ), + ), + ), + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_refunds_report' ); + +/** + * Register payment gateways report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_payment_gateways_report( $reports ) { + try { + + // Variables to hold date filter values. + $options = Reports\get_dates_filter_options(); + $dates = Reports\get_filter_value( 'dates' ); + $exclude_taxes = Reports\get_taxes_excluded_filter(); + $currency = Reports\get_filter_value( 'currencies' ); + $gateway = Reports\get_filter_value( 'gateways' ); + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $dates['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $tiles = array( + 'sales_per_gateway', + 'earnings_per_gateway', + 'refunds_per_gateway', + 'average_value_per_gateway', + ); + + $tables = array_filter( array( + 'gateway_stats', + ), function( $endpoint ) use ( $gateway ) { + return empty( $gateway ) || 'all' === $gateway; + } ); + + $charts = array_filter( array( + 'gateway_sales_breakdown', + 'gateway_earnings_breakdown', + 'gateway_sales_earnings_chart', + ), function( $endpoint ) use ( $gateway ) { + switch( $endpoint ) { + case 'gateway_sales_earnings_chart': + return ! empty( $gateway ) && 'all' !== $gateway; + break; + default: + return ( empty( $gateway ) || 'all' === $gateway ); + } + } ); + + $reports->add_report( 'gateways', array( + 'label' => __( 'Payment Gateways', 'easy-digital-downloads' ), + 'icon' => 'image-filter', + 'priority' => 20, + 'endpoints' => array( + 'tiles' => $tiles, + 'tables' => $tables, + 'charts' => $charts, + ), + 'filters' => array( 'dates', 'gateways', 'taxes', 'currencies' ), + ) ); + + $reports->register_endpoint( 'sales_per_gateway', array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + + return $stats->get_gateway_sales( array( + 'range' => $dates['range'], + 'gateway' => $gateway, + 'currency' => $currency, + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'earnings_per_gateway', array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + + return $stats->get_gateway_earnings( array( + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'gateway' => $gateway, + 'output' => 'formatted', + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'refunds_per_gateway', array( + 'label' => __( 'Refunds', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + + return $stats->get_gateway_earnings( array( + 'range' => $dates['range'], + 'gateway' => $gateway, + 'output' => 'formatted', + 'type' => 'refund', + 'status' => array( 'complete' ), + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_value_per_gateway', array( + 'label' => __( 'Average Order Value', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + $gateway = 'all' !== Reports\get_filter_value( 'gateways' ) + ? Reports\get_filter_value( 'gateways' ) + : ''; + + $stats = new EDD\Stats(); + + if ( empty( $gateway ) ) { + return $stats->get_order_earnings( array( + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'function' => 'AVG', + 'output' => 'formatted', + 'currency' => $currency + ) ); + } else { + return $stats->get_gateway_earnings( array( + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'gateway' => $gateway, + 'function' => 'AVG', + 'output' => 'formatted', + 'currency' => $currency + ) ); + } + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'gateway_stats', array( + 'label' => __( 'Gateway Stats', 'easy-digital-downloads' ) . ' — ' . $options[ $dates['range'] ], + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Payment_Gateways\\Gateway_Stats', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/payment-gateways/class-gateway-stats-list-table.php', + ), + ), + ), + ) ); + + $gateway_list = array_map( 'edd_get_gateway_admin_label', array_keys( edd_get_payment_gateways() ) ); + + $reports->register_endpoint( 'gateway_sales_breakdown', array( + 'label' => __( 'Gateway Sales', 'easy-digital-downloads' ) . ' — ' . $options[ $dates['range'] ], + 'views' => array( + 'chart' => array( + 'data_callback' => function() use ( $dates, $currency ) { + $stats = new EDD\Stats(); + $g = $stats->get_gateway_sales( array( + 'range' => $dates['range'], + 'grouped' => true, + 'currency' => $currency + ) ); + + $gateways = array_flip( array_keys( edd_get_payment_gateways() ) ); + + foreach ( $g as $data ) { + $gateways[ $data->gateway ] = $data->total; + } + + $gateways = array_map( function( $v ) { + return null === $v + ? 0 + : $v; + }, $gateways ); + + return array( + 'sales' => array_values( $gateways ), + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 0, + 'datasets' => array( + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(133,175,91)', + 'rgb(9,149,199)', + 'rgb(8,189,231)', + 'rgb(137,163,87)', + 'rgb(27,98,122)', + ), + ), + ), + 'labels' => $gateway_list, + ), + ), + ) + ) ); + + $reports->register_endpoint( 'gateway_earnings_breakdown', array( + 'label' => __( 'Gateway Earnings', 'easy-digital-downloads' ) . ' — ' . $options[ $dates['range'] ], + 'views' => array( + 'chart' => array( + 'data_callback' => function() use ( $dates, $exclude_taxes, $currency ) { + $stats = new EDD\Stats(); + $g = $stats->get_gateway_earnings( array( + 'grouped' => true, + 'range' => $dates['range'], + 'exclude_taxes' => $exclude_taxes, + 'currency' => $currency + ) ); + + $gateways = array_flip( array_keys( edd_get_payment_gateways() ) ); + + foreach ( $g as $data ) { + $gateways[ $data->gateway ] = $data->earnings; + } + + $gateways = array_values( array_map( function( $v ) { + return null === $v + ? 0.00 + : $v; + }, $gateways ) ); + + return array( + 'earnings' => $gateways, + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 0, + 'datasets' => array( + 'earnings' => array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(133,175,91)', + 'rgb(9,149,199)', + 'rgb(8,189,231)', + 'rgb(137,163,87)', + 'rgb(27,98,122)', + ), + 'type' => 'currency', + ), + ), + 'labels' => $gateway_list, + ), + ), + ) + ) ); + + $reports->register_endpoint( 'gateway_sales_earnings_chart', array( + 'label' => __( 'Sales and Earnings', 'easy-digital-downloads' ) . ' — ' . $label, + 'views' => array( + 'chart' => array( + 'data_callback' => function () use ( $dates, $exclude_taxes, $currency ) { + global $wpdb; + + $dates = Reports\get_dates_filter( 'objects' ); + $chart_dates = Reports\parse_dates_for_range( null, 'now', false ); + $period = Reports\get_graph_period(); + $sql_clauses = Reports\get_sql_clauses( $period, 'date_created' ); + + $gateway = Reports\get_filter_value( 'gateways' ); + $column = $exclude_taxes + ? '( total - tax ) / rate' + : 'total / rate'; + + $currency_sql = ''; + if ( ! empty( $currency ) && array_key_exists( strtoupper( $currency ), edd_get_currencies() ) ) { + $currency_sql = $wpdb->prepare( + " AND currency = %s ", + strtoupper( $currency ) + ); + } + + $results = $wpdb->get_results( $wpdb->prepare( + "SELECT COUNT({$column}) AS sales, SUM({$column}) AS earnings, {$sql_clauses['select']} + FROM {$wpdb->edd_orders} o + WHERE gateway = %s AND status IN ('complete', 'revoked') {$currency_sql} AND date_created >= %s AND date_created <= %s + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + esc_sql( $gateway ), $dates['start']->copy()->format( 'mysql' ), $dates['end']->copy()->format( 'mysql' ) ) ); + + $sales = array(); + $earnings = array(); + + /** + * Initialise all arrays with timestamps and set values to 0. + * + * We use the Chart based dates for this loop, so the graph shows in the proper date ranges while the actual DB queries are all UTC based. + */ + while ( strtotime( $chart_dates['start']->copy()->format( 'mysql' ) ) <= strtotime( $chart_dates['end']->copy()->format( 'mysql' ) ) ) { + $timestamp = $chart_dates['start']->copy()->format( 'U' ); + $date_on_chart = $chart_dates['start']; + + $sales[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $sales[ $timestamp ][1] = 0; + + $earnings[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $earnings[ $timestamp ][1] = 0.00; + + // Loop through each date there were sales/earnings, which we queried from the database. + foreach ( $results as $result ) { + $date_of_db_value = EDD()->utils->date( $result->date ); + + // Add any sales/earnings that happened during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $sales[ $timestamp ][1] += $result->sales; + $earnings[ $timestamp ][1] += $result->earnings; + } + // Add any sales/earnings that happened during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $sales[ $timestamp ][1] += $result->sales; + $earnings[ $timestamp ][1] += $result->earnings; + } + // Add any sales/earnings that happened during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $sales[ $timestamp ][1] += $result->sales; + $earnings[ $timestamp ][1] += $result->earnings; + } + } + } + + // Move the chart along to the next hour/day/month to get ready for the next loop. + if ( 'hour' === $period ) { + $chart_dates['start']->addHour( 1 ); + } elseif ( 'day' === $period ) { + $chart_dates['start']->addDays( 1 ); + } else { + $chart_dates['start']->addMonth( 1 ); + } + } + + $sales = array_values( $sales ); + $earnings = array_values( $earnings ); + + return array( + 'earnings' => $earnings, + 'sales' => $sales, + ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'earnings' => array( + 'label' => __( 'Earnings', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'fill' => true, + 'borderWidth' => 2, + 'type' => 'currency', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'earnings-y', + ), + 'sales' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(252,108,18,0.75)', + 'backgroundColor' => 'rgba(252,108,18,0.05)', + 'fill' => true, + 'borderWidth' => 2, + 'borderCapStyle' => 'round', + 'borderJoinStyle' => 'round', + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'sales-y', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'id' => 'earnings-y', + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'format', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'precision' => 0, + ), + 'gridLines' => array( + 'display' => true, + ), + ), + array( + 'id' => 'sales-y', + 'type' => 'linear', + 'position' => 'right', + 'display' => true, + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'integer', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'hideNegativeTicks' => true, + 'precision' => 0, + ), + 'gridLines' => array( + 'display' => true, + 'color' => 'rgba(0,0,0,0.03)', + ), + ), + ), + ), + ), + ), + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_payment_gateways_report' ); + +/** + * Register taxes report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_taxes_report( $reports ) { + try { + + // Variables to hold date filter values. + $options = Reports\get_dates_filter_options(); + $dates = Reports\get_filter_value( 'dates' ); + $currency = Reports\get_filter_value( 'currencies' ); + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $dates['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $download_data = Reports\get_filter_value( 'products' ); + $download_data = ! empty( $download_data ) && 'all' !== Reports\get_filter_value( 'products' ) + ? edd_parse_product_dropdown_value( Reports\get_filter_value( 'products' ) ) + : false; + + $download_label = ''; + if ( $download_data ) { + $download = edd_get_download( $download_data['download_id'] ); + if ( isset( $download_data['price_id'] ) && is_numeric( $download_data['price_id'] ) ) { + $args = array( 'price_id' => $download_data['price_id'] ); + $price_name = edd_get_price_name( $download->ID, $args ); + if ( $price_name ) { + $download->post_title .= ': ' . $price_name; + } + } + $download_label = esc_html( ' (' . $download->post_title . ')' ); + } + + $country = Reports\get_filter_value( 'countries' ); + $region = Reports\get_filter_value( 'regions' ); + + $tiles = array( + 'total_tax_collected', + 'total_tax_collected_for_location', + ); + + $tables = array_filter( array( + 'tax_collected_by_location', + ), function( $table ) use ( $download_data ) { + return false === $download_data; + } ); + + $reports->add_report( 'taxes', array( + 'label' => __( 'Taxes', 'easy-digital-downloads' ), + 'priority' => 25, + 'icon' => 'editor-paste-text', + 'endpoints' => array( + 'tiles' => $tiles, + 'tables' => $tables, + ), + 'filters' => array( 'dates', 'products', 'countries', 'regions', 'currencies' ), + ) ); + + $reports->register_endpoint( 'total_tax_collected', array( + 'label' => __( 'Total Tax Collected', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $currency ) { + $download = Reports\get_filter_value( 'products' ); + $download = ! empty( $download ) && 'all' !== Reports\get_filter_value( 'products' ) + ? edd_parse_product_dropdown_value( Reports\get_filter_value( 'products' ) ) + : array( 'download_id' => '', 'price_id' => '' ); + + $stats = new EDD\Stats(); + return $stats->get_tax( array( + 'output' => 'formatted', + 'range' => $dates['range'], + 'download_id' => $download['download_id'], + 'price_id' => (string) $download['price_id'], + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label . $download_label, + ), + ), + ), + ) ); + + if ( ! empty( $country ) && 'all' !== $country ) { + $location = ''; + + if ( ! empty( $region ) && 'all' !== $region ) { + $location = edd_get_state_name( $country, $region ) . ', '; + } + + $location .= edd_get_country_name( $country ); + + $reports->register_endpoint( 'total_tax_collected_for_location', array( + 'label' => __( 'Total Tax Collected for ', 'easy-digital-downloads' ) . $location, + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates, $country, $region, $currency ) { + $download = Reports\get_filter_value( 'products' ); + $download = ! empty( $download ) && 'all' !== Reports\get_filter_value( 'products' ) + ? edd_parse_product_dropdown_value( Reports\get_filter_value( 'products' ) ) + : array( 'download_id' => '', 'price_id' => '' ); + + $stats = new EDD\Stats(); + + return $stats->get_tax_by_location( array( + 'output' => 'formatted', + 'range' => $dates['range'], + 'download_id' => $download['download_id'], + 'price_id' => (string) $download['price_id'], + 'country' => $country, + 'region' => $region, + 'currency' => $currency + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label . $download_label, + ), + ), + ), + ) ); + } + + $reports->register_endpoint( 'tax_collected_by_location', array( + 'label' => __( 'Tax Collected by Location', 'easy-digital-downloads' ), + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Taxes\\Tax_Collected_By_Location', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/taxes/class-tax-collected-by-location-list-table.php', + ), + ), + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_taxes_report' ); + +/** + * Register file downloads report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_file_downloads_report( $reports ) { + try { + + // Variables to hold date filter values. + $options = Reports\get_dates_filter_options(); + $filter = Reports\get_filter_value( 'dates' ); + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $filter['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $download_data = Reports\get_filter_value( 'products' ); + $download_data = ! empty( $download_data ) && 'all' !== Reports\get_filter_value( 'products' ) + ? edd_parse_product_dropdown_value( Reports\get_filter_value( 'products' ) ) + : false; + + $download_label = ''; + if ( $download_data ) { + $download = edd_get_download( $download_data['download_id'] ); + if ( isset( $download_data['price_id'] ) && is_numeric( $download_data['price_id'] ) ) { + $args = array( 'price_id' => $download_data['price_id'] ); + $price_name = edd_get_price_name( $download->ID, $args ); + if ( $price_name ) { + $download->post_title .= ': ' . $price_name; + } + } + $download_label = esc_html( ' (' . $download->post_title . ')' ); + } + + $tiles = array_filter( array( + 'number_of_file_downloads', + 'average_file_downloads_per_customer', + 'most_downloaded_product', + 'average_file_downloads_per_order', + ), function( $endpoint ) use ( $download_data ) { + switch( $endpoint ) { + case 'average_file_downloads_per_customer': + case 'most_downloaded_product': + case 'average_file_downloads_per_order': + return false === $download_data; + break; + default: + return true; + } + } ); + + $tables = array_filter( array( + 'top_five_most_downloaded_products', + ), function( $endpoint ) use ( $download_data ) { + return false === $download_data; + } ); + + $charts = array( + 'file_downloads_chart', + ); + + $reports->add_report( 'file_downloads', array( + 'label' => __( 'File Downloads', 'easy-digital-downloads' ), + 'icon' => 'download', + 'priority' => 30, + 'endpoints' => array( + 'tiles' => $tiles, + 'tables' => $tables, + 'charts' => $charts, + ), + 'filters' => array( 'dates', 'products' ), + ) ); + + $reports->register_endpoint( 'number_of_file_downloads', array( + 'label' => __( 'Number of File Downloads', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $download = Reports\get_filter_value( 'products' ); + $download = ! empty( $download ) && 'all' !== Reports\get_filter_value( 'products' ) + ? edd_parse_product_dropdown_value( Reports\get_filter_value( 'products' ) ) + : array( 'download_id' => '', 'price_id' => '' ); + + $stats = new EDD\Stats(); + return $stats->get_file_download_count( array( + 'range' => $filter['range'], + 'download_id' => $download['download_id'], + 'price_id' => (string) $download['price_id'], + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label . $download_label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_file_downloads_per_customer', array( + 'label' => __( 'Average per Customer', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $stats = new EDD\Stats(); + return $stats->get_average_file_download_count( array( + 'range' => $filter['range'], + 'column' => 'customer_id', + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_file_downloads_per_order', array( + 'label' => __( 'Average per Order', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $stats = new EDD\Stats(); + return $stats->get_average_file_download_count( array( + 'range' => $filter['range'], + 'column' => 'order_id', + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'most_downloaded_product', array( + 'label' => __( 'Most Downloaded Product', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $stats = new EDD\Stats(); + $d = $stats->get_most_downloaded_products( array( 'range' => $filter['range'] ) ); + if ( $d ) { + return esc_html( $d[0]->object->post_title ); + } + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'top_five_most_downloaded_products', array( + 'label' => __( 'Top Five Most Downloaded Products', 'easy-digital-downloads' ) . ' – ' . $label, + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\File_Downloads\\Top_Five_Most_Downloaded_List_Table', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php', + ), + ), + ), + ) ); + + $reports->register_endpoint( 'file_downloads_chart', array( + 'label' => __( 'Number of File Downloads', 'easy-digital-downloads' ) . $download_label, + 'views' => array( + 'chart' => array( + 'data_callback' => function () use ( $filter, $download_data ) { + global $wpdb; + + $dates = Reports\get_dates_filter( 'objects' ); + $chart_dates = Reports\parse_dates_for_range( null, 'now', false ); + $period = Reports\get_graph_period(); + $sql_clauses = Reports\get_sql_clauses( $period ); + + $product_id = ''; + $price_id = ''; + + if ( is_array( $download_data ) ) { + $product_id = $wpdb->prepare( 'AND product_id = %d', absint( $download_data['download_id'] ) ); + + $price_id = isset( $download_data['price_id'] ) && is_numeric( $download_data['price_id'] ) + ? $wpdb->prepare( 'AND price_id = %d', absint( $download_data['price_id'] ) ) + : ''; + } + + $results = $wpdb->get_results( $wpdb->prepare( + "SELECT COUNT(id) AS total, {$sql_clauses['select']} + FROM {$wpdb->edd_logs_file_downloads} edd_lfd + WHERE edd_lfd.date_created >= %s AND edd_lfd.date_created <= %s {$product_id} {$price_id} + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + $dates['start']->copy()->format( 'mysql' ), $dates['end']->copy()->format( 'mysql' ) ) ); + + $file_downloads = array(); + + // Initialise all arrays with timestamps and set values to 0. + while ( strtotime( $chart_dates['start']->copy()->format( 'mysql' ) ) <= strtotime( $chart_dates['end']->copy()->format( 'mysql' ) ) ) { + $timestamp = $chart_dates['start']->copy()->format( 'U' ); + $date_on_chart = $chart_dates['start']; + + $file_downloads[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $file_downloads[ $timestamp ][1] = 0; + + foreach ( $results as $result ) { + $date_of_db_value = EDD()->utils->date( $result->date ); + + // Add any file downloads that happened during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $file_downloads[ $timestamp ][1] += absint( $result->total ); + } + // Add any file downloads that happened during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $file_downloads[ $timestamp ][1] += absint( $result->total ); + } + // Add any file downloads that happened during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $file_downloads[ $timestamp ][1] += absint( $result->total ); + } + } + } + + // Move the chart along to the next hour/day/month to get ready for the next loop. + if ( 'hour' === $period ) { + $chart_dates['start']->addHour( 1 ); + } elseif ( 'day' === $period ) { + $chart_dates['start']->addDays( 1 ); + } else { + $chart_dates['start']->addMonth( 1 ); + } + } + + $file_downloads = array_values( $file_downloads ); + + return array( 'file_downloads' => $file_downloads ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'file_downloads' => array( + 'label' => __( 'File Downloads', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'fill' => true, + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + 'yAxisID' => 'file-downloads-y', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'id' => 'file-downloads-y', + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'integer', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'precision' => 0, + ), + ), + ), + ), + ), + ), + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_file_downloads_report' ); + +/** + * Register discounts report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_discounts_report( $reports ) { + try { + + // Variables to hold date filter values. + $options = Reports\get_dates_filter_options(); + $filter = Reports\get_filter_value( 'dates' ); + $currency = Reports\get_filter_value( 'currencies' ); + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $filter['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $discount = Reports\get_filter_value( 'discounts' ); + $discount = ! empty( $discount ) && 'all' !== $discount + ? $discount + : 0; + + $d = edd_get_discount( $discount ); + + $discount_label = false !== $d + ? esc_html( ' (' . $d->name . ')' ) + : ''; + + $tiles = array_filter( array( + 'number_of_discounts_used', + 'ratio_of_discounted_orders', + 'customer_savings', + 'average_discount_amount', + 'most_popular_discount', + 'discount_usage_count', + ), function( $tile ) use ( $discount ) { + switch ( $tile ) { + case 'discount_usage_count': + return 0 !== $discount; + break; + default: + return 0 === $discount; + } + } ); + + $tables = array_filter( array( + 'top_five_discounts', + ), function( $table ) use ( $discount ) { + return 0 === $discount; + } ); + + $charts = array( + 'discount_usage_chart', + ); + + $reports->add_report( 'discounts', array( + 'label' => __( 'Discounts', 'easy-digital-downloads' ), + 'icon' => 'tickets-alt', + 'priority' => 35, + 'endpoints' => array( + 'tiles' => $tiles, + 'tables' => $tables, + 'charts' => $charts, + ), + 'filters' => array( 'dates', 'discounts' ), + ) ); + + $reports->register_endpoint( 'number_of_discounts_used', array( + 'label' => __( 'Number of Discounts Used', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $stats = new EDD\Stats(); + return $stats->get_discount_usage_count( array( + 'range' => $filter['range'], + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'ratio_of_discounted_orders', array( + 'label' => __( 'Discount Ratio', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $stats = new EDD\Stats(); + return $stats->get_ratio_of_discounted_orders( array( + 'range' => $filter['range'], + ) ); + }, + 'display_args' => array( + 'context' => 'secondary', + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'customer_savings', array( + 'label' => __( 'Customer Savings', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter, $d ) { + $stats = new EDD\Stats(); + return $stats->get_discount_savings( array( + 'range' => $filter['range'], + 'output' => 'formatted', + 'discount_code' => isset( $d->code ) + ? $d->code + : '', + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label . $discount_label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_discount_amount', array( + 'label' => __( 'Average Discount Amount', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $stats = new EDD\Stats(); + return $stats->get_average_discount_amount( array( + 'range' => $filter['range'], + 'output' => 'formatted', + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'most_popular_discount', array( + 'label' => __( 'Most Popular Discount', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter ) { + $stats = new EDD\Stats(); + + $r = $stats->get_most_popular_discounts( array( + 'range' => $filter['range'], + 'number' => 1, + ) ); + + if ( ! empty( $r ) ) { + $r = $r[0]; + return esc_html( $r->code . ' (' . $r->count . ')' ); + } + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + if ( $d ) { + $reports->register_endpoint( 'discount_usage_count', array( + 'label' => __( 'Discount Usage Count', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $filter, $d ) { + $stats = new EDD\Stats(); + return $stats->get_discount_usage_count( array( + 'range' => $filter['range'], + 'discount_code' => $d->code, + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label . $discount_label, + ), + ), + ), + ) ); + } + + $reports->register_endpoint( 'top_five_discounts', array( + 'label' => __( 'Top Five Discounts', 'easy-digital-downloads' ) . ' – ' . $label, + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Discounts\\Top_Five_Discounts_List_Table', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/discounts/class-top-five-discounts-list-table.php', + ), + ), + ), + ) ); + + if ( $d ) { + $reports->register_endpoint( 'discount_usage_chart', array( + 'label' => __( 'Discount Usage', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function () use ( $filter, $d ) { + global $wpdb; + + $dates = Reports\get_dates_filter( 'objects' ); + $chart_dates = Reports\parse_dates_for_range( null, 'now', false ); + $period = Reports\get_graph_period(); + $sql_clauses = Reports\get_sql_clauses( $period, 'edd_oa.date_created' ); + + $discount_code = ! empty( $d->code ) + ? $wpdb->prepare( 'AND type = %s AND description = %s', 'discount', esc_sql( $d->code ) ) + : $wpdb->prepare( 'AND type = %s', 'discount' ); + + $results = $wpdb->get_results( $wpdb->prepare( + "SELECT COUNT(id) AS total, {$sql_clauses['select']} + FROM {$wpdb->edd_order_adjustments} edd_oa + WHERE 1=1 {$discount_code} AND edd_oa.date_created >= %s AND edd_oa.date_created <= %s + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + $dates['start']->copy()->format( 'mysql' ), $dates['end']->copy()->format( 'mysql' ) ) ); + + $discount_usage = array(); + + // Initialise all arrays with timestamps and set values to 0. + while ( strtotime( $chart_dates['start']->copy()->format( 'mysql' ) ) <= strtotime( $chart_dates['end']->copy()->format( 'mysql' ) ) ) { + $timestamp = $chart_dates['start']->copy()->format( 'U' ); + $date_on_chart = $chart_dates['start']; + + $discount_usage[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $discount_usage[ $timestamp ][1] = 0; + + // Loop through each date in which there were discount codes used, which we queried from the database. + foreach ( $results as $result ) { + $date_of_db_value = EDD()->utils->date( $result->date ); + + // Add any discount codes that were used during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $discount_usage[ $timestamp ][1] += abs( $result->total ); + } + // Add any discount codes that were used during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $discount_usage[ $timestamp ][1] += abs( $result->total ); + } + // Add any discount codes that were used during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $discount_usage[ $timestamp ][1] += abs( $result->total ); + } + } + } + + // Move the chart along to the next hour/day/month to get ready for the next loop. + if ( 'hour' === $period ) { + $chart_dates['start']->addHour( 1 ); + } elseif ( 'day' === $period ) { + $chart_dates['start']->addDays( 1 ); + } else { + $chart_dates['start']->addMonth( 1 ); + } + } + + $discount_usage = array_values( $discount_usage ); + + return array( 'discount_usage' => $discount_usage ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'discount_usage' => array( + 'label' => __( 'Discount Usage', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'fill' => true, + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'maxTicksLimit' => 2, + 'formattingType' => 'integer', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'precision' => 0, + ), + ), + ), + ), + ), + ), + ), + ) ); + } + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_discounts_report' ); + +/** + * Register customer report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_customer_report( $reports ) { + try { + + // Variables to hold date filter values. + $options = Reports\get_dates_filter_options(); + $dates = Reports\get_filter_value( 'dates' ); + $exclude_taxes = Reports\get_taxes_excluded_filter(); + + $hbh = Reports\get_dates_filter_hour_by_hour(); + $label = $options[ $dates['range'] ] . ( $hbh ? ' (' . edd_get_timezone_abbr() . ')' : '' ); + + $reports->add_report( 'customers', array( + 'label' => __( 'Customers', 'easy-digital-downloads' ), + 'icon' => 'groups', + 'priority' => 40, + 'endpoints' => array( + 'tiles' => array( + 'new_customer_growth', + 'average_revenue_per_customer', + 'average_number_of_orders_per_customer', + ), + 'tables' => array( + 'top_five_customers', + 'most_valuable_customers', + ), + 'charts' => array( + 'new_customers', + ), + ), + ) ); + + $reports->register_endpoint( 'new_customer_growth', array( + 'label' => __( 'New Customers', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates ) { + $stats = new EDD\Stats(); + return $stats->get_customer_count( array( + 'range' => $dates['range'], + 'relative' => true, + 'purchase_count' => true, + ) ); + }, + 'display_args' => array( + 'comparison_label' => $label, + ), + ), + ), + ) ); + + $reports->register_endpoint( 'average_revenue_per_customer', array( + 'label' => __( 'Average Revenue per Customer', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () { + $stats = new EDD\Stats(); + return $stats->get_customer_lifetime_value( array( + 'function' => 'AVG', + 'output' => 'formatted', + ) ); + }, + ), + ), + ) ); + + $reports->register_endpoint( 'average_number_of_orders_per_customer', array( + 'label' => __( 'Average Orders per Customer', 'easy-digital-downloads' ), + 'views' => array( + 'tile' => array( + 'data_callback' => function () use ( $dates ) { + $stats = new EDD\Stats(); + return $stats->get_customer_order_count( array( + 'range' => $dates['range'], + 'function' => 'AVG', + 'relative' => true, + ) ); + }, + ), + ), + ) ); + + $reports->register_endpoint( 'top_five_customers', array( + 'label' => __( 'Top Five Customers — All Time', 'easy-digital-downloads' ), + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Customers\\Top_Five_Customers_List_Table', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/customers/class-top-five-customers-list-table.php', + ), + ), + ), + ) ); + + $reports->register_endpoint( 'most_valuable_customers', array( + 'label' => __( 'Most Valuable Customers', 'easy-digital-downloads' ) . ' — '. $label, + 'views' => array( + 'table' => array( + 'display_args' => array( + 'class_name' => '\\EDD\\Reports\\Data\\Customers\\Most_Valuable_Customers_List_Table', + 'class_file' => EDD_PLUGIN_DIR . 'includes/reports/data/customers/class-most-valuable-customers-list-table.php', + ), + ), + ), + ) ); + + $reports->register_endpoint( 'new_customers', array( + 'label' => __( 'New Customers', 'easy-digital-downloads' ) . ' — ' . $label, + 'views' => array( + 'chart' => array( + 'data_callback' => function () { + global $wpdb; + + $dates = Reports\get_dates_filter( 'objects' ); + $chart_dates = Reports\parse_dates_for_range( null, 'now', false ); + $period = Reports\get_graph_period(); + $sql_clauses = Reports\get_sql_clauses( $period ); + + $results = $wpdb->get_results( $wpdb->prepare( + "SELECT COUNT(c.id) AS total, {$sql_clauses['select']} + FROM {$wpdb->edd_customers} c + WHERE c.date_created >= %s AND c.date_created <= %s + AND c.purchase_count > 0 + GROUP BY {$sql_clauses['groupby']} + ORDER BY {$sql_clauses['orderby']} ASC", + $dates['start']->copy()->format( 'mysql' ), + $dates['end']->copy()->format( 'mysql' ) ) ); + + $customers = array(); + + // Initialise all arrays with timestamps and set values to 0. + while ( strtotime( $chart_dates['start']->copy()->format( 'mysql' ) ) <= strtotime( $chart_dates['end']->copy()->format( 'mysql' ) ) ) { + $timestamp = $chart_dates['start']->copy()->format( 'U' ); + $date_on_chart = $chart_dates['start']; + + $customers[ $timestamp ][0] = $date_on_chart->format( 'Y-m-d H:i:s' ); + $customers[ $timestamp ][1] = 0; + + foreach ( $results as $result ) { + $date_of_db_value = EDD()->utils->date( $result->date ); + + // Add any new customers that were created during this hour. + if ( 'hour' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d H' ) === $date_on_chart->format( 'Y-m-d H' ) ) { + $customers[ $timestamp ][1] += $result->total; + } + // Add any new customers that were created during this day. + } elseif ( 'day' === $period ) { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m-d' ) === $date_on_chart->format( 'Y-m-d' ) ) { + $customers[ $timestamp ][1] += $result->total; + } + // Add any new customers that were created during this month. + } else { + // If the date of this db value matches the date on this line graph/chart, set the y axis value for the chart to the number in the DB result. + if ( $date_of_db_value->format( 'Y-m' ) === $date_on_chart->format( 'Y-m' ) ) { + $customers[ $timestamp ][1] += $result->total; + } + } + } + + // Move the chart along to the next hour/day/month to get ready for the next loop. + if ( 'hour' === $period ) { + $chart_dates['start']->addHour( 1 ); + } elseif ( 'day' === $period ) { + $chart_dates['start']->addDays( 1 ); + } else { + $chart_dates['start']->addMonth( 1 ); + } + } + + return array( + 'customers' => array_values( $customers ), + ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'customers' => array( + 'label' => __( 'New Customers', 'easy-digital-downloads' ), + 'borderColor' => 'rgba(24,126,244,0.75)', + 'backgroundColor' => 'rgba(24,126,244,0.1)', + 'fill' => true, + 'borderWidth' => 2, + 'pointRadius' => 4, + 'pointHoverRadius' => 6, + 'pointBackgroundColor' => 'rgb(255,255,255)', + ), + ), + 'scales' => array( + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'maxTicksLimit' => 5, + 'formattingType' => 'integer', + 'suggestedMin' => 0, + 'beginAtZero' => true, + 'precision' => 0, + ), + ), + ), + ), + ), + ), + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_customer_report' ); + +/** + * Register export report and endpoints. + * + * @since 3.0 + * + * @param \EDD\Reports\Data\Report_Registry $reports Report registry. + */ +function edd_register_export_report( $reports ) { + try { + $reports->add_report( 'export', array( + 'label' => __( 'Export', 'easy-digital-downloads' ), + 'icon' => 'migrate', + 'priority' => 1000, + 'capability' => 'export_shop_reports', + 'display_callback' => 'display_export_report', + 'filters' => false, + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } +} +add_action( 'edd_reports_init', 'edd_register_export_report' ); +/** + * Render the `Export` report. + * + * @since 3.0 + */ +function display_export_report() { + wp_enqueue_script( 'edd-admin-tools-export' ); + ?> +
    +
    +
    +
    + +
    +
    +
    +
    + 0, + 'sales' => 0 + ); + + $stats = new EDD_Payment_Stats; + + $to_date_earnings = $stats->get_earnings( 0, 'this_month', null, $include_taxes ); + $to_date_sales = $stats->get_sales( 0, 'this_month' ); + + $current_day = date( 'd', current_time( 'timestamp' ) ); + $current_month = date( 'n', current_time( 'timestamp' ) ); + $current_year = date( 'Y', current_time( 'timestamp' ) ); + $days_in_month = cal_days_in_month( CAL_GREGORIAN, $current_month, $current_year ); + + $estimated['earnings'] = ( $to_date_earnings / $current_day ) * $days_in_month; + $estimated['sales'] = ( $to_date_sales / $current_day ) * $days_in_month; + + // Cache for one day + set_transient( 'edd_estimated_monthly_stats' . $include_taxes, $estimated, 86400 ); + } + + return maybe_unserialize( $estimated ); +} + +/** + * Adds postbox nonces, which are used to save the position of tile endpoint meta boxes. + * + * @since 3.0 + */ +function edd_add_screen_options_nonces() { + wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); + wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); +} +add_action( 'admin_footer', 'edd_add_screen_options_nonces' ); + +/** + * This function adds a notice to the bottom of the Tax reports screen if a default tax rate is detected, stating + * that we cannot report on the default tax rate. + * + * @since 3.0 + * @param \EDD\Reports\Data\Report|\WP_Error $report The current report object, or WP_Error if invalid. + */ +function edd_tax_report_notice( $report ) { + if ( 'taxes' === $report->object_id && false !== edd_get_option( 'tax_rate' ) ) { + ?> +

    + : + +

    + +

    +
    +

    +
    +
    + + + + + html->date_field( + array( + 'id' => 'edd-api-requests-export-start', + 'class' => 'edd-export-start', + 'name' => 'api-requests-export-start', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> + + html->date_field( + array( + 'id' => 'edd-api-requests-export-end', + 'class' => 'edd-export-end', + 'name' => 'api-requests-export-end', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> +
    + + + +
    +
    + diff --git a/includes/admin/reporting/views/export-customers.php b/includes/admin/reporting/views/export-customers.php new file mode 100644 index 00000000000..bb3cf5b96a6 --- /dev/null +++ b/includes/admin/reporting/views/export-customers.php @@ -0,0 +1,63 @@ +
    +

    +
    +

    +
    + prepare( "tt.taxonomy IN ({$placeholders})", $taxonomies ); + + $sql = "SELECT t.*, tt.*, tr.object_id + FROM {$wpdb->terms} AS t + INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id + INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id + WHERE {$taxonomy__in}"; + + $results = $wpdb->get_results( $sql ); + + $taxonomies = array(); + + if ( $results ) { + foreach ( $results as $r ) { + $t = get_taxonomy( $r->taxonomy ); + $taxonomies[ absint( $r->term_id ) ] = $t->labels->singular_name . ': ' . esc_html( $r->name ); + } + } + ?> + + html->select( + array( + 'name' => 'taxonomy', + 'id' => 'edd_export_taxonomy', + 'options' => $taxonomies, + 'selected' => false, + 'show_option_none' => false, + 'show_option_all' => __( 'All Taxonomies', 'easy-digital-downloads' ), + ) + ); + ?> + + html->product_dropdown( + array( + 'name' => 'download', + 'id' => 'edd_customer_export_download', + 'chosen' => true, + /* translators: the plural post type label */ + 'placeholder' => sprintf( __( 'All %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + ) + ); + + wp_nonce_field( 'edd_ajax_export', 'edd_ajax_export' ); + ?> + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-download-history.php b/includes/admin/reporting/views/export-download-history.php new file mode 100644 index 00000000000..03a4cebf35d --- /dev/null +++ b/includes/admin/reporting/views/export-download-history.php @@ -0,0 +1,49 @@ +
    +

    +
    +

    +
    + + html->product_dropdown( + array( + 'name' => 'download_id', + 'id' => 'edd_file_download_export_download', + 'chosen' => true, + /* translators: the plural post type label */ + 'placeholder' => sprintf( __( 'All %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + ) + ); + ?> +
    + + + + + html->date_field( + array( + 'id' => 'edd-file-download-export-start', + 'class' => 'edd-export-start', + 'name' => 'file-download-export-start', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> + + html->date_field( + array( + 'id' => 'edd-file-download-export-end', + 'class' => 'edd-export-end', + 'name' => 'file-download-export-end', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> +
    + + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-downloads.php b/includes/admin/reporting/views/export-downloads.php new file mode 100644 index 00000000000..7ede65552ee --- /dev/null +++ b/includes/admin/reporting/views/export-downloads.php @@ -0,0 +1,26 @@ +
    +

    +
    +

    +
    + + html->product_dropdown( + array( + 'name' => 'download_id', + 'id' => 'edd_download_export_download', + 'chosen' => true, + /* translators: the plural post type label */ + 'placeholder' => sprintf( __( 'All %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + ) + ); + ?> + + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-earnings-report.php b/includes/admin/reporting/views/export-earnings-report.php new file mode 100644 index 00000000000..d12e3e1a8b2 --- /dev/null +++ b/includes/admin/reporting/views/export-earnings-report.php @@ -0,0 +1,32 @@ +
    +

    +
    +

    +
    +
    + + + + + html->month_dropdown( 'start_month', 0, 'edd_export_earnings', true ); ?> + + html->year_dropdown( 'start_year', 0, 5, 0, 'edd_export_earnings' ); ?> +
    + + + +
    + + + + + html->month_dropdown( 'end_month', 0, 'edd_export_earnings', true ); ?> + + html->year_dropdown( 'end_year', 0, 5, 0, 'edd_export_earnings' ); ?> +
    + + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-orders.php b/includes/admin/reporting/views/export-orders.php new file mode 100644 index 00000000000..626ed1fe7c6 --- /dev/null +++ b/includes/admin/reporting/views/export-orders.php @@ -0,0 +1,52 @@ +
    +

    +
    +

    +
    +
    + + + + + html->date_field( + array( + 'id' => 'edd-orders-export-start', + 'class' => 'edd-export-start', + 'name' => 'orders-export-start', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ), + ) + ); + ?> + + html->date_field( + array( + 'id' => 'edd-orders-export-end', + 'class' => 'edd-export-end', + 'name' => 'orders-export-end', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ), + ) + ); + ?> +
    + + html->select( + array( + 'id' => 'edd_orders_export_status', + 'name' => 'status', + 'show_option_all' => __( 'All Statuses', 'easy-digital-downloads' ), + 'show_option_none' => false, + 'selected' => false, + 'options' => edd_get_payment_statuses(), + ) + ); + + wp_nonce_field( 'edd_ajax_export', 'edd_ajax_export' ); + ?> + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-sales-earnings.php b/includes/admin/reporting/views/export-sales-earnings.php new file mode 100644 index 00000000000..d62be100faa --- /dev/null +++ b/includes/admin/reporting/views/export-sales-earnings.php @@ -0,0 +1,64 @@ +
    +

    +
    +

    +
    +
    + + + + + html->date_field( + array( + 'id' => 'edd-order-export-start', + 'class' => 'edd-export-start', + 'name' => 'order-export-start', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ), + ) + ); + ?> + + html->date_field( + array( + 'id' => 'edd-order-export-end', + 'class' => 'edd-export-end', + 'name' => 'order-export-end', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ), + ) + ); + + ?> +
    + + html->product_dropdown( + array( + 'name' => 'download_id', + 'id' => 'edd_orders_export_download', + 'chosen' => true, + /* translators: the plural post type label */ + 'placeholder' => sprintf( __( 'All %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + ) + ); + ?> + + html->customer_dropdown( + array( + 'name' => 'customer_id', + 'id' => 'edd_order_export_customer', + 'chosen' => true, + 'none_selected' => '', + 'placeholder' => __( 'All Customers', 'easy-digital-downloads' ), + ) + ); + + wp_nonce_field( 'edd_ajax_export', 'edd_ajax_export' ); ?> + + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-sales.php b/includes/admin/reporting/views/export-sales.php new file mode 100644 index 00000000000..c324c241608 --- /dev/null +++ b/includes/admin/reporting/views/export-sales.php @@ -0,0 +1,49 @@ +
    +

    +
    +

    +
    +
    + + + + + html->date_field( + array( + 'id' => 'edd-sales-export-start', + 'class' => 'edd-sales-export-start', + 'name' => 'sales-export-start', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ), + ) + ); + ?> + + html->date_field( + array( + 'id' => 'edd-sales-export-end', + 'class' => 'edd-export-end', + 'name' => 'sales-export-end', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ), + ) + ); + + ?> +
    + + html->product_dropdown( + array( + 'name' => 'download_id', + 'id' => 'edd_sales_export_download', + 'chosen' => true, + ) + ); + ?> + + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-taxed-customers.php b/includes/admin/reporting/views/export-taxed-customers.php new file mode 100644 index 00000000000..04e86600bec --- /dev/null +++ b/includes/admin/reporting/views/export-taxed-customers.php @@ -0,0 +1,37 @@ +
    +

    +
    +

    +
    +
    + + + + html->date_field( + array( + 'id' => 'edd-taxed-customers-export-start', + 'class' => 'edd-export-start', + 'name' => 'taxed-customers-export-start', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> + + html->date_field( + array( + 'id' => 'edd-taxed-customers-export-end', + 'class' => 'edd-export-end', + 'name' => 'taxed-customers-export-end', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> +
    + + + +
    +
    +
    diff --git a/includes/admin/reporting/views/export-taxed-orders.php b/includes/admin/reporting/views/export-taxed-orders.php new file mode 100644 index 00000000000..359a5de9ed0 --- /dev/null +++ b/includes/admin/reporting/views/export-taxed-orders.php @@ -0,0 +1,72 @@ +
    +

    +
    +

    +
    +
    + + + + + html->date_field( + array( + 'id' => 'edd-taxed-orders-export-start', + 'class' => 'edd-export-start', + 'name' => 'taxed-orders-export-start', + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> + + html->date_field( + array( + 'id' => 'edd-taxed-orders-export-end', + 'class' => 'edd-export-end', + 'name' => 'taxed-orders-export-end', + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ) + ) + ); + ?> +
    + + html->select( + array( + 'id' => 'edd_taxed_orders_export_status', + 'name' => 'status', + 'show_option_all' => __( 'All Statuses', 'easy-digital-downloads' ), + 'show_option_none' => false, + 'selected' => false, + 'options' => edd_get_payment_statuses(), + ) + ); + ?> + + html->country_select( + array( + 'name' => 'country', + 'id' => 'edd_reports_filter_taxed_countries', + 'selected' => false, + 'show_option_all' => false, + ) + ); + ?> + + html->region_select( + array( + 'id' => 'edd_reports_filter_regions', + 'placeholder' => __( 'All Regions', 'easy-digital-downloads' ), + ) + ); + + wp_nonce_field( 'edd_ajax_export', 'edd_ajax_export' ); + ?> + + +
    +
    +
    diff --git a/includes/admin/settings/contextual-help.php b/includes/admin/settings/contextual-help.php new file mode 100755 index 00000000000..e62cf8a02d1 --- /dev/null +++ b/includes/admin/settings/contextual-help.php @@ -0,0 +1,136 @@ +id ) { + return; + } + + $pass_manager = new Pass_Manager(); + if ( $pass_manager->isFree() ) { + $docs_url = edd_link_helper( + 'https://easydigitaldownloads.com/docs/', + array( + 'utm_medium' => 'settings-contextual-help', + 'utm_content' => 'documentation', + ) + ); + + $upgrade_url = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => 'settings-contextual-help', + 'utm_content' => 'lite-upgrade', + ) + ); + + $screen->set_help_sidebar( + '

    ' . __( 'For more information:', 'easy-digital-downloads' ) . '

    ' . + '

    ' . sprintf( __( 'Visit the documentation on the Easy Digital Downloads website.', 'easy-digital-downloads' ), $docs_url ) . '

    ' . + '

    ' . sprintf( + __( 'Need more from your Easy Digital Downloads store? Upgrade Now!.', 'easy-digital-downloads' ), + $upgrade_url + ) . '

    ' + ); + } + + $screen->add_help_tab( array( + 'id' => 'edd-settings-general', + 'title' => __( 'General', 'easy-digital-downloads' ), + 'content' => '

    ' . __( 'This screen provides the most basic settings for configuring your store. You can set the currency, page templates, and general store settings.', 'easy-digital-downloads' ) . '

    ', + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-payment-gateways', + 'title' => __( 'Payments', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'This screen provides ways to enable Test Mode, toggle payment gateways on or off, manage accounting settings, and configure gateway-specific settings. Any extra payment gateway extensions you have installed will appear on this page, and can be configured to suit your needs.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Test Payment - This included gateway is great for testing your store, as it requires no payment, and leads straight to product downloads. However, please remember to turn it off once your site is live!', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'PayPal - A PayPal payment gateway is included as standard with Easy Digital Downloads. To test the PayPal gateway, you need a Sandbox account for PayPal and the site must be placed in Test Mode from the Payments > Gateways tab. Please remember to enter your PayPal account email address in order for payments to get processed.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Stripe - The Stripe payment gateway is also included with Easy Digital Downloads. To test the Stripe gateway, you must "Connect with Stripe" and the site must be placed in Test Mode from the Payments > Gateways tab.', 'easy-digital-downloads' ) . '

    ', + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-emails', + 'title' => __( 'Emails', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( "This screen allows you to customize how emails act throughout your store. You can choose a premade template, set the sender's name, email address, and subject.", 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'A set of email tag markers has also been provided to allow the creation of personalized emails. A tag consists of a keyword surrounded by curly braces: {tag}.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-marketing', + 'title' => __( 'Marketing', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'Marketing settings will help you connect with your customers.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Marketing specific extensions will add their settings here as well.', 'easy-digital-downloads' ) . '

    ', + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-styles', + 'title' => __( 'Styles', 'easy-digital-downloads' ), + 'content' => '

    ' . __( "This screen allows customization of your store's styles. For complete control, you can completely disable all styles generated by the plugin.", 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-taxes', + 'title' => __( 'Taxes', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'This screen allows you to configure the tax rules for your store.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'If you do not wish to charge any tax on purchase, simply leave the Enable Taxes option unchecked.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Default Tax Rate: The default tax rate is the tax rate charged to customers located in your base country / state or province.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Base Country: This determines the country that is loaded by default on the checkout screen for customers that do not have an address stored in their account.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Base State / Province: This determines the region that is loaded by default on the checkout screen for customers that do not have an address stored in their account.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Prices Entered with Tax: if enabled, this means that the price entered on the product edit screens is the total amount the customer will pay after taxes. For example, if enabled and the price of a product is $20, the customer will pay 20$ at checkout. The exact amount charged in tax will be calculated automatically.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Display Tax Rate on Prices: when enabled, the amount the customer is expected to pay in tax will be displayed below purchase buttons.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Display During Checkout: This determines whether prices are shown with taxes or without taxes on checkout. If set to Including Tax, a $10 product with a 10% tax will be shown as $11.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Calculate Tax After Discounts: If enabled, this option will make it so that tax is calculated on the after-discount amount. If a purchase of $20 is made and a 20% discount is applied, tax will be calculated off of $16 instead of $20.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'Additional Tax Rates: This section lets you add tax rates for specific countries and/or states/provinces in those countries.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-privacy', + 'title' => __( 'Policies', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'This screen provides access to customer privacy policies, terms & agreements, and how to display them on the front of your site.', 'easy-digital-downloads' ) . '

    ' . + '

    ' . __( 'You may also override what happens to order records when a customer exercises their right to be forgotten from your site.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-extensions', + 'title' => __( 'Extensions', 'easy-digital-downloads' ), + 'content' => '

    ' . __( 'This screen provides access to settings added by most Easy Digital Downloads extensions.', 'easy-digital-downloads' ) . '

    ' + ) ); + + $screen->add_help_tab( array( + 'id' => 'edd-settings-misc', + 'title' => __( 'Miscellaneous', 'easy-digital-downloads' ), + 'content' => + '

    ' . __( 'This screen provides other miscellaneous options such as configuring your store buttons, file download functionality, and terms of service.', 'easy-digital-downloads' ) . '

    ', + ) ); + + do_action( 'edd_settings_contextual_help', $screen ); +} +add_action( 'load-download_page_edd-settings', 'edd_settings_contextual_help' ); diff --git a/includes/admin/settings/display-settings.php b/includes/admin/settings/display-settings.php new file mode 100755 index 00000000000..c88c03ca8ef --- /dev/null +++ b/includes/admin/settings/display-settings.php @@ -0,0 +1,441 @@ +notifications->countActiveNotifications(); + $current_page = ! empty( $_GET['page'] ) ? $_GET['page'] : ''; + $is_single_view = (bool) apply_filters( 'edd_admin_is_single_view', ! empty( $_GET['view'] ) ); + + $page_title = __( 'Downloads', 'easy-digital-downloads' ); + switch( $current_page ) { + case 'edd-settings': + $page_title = __( 'Settings', 'easy-digital-downloads' ); + break; + case 'edd-reports': + $page_title = __( 'Reports', 'easy-digital-downloads' ); + break; + case 'edd-payment-history': + $page_title = __( 'Orders', 'easy-digital-downloads' ); + break; + case 'edd-discounts': + $page_title = __( 'Discounts', 'easy-digital-downloads' ); + break; + case 'edd-customers': + $page_title = __( 'Customers', 'easy-digital-downloads' ); + break; + case 'edd-tools': + $page_title = __( 'Tools', 'easy-digital-downloads' ); + break; + case 'edd-addons': + $page_title = __( 'View Extensions', 'easy-digital-downloads' ); + if ( edd_is_pro() ) { + $page_title = __( 'Manage Extensions', 'easy-digital-downloads' ); + } + break; + default: + if ( ! empty( $_GET['page'] ) ) { + $page_title = ucfirst( str_replace( array( 'edd-', 'fes-' ), '', $current_page ) ); + } elseif ( ! empty( $_GET['post_type'] ) ) { + $post_type = get_post_type_object( $_GET['post_type'] ); + $page_title = $post_type->labels->name; + } + break; + } + + $page_title = apply_filters( 'edd_settings_page_title', $page_title, $current_page, $is_single_view ); + if ( ! empty( $page_title ) && empty( $is_single_view ) ) { + ?> + + + + +
    +
    + + + + + + + / + + < class="edd-header-page-title">> + + + +
    + +
    +
    +
    + + + __( 'General', 'easy-digital-downloads' ) + ); + } + + // Default links array + $links = array(); + + // Loop through sections + foreach ( $sections as $section_id => $section_name ) { + + // Tab & Section + $tab_url = add_query_arg( + array( + 'post_type' => 'download', + 'page' => 'edd-settings', + 'tab' => $active_tab, + 'section' => $section_id, + ), + edd_get_admin_base_url() + ); + + // Settings not updated + $tab_url = remove_query_arg( 'settings-updated', $tab_url ); + + // Class for link + $class = ( $section === $section_id ) + ? 'current' + : ''; + + // Add to links array + $links[ $section_id ] = '
  • ' . esc_html( $section_name ) . '
  • '; + } + if ( count( $links ) < 2 ) { + return; + } + ?> + +
    +
      + +
    +
    + + + +
    +
    +
    + +
    +
    +
    + + $stitle ) { + if ( is_string( $sid ) && ! empty( $sections ) && array_key_exists( $sid, $sections ) ) { + continue; + } else { + $has_main_settings = true; + break; + } + } + } + + // Unset 'main' if it's empty and default to the first non-empty if it's the chosen section. + if ( false === $has_main_settings ) { + unset( $sections['main'] ); + + if ( 'main' === $section ) { + foreach ( $sections as $section_key => $section_title ) { + if ( ! empty( $all_settings[ $active_tab ][ $section_key ] ) ) { + $section = $section_key; + $override = true; + break; + } + } + } + } + + // Start a buffer + ob_start(); ?> + +
    +

    + +
    +
    +
    +

    + +

    +
    +
    + +
    +

    + +

    + highest_license_key ) : + ?> +

    + 'edd-settings', + 'tab' => 'general', + ) + ); + printf( + wp_kses_post( + /* translators: 1. opening anchor tag; 2. closing anchor tag */ + __( 'Have a pass? You\'re ready to set up EDD (Pro). %1$sActivate Your Pass%2$s' ) + ), + '', + '' + ); + ?> +

    + +
    + $sections ) { + + // Loop through sections + foreach ( $sections as $section => $settings ) { + + // Check for backwards compatibility + $section_tabs = edd_get_settings_tab_sections( $tab ); + if ( ! is_array( $section_tabs ) || ! array_key_exists( $section, $section_tabs ) ) { + $section = 'main'; + $settings = $sections; + } + + // Current page + $page = "edd_settings_{$tab}_{$section}"; + + // Add the settings section + add_settings_section( + $page, + __return_null(), + '__return_false', + $page + ); + + foreach ( $settings as $option ) { + + // For backwards compatibility + if ( empty( $option['id'] ) ) { + continue; + } + + // Parse args + $args = wp_parse_args( $option, array( + 'section' => $section, + 'id' => null, + 'desc' => '', + 'name' => '', + 'size' => null, + 'options' => '', + 'std' => '', + 'min' => null, + 'max' => null, + 'step' => null, + 'chosen' => null, + 'multiple' => null, + 'placeholder' => null, + 'allow_blank' => true, + 'readonly' => false, + 'faux' => false, + 'tooltip_title' => false, + 'tooltip_desc' => false, + 'field_class' => '', + 'label_for' => false + ) ); + + // Callback fallback + $func = 'edd_' . $args['type'] . '_callback'; + $callback = ! function_exists( $func ) + ? 'edd_missing_callback' + : $func; + + // Link the label to the form field + if ( empty( $args['label_for'] ) ) { + $args['label_for'] = 'edd_settings[' . $args['id'] . ']'; + } + + // Add the settings field + add_settings_field( + 'edd_settings[' . $args['id'] . ']', + $args['name'], + $callback, + $page, + $page, + $args + ); + } + } + } + + // Register our setting in the options table + register_setting( 'edd_settings', 'edd_settings', 'edd_settings_sanitize' ); +} +add_action( 'admin_init', 'edd_register_settings' ); + +/** + * Retrieve the array of plugin settings + * + * @since 1.8 + * @since 3.0 Use a static variable internally to store registered settings + * @return array + */ +function edd_get_registered_settings() { + static $edd_settings = null; + + // Only build settings if not already built. + if ( null === $edd_settings ) { + $settings = new EDD\Admin\Settings\Register(); + $edd_settings = $settings->get(); + } + + return $edd_settings; +} + +/** + * Settings Sanitization + * + * Adds a settings error (for the updated message) + * At some point this will validate input + * + * @since 1.0.8.2 + * + * @param array $input The value inputted in the field + * + * @global array $edd_options Array of all the EDD Options + * + * @return array $input Sanitized value + */ +function edd_settings_sanitize( $input = array() ) { + global $edd_options; + + // Default values + $referrer = ''; + $setting_types = edd_get_registered_settings_types(); + $doing_section = ! empty( $_POST['_wp_http_referer'] ); + $input = ! empty( $input ) + ? $input + : array(); + + if ( true === $doing_section ) { + + // Pull out the tab and section + parse_str( $_POST['_wp_http_referer'], $referrer ); + $tab = ! empty( $referrer['tab'] ) ? sanitize_key( $referrer['tab'] ) : 'general'; + $section = ! empty( $referrer['section'] ) ? sanitize_key( $referrer['section'] ) : 'main'; + + if ( ! empty( $_POST['edd_tab_override'] ) ) { + $tab = sanitize_text_field( $_POST['edd_tab_override'] ); + } + + // Maybe override the tab section + if ( ! empty( $_POST['edd_section_override'] ) ) { + $section = sanitize_text_field( $_POST['edd_section_override'] ); + } + + // Get setting types for this section + $setting_types = edd_get_registered_settings_types( $tab, $section ); + + // Run a general sanitization for the tab for special fields (like taxes) + $input = apply_filters( 'edd_settings_' . $tab . '_sanitize', $input ); + + // Run a general sanitization for the section so custom tabs with sub-sections can save special data + $input = apply_filters( 'edd_settings_' . $tab . '-' . $section . '_sanitize', $input ); + } + + // Remove non setting types and merge settings together + $non_setting_types = edd_get_non_setting_types(); + $setting_types = array_diff( $setting_types, $non_setting_types ); + $output = array_merge( $edd_options, $input ); + + // Loop through settings, and apply any filters + foreach ( $setting_types as $key => $type ) { + + // Skip if type is empty + if ( empty( $type ) ) { + continue; + } + + if ( array_key_exists( $key, $output ) ) { + $output[ $key ] = apply_filters( 'edd_settings_sanitize_' . $type, $output[ $key ], $key ); + $output[ $key ] = apply_filters( 'edd_settings_sanitize', $output[ $key ], $key ); + } + + if ( true === $doing_section ) { + switch ( $type ) { + case 'checkbox': + case 'checkbox_description': + case 'gateways': + case 'multicheck': + case 'payment_icons': + if ( array_key_exists( $key, $input ) && $output[ $key ] === '-1' ) { + unset( $output[ $key ] ); + } + break; + case 'text': + if ( array_key_exists( $key, $input ) && empty( $input[ $key ] ) ) { + unset( $output[ $key ] ); + } + break; + case 'number': + if ( array_key_exists( $key, $output ) && ! array_key_exists( $key, $input ) ) { + unset( $output[ $key ] ); + } + + $setting_details = edd_get_registered_setting_details( $tab, $section, $key ); + $number_type = ! empty( $setting_details['step'] ) && false !== strpos( $setting_details['step'], '.' ) ? 'floatval' : 'intval'; + $minimum = isset( $setting_details['min'] ) ? $number_type( $setting_details['min'] ) : false; + $maximum = isset( $setting_details['max'] ) ? $number_type( $setting_details['max'] ) : false; + $new_value = $number_type( $input[ $key ] ); + + if ( ( false !== $minimum && $minimum > $new_value ) || ( false !== $maximum && $maximum < $new_value ) ) { + unset( $output[ $key ] ); + } + break; + default: + if ( array_key_exists( $key, $input ) && empty( $input[ $key ] ) || ( array_key_exists( $key, $output ) && ! array_key_exists( $key, $input ) ) ) { + unset( $output[ $key ] ); + } + break; + } + } elseif ( empty( $input[ $key ] ) ) { + unset( $output[ $key ] ); + } + } + + // Return output. + return (array) $output; +} + +/** + * Flattens the set of registered settings and their type so we can easily sanitize all the settings + * in a much cleaner set of logic in edd_settings_sanitize + * + * @since 2.6.5 + * @since 2.8 - Added the ability to filter setting types by tab and section + * + * @param $filtered_tab bool|string A tab to filter setting types by. + * @param $filtered_section bool|string A section to filter setting types by. + * + * @return array Key is the setting ID, value is the type of setting it is registered as + */ +function edd_get_registered_settings_types( $filtered_tab = false, $filtered_section = false ) { + $settings = edd_get_registered_settings(); + $setting_types = array(); + + foreach ( $settings as $tab_id => $tab ) { + + if ( false !== $filtered_tab && $filtered_tab !== $tab_id ) { + continue; + } + + foreach ( $tab as $section_id => $section_or_setting ) { + + // See if we have a setting registered at the tab level for backwards compatibility + if ( false !== $filtered_section && is_array( $section_or_setting ) && array_key_exists( 'type', $section_or_setting ) ) { + $setting_types[ $section_or_setting['id'] ] = $section_or_setting['type']; + continue; + } + + if ( false !== $filtered_section && $filtered_section !== $section_id ) { + continue; + } + + foreach ( $section_or_setting as $section_settings ) { + if ( ! empty( $section_settings['type'] ) ) { + $setting_types[ $section_settings['id'] ] = $section_settings['type']; + } + } + } + } + + return $setting_types; +} + +/** + * Allow getting a specific setting's details. + * + * @since 3.0 + * + * @param string $filtered_tab The tab the setting's section is in. + * @param string $filtered_section The section the setting is located in. + * @param string $setting_key The key associated with the setting. + * + * @return array + */ +function edd_get_registered_setting_details( $filtered_tab = '', $filtered_section = '', $setting_key = '' ) { + $settings = edd_get_registered_settings(); + $setting_details = array(); + + if ( isset( $settings[ $filtered_tab ][ $filtered_section][ $setting_key ] ) ) { + $setting_details = $settings[ $filtered_tab ][ $filtered_section][ $setting_key ]; + } + + return $setting_details; +} + +/** + * Return array of settings field types that aren't settings. + * + * @since 3.0 + * + * @return array + */ +function edd_get_non_setting_types() { + return apply_filters( 'edd_non_setting_types', array( + 'header', + 'descriptive_text', + 'hook', + ) ); +} + +/** + * Misc File Download Settings Sanitization + * + * @since 2.5 + * + * @param array $input The value inputted in the field + * + * @return string $input Sanitized value + */ +function edd_settings_sanitize_misc_file_downloads( $input ) { + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return $input; + } + + if ( edd_get_file_download_method() != $input['download_method'] || ! edd_htaccess_exists() ) { + // Force the .htaccess files to be updated if the Download method was changed. + edd_create_protection_files( true, $input['download_method'] ); + } + + return $input; +} + +add_filter( 'edd_settings_misc-file_downloads_sanitize', 'edd_settings_sanitize_misc_file_downloads' ); + +/** + * Misc Accounting Settings Sanitization + * + * @since 2.5 + * + * @param array $input The value inputted in the field + * + * @return array $input Sanitized value + */ +function edd_settings_sanitize_misc_accounting( $input ) { + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return $input; + } + + return $input; +} + +/** + * Taxes Settings Sanitization + * + * Adds a settings error (for the updated message) + * This also saves the tax rates table + * + * @since 1.6 + * + * @param array $input The value inputted in the field + * + * @return array $input Sanitized value. + */ +function edd_settings_sanitize_taxes( $input ) { + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return $input; + } + + if ( ! isset( $_POST['tax_rates'] ) ) { + return $input; + } + + $tax_rates = ! empty( $_POST['tax_rates'] ) + ? $_POST['tax_rates'] + : array(); + + foreach ( $tax_rates as $tax_rate ) { + + $scope = isset( $tax_rate['global'] ) + ? 'country' + : 'region'; + + $region = isset( $tax_rate['state'] ) + ? sanitize_text_field( $tax_rate['state'] ) + : ''; + + $name = '*' === $tax_rate['country'] + ? '' + : sanitize_text_field( $tax_rate['country'] ); + + if ( empty( $name ) ) { + $scope = 'global'; + } + + $adjustment_data = array( + 'name' => $name, + 'type' => 'tax_rate', + 'scope' => $scope, + 'amount_type' => 'percent', + 'amount' => floatval( $tax_rate['rate'] ), + 'description' => $region, + ); + + if ( ( empty( $adjustment_data['name'] ) && 'global' !== $adjustment_data['scope'] ) || $adjustment_data['amount'] < 0 ) { + continue; + } + + $existing_adjustment = edd_get_adjustments( $adjustment_data ); + + if ( ! empty( $existing_adjustment ) ) { + $adjustment = $existing_adjustment[0]; + $adjustment_data['status'] = sanitize_text_field( $tax_rate['status'] ); + + edd_update_adjustment( $adjustment->id, $adjustment_data ); + } else { + $adjustment_data['status'] = 'active'; + + edd_add_tax_rate( $adjustment_data ); + } + } + + return $input; +} +add_filter( 'edd_settings_taxes_sanitize', 'edd_settings_sanitize_taxes' ); + +/** + * Payment Gateways Settings Sanitization + * + * @since 2.7 + * + * @param array $input The value inputted in the field + * + * @return string $input Sanitized value + */ +function edd_settings_sanitize_gateways( $input = array() ) { + + // Bail if user cannot manage shop settings + if ( ! current_user_can( 'manage_shop_settings' ) || empty( $input['default_gateway'] ) ) { + return $input; + } + + // Unset the default gateway if there are no `gateways` enabled + if ( empty( $input['gateways'] ) || '-1' == $input['gateways'] ) { + unset( $input['default_gateway'] ); + + // Current gateway is no longer enabled, so + } elseif ( ! array_key_exists( $input['default_gateway'], $input['gateways'] ) ) { + $enabled_gateways = $input['gateways']; + + reset( $enabled_gateways ); + + $first_gateway = key( $enabled_gateways ); + + if ( $first_gateway ) { + $input['default_gateway'] = $first_gateway; + } + } + + return $input; +} +add_filter( 'edd_settings_gateways_sanitize', 'edd_settings_sanitize_gateways' ); + +/** + * Sanitize text fields + * + * @since 1.8 + * + * @param array $input The field value + * + * @return string $input Sanitized value + */ +function edd_sanitize_text_field( $input = '' ) { + $allowed_tags = edd_get_allowed_tags(); + + return trim( wp_kses( $input, $allowed_tags ) ); +} + +add_filter( 'edd_settings_sanitize_text', 'edd_sanitize_text_field' ); + +/** + * Sanitize HTML Class Names + * + * @since 2.6.11 + * + * @param string|array $class HTML Class Name(s) + * + * @return string $class + */ +function edd_sanitize_html_class( $class = '' ) { + + if ( is_string( $class ) ) { + $class = sanitize_html_class( $class ); + } else if ( is_array( $class ) ) { + $class = array_values( array_map( 'sanitize_html_class', $class ) ); + $class = implode( ' ', array_unique( $class ) ); + } + + return $class; +} + + +/** + * Sanitizes banned emails. + * + * @since 3.0 + */ +function edd_sanitize_banned_emails( $input ) { + + $emails = ''; + if ( ! empty( $input['banned_emails'] ) ) { + // Sanitize the input + $emails = array_map( 'trim', explode( "\n", $input['banned_emails'] ) ); + $emails = array_unique( $emails ); + $emails = array_map( 'sanitize_text_field', $emails ); + + foreach ( $emails as $id => $email ) { + if ( ! is_email( $email ) && $email[0] != '@' && $email[0] != '.' ) { + unset( $emails[ $id ] ); + } + } + } + $input['banned_emails'] = $emails; + + return $input; +} +add_filter( 'edd_settings_gateways-checkout_sanitize', 'edd_sanitize_banned_emails' ); + +/** + * Retrieve settings tabs + * + * @since 1.8 + * @since 2.11.4 Any tabs with no registered settings are filtered out in `edd_options_page`. + * + * @return array $tabs + */ +function edd_get_settings_tabs() { + return apply_filters( 'edd_settings_tabs', array( + 'general' => __( 'General', 'easy-digital-downloads' ), + 'gateways' => __( 'Payments', 'easy-digital-downloads' ), + 'emails' => __( 'Emails', 'easy-digital-downloads' ), + 'marketing' => __( 'Marketing', 'easy-digital-downloads' ), + 'styles' => __( 'Styles', 'easy-digital-downloads' ), + 'taxes' => __( 'Taxes', 'easy-digital-downloads' ), + 'privacy' => __( 'Policies', 'easy-digital-downloads' ), + 'extensions' => __( 'Extensions', 'easy-digital-downloads' ), + 'licenses' => __( 'Licenses', 'easy-digital-downloads' ), + 'misc' => __( 'Misc', 'easy-digital-downloads' ), + ) ); +} + +/** + * Retrieve settings tabs + * + * @since 2.5 + * @return array $section + */ +function edd_get_settings_tab_sections( $tab = false ) { + $tabs = array(); + $sections = edd_get_registered_settings_sections(); + + if ( $tab && ! empty( $sections[ $tab ] ) ) { + $tabs = $sections[ $tab ]; + } else if ( $tab ) { + $tabs = array(); + } + + return $tabs; +} + +/** + * Get the settings sections for each tab + * Uses a static to avoid running the filters on every request to this function + * + * @since 2.5 + * @return array Array of tabs and sections + */ +function edd_get_registered_settings_sections() { + static $sections = null; + + if ( null === $sections ) { + $sections = array( + 'general' => apply_filters( 'edd_settings_sections_general', array( + 'main' => __( 'Store', 'easy-digital-downloads' ), + 'currency' => __( 'Currency', 'easy-digital-downloads' ), + 'pages' => __( 'Pages', 'easy-digital-downloads' ), + 'api' => __( 'API', 'easy-digital-downloads' ), + ) ), + 'gateways' => apply_filters( 'edd_settings_sections_gateways', array( + 'main' => __( 'General', 'easy-digital-downloads' ), + 'checkout' => __( 'Checkout', 'easy-digital-downloads' ), + 'refunds' => __( 'Refunds', 'easy-digital-downloads' ), + 'accounting' => __( 'Accounting', 'easy-digital-downloads' ), + ) ), + 'emails' => apply_filters( 'edd_settings_sections_emails', array( + 'main' => __( 'General', 'easy-digital-downloads' ), + 'purchase_receipts' => __( 'Purchase Receipts', 'easy-digital-downloads' ), + 'sale_notifications' => __( 'Sale Notifications', 'easy-digital-downloads' ), + 'email_summaries' => __( 'Summaries', 'easy-digital-downloads' ), + ) ), + 'marketing' => apply_filters( 'edd_settings_sections_marketing', array( + 'main' => __( 'General', 'easy-digital-downloads' ), + ) ), + 'styles' => apply_filters( 'edd_settings_sections_styles', array( + 'main' => __( 'General', 'easy-digital-downloads' ), + 'buttons' => __( 'Buttons', 'easy-digital-downloads' ) + ) ), + 'taxes' => apply_filters( 'edd_settings_sections_taxes', array( + 'main' => __( 'General', 'easy-digital-downloads' ), + 'rates' => __( 'Rates', 'easy-digital-downloads' ), + ) ), + 'privacy' => apply_filters( 'edd_settings_section_privacy', array( + 'main' => __( 'Privacy Policy', 'easy-digital-downloads' ), + 'site_terms' => __( 'Terms & Agreements', 'easy-digital-downloads' ), + 'export_erase' => __( 'Export & Erase', 'easy-digital-downloads' ) + ) ), + 'extensions' => apply_filters( 'edd_settings_sections_extensions', array( + 'main' => __( 'Main', 'easy-digital-downloads' ) + ) ), + 'licenses' => apply_filters( 'edd_settings_sections_licenses', array() ), + 'misc' => apply_filters( 'edd_settings_sections_misc', array( + 'main' => __( 'General', 'easy-digital-downloads' ), + 'button_text' => __( 'Purchase Buttons', 'easy-digital-downloads' ), + 'file_downloads' => __( 'File Downloads', 'easy-digital-downloads' ), + ) ) + ); + } + + // Filter & return + return apply_filters( 'edd_settings_sections', $sections ); +} + +/** + * Retrieve a list of all published pages + * + * On large sites this can be expensive, so only load if on the settings page or $force is set to true + * + * @since 1.9.5 + * + * @param bool $force Force the pages to be loaded even if not on settings + * + * @return array $pages_options An array of the pages + */ +function edd_get_pages( $force = false ) { + + $pages_options = array( '' => __( 'None', 'easy-digital-downloads' ) ); + + if ( ( ! isset( $_GET['page'] ) || 'edd-settings' !== $_GET['page'] ) && ! $force ) { + return $pages_options; + } + + $pages = get_pages(); + if ( $pages ) { + foreach ( $pages as $page ) { + $pages_options[ $page->ID ] = $page->post_title; + } + } + + return $pages_options; +} + +/** + * Header Callback + * + * Renders the header. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_header_callback( $args ) { + echo apply_filters( 'edd_after_setting_output', '', $args ); +} + +/** + * Checkbox Callback + * + * Renders checkboxes. + * + * @since 1.0 + * @since 3.0 Updated to use `EDD_HTML_Elements`. + * + * @param array $args Arguments passed by the setting. + */ +function edd_checkbox_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( isset( $args['faux'] ) && true === $args['faux'] ) { + $name = ''; + } else { + $name = 'edd_settings[' . edd_sanitize_key( $args['id'] ) . ']'; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $args['name'] = $name; + $args['class'] = $class; + $args['current'] = ! empty( $edd_option ) + ? $edd_option + : ''; + $args['label'] = wp_kses_post( $args['desc'] ); + $args['value'] = 1; + + $html = ''; + $html .= '
    '; + $html .= EDD()->html->checkbox( $args ); + $html .= '
    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Checkbox with description Callback + * + * Renders checkboxes with a description. + * + * @since 3.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_checkbox_description_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + // Allow a setting or filter to override what the found value is. + if ( isset( $args['current'] ) ) { + $edd_option = $args['current']; + } + + if ( isset( $args['faux'] ) && true === $args['faux'] ) { + $name = ''; + } else { + $name = 'edd_settings[' . edd_sanitize_key( $args['id'] ) . ']'; + } + + $args['name'] = $name; + $args['class'] = edd_sanitize_html_class( $args['field_class'] ); + $args['current'] = ! empty( $edd_option ) ? $edd_option : ''; + $args['label'] = false; + $args['value'] = 1; + + $html = ''; + $html .= '
    '; + $html .= EDD()->html->checkbox( $args ); + $html .= ''; + $html .= '
    '; + if ( ! empty( $args['desc'] ) ) { + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + } + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Multicheck Callback + * + * Renders multiple checkboxes. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_multicheck_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $html = ''; + if ( ! empty( $args['options'] ) ) { + $html .= ''; + + foreach ( $args['options'] as $key => $option ): + if ( isset( $edd_option[ $key ] ) ) { + $enabled = $option; + } else { + $enabled = null; + } + $html .= '
    '; + $html .= ' '; + $html .= ''; + $html .= '
    '; + endforeach; + $html .= '

    ' . $args['desc'] . '

    '; + } + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Payment method icons callback + * + * @since 2.1 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_payment_icons_callback( $args = array() ) { + + // Start an output buffer + ob_start(); + + $edd_option = edd_get_option( $args['id'] ); + $class = edd_sanitize_html_class( $args['field_class'] ); ?> + + + + +
      + + $option ) : + $enabled = isset( $edd_option[ $key ] ) + ? $option + : null; ?> + +
    • + +
    • + + + +
    + +

    + + $option ) : + $checked = false; + + if ( $edd_options && $edd_options == $key ) { + $checked = true; + } elseif ( isset( $args['std'] ) && $args['std'] == $key && ! $edd_options ) { + $checked = true; + } + + $html .= '
    '; + $html .= ' '; + $html .= ''; + $html .= '
    '; + endforeach; + + $html .= '

    ' . apply_filters( 'edd_after_setting_output', wp_kses_post( $args['desc'] ), $args ) . '

    '; + + echo $html; +} + +/** + * Gateways Callback + * + * Renders gateways fields. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_gateways_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + $html = ''; + $html .= ''; + + if ( ! empty( $args['options'] ) ) { + $class = edd_sanitize_html_class( $args['field_class'] ); + $html .= '
      '; + + foreach ( $args['options'] as $key => $option ) { + if ( isset( $edd_option[ $key ] ) ) { + $enabled = '1'; + } else { + $enabled = null; + } + + $html .= '
    • '; + $html .= ''; + $html .= '
    • '; + } + + $html .= '
    '; + + $url = edd_link_helper( + 'https://easydigitaldownloads.com/downloads/category/extensions/gateways/', + array( + 'utm_medium' => 'payment-settings', + 'utm_content' => 'gateways', + ) + ); + + /* translators: 1. opening link tag; do not translate; 2. closing link tag; do not translate */ + $html .= '

    ' . esc_html__( 'Choose how you want to allow your customers to pay you.', 'easy-digital-downloads' ) . '
    ' . sprintf( __( 'More %1$sPayment Gateways%2$s are available.', 'easy-digital-downloads' ), '', '' ) . '

    '; + } + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Gateways Callback (drop down) + * + * Renders gateways select menu + * + * @since 1.5 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_gateway_select_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + $class = edd_sanitize_html_class( $args['field_class'] ); + if ( isset( $args['chosen'] ) ) { + $class .= ' edd-select-chosen'; + if ( is_rtl() ) { + $class .= ' chosen-rtl'; + } + } + + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Text Callback + * + * Renders text fields. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_text_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + $value = $edd_option; + } elseif ( ! empty( $args['allow_blank'] ) && empty( $edd_option ) ) { + $value = ''; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + if ( isset( $args['faux'] ) && true === $args['faux'] ) { + $args['readonly'] = true; + $value = isset( $args['std'] ) ? $args['std'] : ''; + $name = ''; + } else { + $name = 'name="edd_settings[' . esc_attr( $args['id'] ) . ']"'; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $placeholder = ! empty( $args['placeholder'] ) + ? ' placeholder="' . esc_attr( $args['placeholder'] ) . '"' + : ''; + + $disabled = ! empty( $args['disabled'] ) ? ' disabled="disabled"' : ''; + $readonly = $args['readonly'] === true ? ' readonly="readonly"' : ''; + $size = ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular'; + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Email Callback + * + * Renders email fields. + * + * @since 2.8 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_email_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + $value = $edd_option; + } elseif ( ! empty( $args['allow_blank'] ) && empty( $edd_option ) ) { + $value = ''; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + if ( isset( $args['faux'] ) && true === $args['faux'] ) { + $args['readonly'] = true; + $value = isset( $args['std'] ) ? $args['std'] : ''; + $name = ''; + } else { + $name = 'name="edd_settings[' . esc_attr( $args['id'] ) . ']"'; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $placeholder = isset( $args['placeholder'] ) + ? $args['placeholder'] + : ''; + + $disabled = ! empty( $args['disabled'] ) ? ' disabled="disabled"' : ''; + $readonly = $args['readonly'] === true ? ' readonly="readonly"' : ''; + $size = ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular'; + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Number Callback + * + * Renders number fields. + * + * @since 1.9 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_number_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( is_numeric( $edd_option ) ) { + $value = $edd_option; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + if ( isset( $args['faux'] ) && true === $args['faux'] ) { + $args['readonly'] = true; + $value = isset( $args['std'] ) ? $args['std'] : ''; + $name = ''; + } else { + $name = 'name="edd_settings[' . esc_attr( $args['id'] ) . ']"'; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $max = isset( $args['max'] ) ? $args['max'] : 999999; + $min = isset( $args['min'] ) ? $args['min'] : 0; + $step = isset( $args['step'] ) ? $args['step'] : 1; + + $readonly = ! empty( $args['readonly'] ) ? ' readonly' : ''; + $disabled = ! empty( $args['disabled'] ) ? ' disabled="disabled"' : ''; + + $size = ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular'; + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Textarea Callback + * + * Renders textarea fields. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_textarea_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + if ( is_array( $edd_option ) ) { + $value = implode( "\n", maybe_unserialize( $edd_option ) ); + } else { + $value = $edd_option; + } + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + $placeholder = ! empty( $args['placeholder'] ) + ? ' placeholder="' . esc_attr( $args['placeholder'] ) . '"' + : ''; + + $readonly = $args['readonly'] === true ? ' readonly="readonly"' : ''; + + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Password Callback + * + * Renders password fields. + * + * @since 1.3 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_password_callback( $args ) { + $edd_options = edd_get_option( $args['id'] ); + + if ( $edd_options ) { + $value = $edd_options; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $size = ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular'; + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Missing Callback + * + * If a function is missing for settings callbacks alert the user. + * + * @since 1.3.1 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_missing_callback( $args ) { + printf( + wp_kses_post( + /* translators: %s: the setting ID */ + __( 'The callback function used for the %s setting is missing.', 'easy-digital-downloads' ) + ), + '' . esc_attr( $args['id'] ) . '' + ); +} + +/** + * Select Callback + * + * Renders select fields. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_select_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + $value = $edd_option; + } else { + + // Properly set default fallback if the Select Field allows Multiple values + if ( empty( $args['multiple'] ) ) { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } else { + $value = ! empty( $args['std'] ) ? $args['std'] : array(); + } + + } + + $placeholder = isset( $args['placeholder'] ) + ? $args['placeholder'] + : ''; + + $class = edd_sanitize_html_class( $args['field_class'] ); + + if ( isset( $args['chosen'] ) ) { + $class .= ' edd-select-chosen'; + if ( is_rtl() ) { + $class .= ' chosen-rtl'; + } + } + + // Nonce + $nonce = isset( $args['data']['nonce'] ) + ? ' data-nonce="' . sanitize_text_field( $args['data']['nonce'] ) . '"' + : ''; + + // If the Select Field allows Multiple values, save as an Array + $name_attr = 'edd_settings[' . esc_attr( $args['id'] ) . ']'; + $name_attr = ( $args['multiple'] ) ? $name_attr . '[]' : $name_attr; + + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Color select Callback + * + * Renders color select fields. + * + * @since 1.8 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_color_select_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + $value = $edd_option; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + if ( $args['chosen'] ) { + $class .= 'edd-select-chosen'; + if ( is_rtl() ) { + $class .= ' chosen-rtl'; + } + } + + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Rich Editor Callback + * + * Renders rich editor fields. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + */ +function edd_rich_editor_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + $value = $edd_option; + } else { + if ( ! empty( $args['allow_blank'] ) && empty( $edd_option ) ) { + $value = ''; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + } + + $rows = isset( $args['size'] ) ? $args['size'] : 20; + + $class = edd_sanitize_html_class( $args['field_class'] ); + + ob_start(); + + wp_editor( stripslashes( $value ), 'edd_settings_' . esc_attr( $args['id'] ), array( + 'textarea_name' => 'edd_settings[' . esc_attr( $args['id'] ) . ']', + 'textarea_rows' => absint( $rows ), + 'editor_class' => $class, + ) ); + + if ( ! empty( $args['desc'] ) ) { + echo '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + } + + $html = ob_get_clean(); + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Upload Callback + * + * Renders upload fields. + * + * @since 1.0 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_upload_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + $value = $edd_option; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $size = ( isset( $args['size'] ) && ! is_null( $args['size'] ) ) ? $args['size'] : 'regular'; + $html = '
    '; + $html .= ''; + $html .= ''; + $html .= '
    '; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Color picker Callback + * + * Renders color picker fields. + * + * @since 1.6 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_color_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + + if ( $edd_option ) { + $value = $edd_option; + } else { + $value = isset( $args['std'] ) ? $args['std'] : ''; + } + + $default = isset( $args['std'] ) ? $args['std'] : ''; + + $class = edd_sanitize_html_class( $args['field_class'] ); + + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Shop States Callback + * + * Renders states drop down based on the currently selected country + * + * @since 1.6 + * + * @param array $args Arguments passed by the setting + * + * @return void + */ +function edd_shop_states_callback( $args ) { + $edd_option = edd_get_option( $args['id'] ); + $states = edd_get_shop_states(); + $class = edd_sanitize_html_class( $args['field_class'] ); + $placeholder = isset( $args['placeholder'] ) + ? $args['placeholder'] + : ''; + + if ( $args['chosen'] ) { + $class .= ' edd-select-chosen'; + if ( is_rtl() ) { + $class .= ' chosen-rtl'; + } + } + + if ( empty( $states ) ) { + $class .= ' edd-no-states'; + } + + $html = ''; + $html .= '

    ' . wp_kses_post( $args['desc'] ) . '

    '; + + echo apply_filters( 'edd_after_setting_output', $html, $args ); +} + +/** + * Outputs the "Default Rate" setting. + * + * @since 3.0 + * + * @param array $args Arguments passed to the setting. + */ +function edd_tax_rate_callback( $args ) { + echo ''; + echo wp_kses_post( $args['desc'] ); +} + +/** + * Recapture Callback + * + * Renders Recapture Settings + * + * @since 2.10.2 + * @param array $args Arguments passed by the setting + * @return void + */ +function edd_recapture_callback($args) { + $client_connected = false; + + if ( class_exists( 'RecaptureEDD' ) ) { + $client_connected = RecaptureEDD::is_ready(); + } + + ob_start(); + + echo $args['desc']; + + // Output the appropriate button and label based on connection status + if ( $client_connected ) : + $connection_complete = get_option( 'recapture_api_key' ); + ?> +
    +

    + + ', + '' + ); + ?> +

    + + +

    + +

    + +

    + ', + '' + ); + ?> +

    + +
    + +

    + ', + '' + ); + ?> +

    + +

    + +

    + + + $rates, + 'nonce' => wp_create_nonce( 'edd-country-field-nonce' ), + 'i18n' => array( + /* translators: Tax rate country code */ + 'duplicateRate' => esc_html__( 'Duplicate tax rates are not allowed. Please deactivate the existing %s tax rate before adding or activating another.', 'easy-digital-downloads' ), + 'emptyCountry' => esc_html__( 'Please select a country.', 'easy-digital-downloads' ), + 'negativeTax' => esc_html__( 'Please enter a tax rate greater than 0.', 'easy-digital-downloads' ), + 'emptyTax' => esc_html__( 'Are you sure you want to add a 0% tax rate?', 'easy-digital-downloads' ), + ), + ) ); + + $templates = array( + 'meta', + 'row', + 'row-empty', + 'add', + 'bulk-actions' + ); + + echo '

    ' . $args['desc'] . '

    '; + + echo '
    '; + + foreach ( $templates as $tmpl ) { +?> + + + +' . esc_html( $args['tooltip_title'] ) . '
    : ' . esc_html( $args['tooltip_desc'] ) . '">'; + $has_p_tag = strstr( $html, '

    ' ); + $has_label = strstr( $html, '' ); + + // Insert tooltip at end of paragraph + if ( false !== $has_p_tag ) { + $html = str_replace( '

    ', $tooltip . '

    ', $html ); + + // Insert tooltip at end of label + } elseif ( false !== $has_label ) { + $html = str_replace( '', '' . $tooltip, $html ); + + // Append tooltip to end of HTML + } else { + $html .= $tooltip; + } + } + + return $html; +} +add_filter( 'edd_after_setting_output', 'edd_add_setting_tooltip', 10, 2 ); + +/** + * Filters the edd_get_option call for test_mode. + * + * This allows us to ensure that calls directly to edd_get_option respect the constant + * in addition to the edd_is_test_mode() function call and included filter. + * + * @since 3.1 + * + * @param bool $value If test_mode is enabled in the settings. + * @param string $key The key of the setting, should be test_mode. + * @param bool $default The default setting, which is 'false' for test_mode. + */ +function edd_filter_test_mode_option( $value, $key, $default ) { + if ( edd_is_test_mode_forced() ) { + $value = true; + } + + return $value; +} +add_filter( 'edd_get_option_test_mode', 'edd_filter_test_mode_option', 10, 3 ); + +/** + * Determine if test mode is being forced to true. + * + * Using the EDD_TEST_MODE and the edd_is_test_mode filter, determine if the value of true + * is being forced for test_mode so we can properly alter the setting for it. + * + * @since 3.1 + * + * @return bool If test_mode is being forced or not. + */ +function edd_is_test_mode_forced() { + if ( defined( 'EDD_TEST_MODE' ) && true === EDD_TEST_MODE ) { + return true; + } + + if ( false !== has_filter( 'edd_is_test_mode', '__return_true' ) ) { + return true; + } + + return false; +} + +/** + * Checks for an incorrect setting for the privacy policy. + * Required in updating from EDD 2.9.2 to 2.9.3. + */ +add_filter( 'edd_get_option_show_privacy_policy_on_checkout', function( $value ) { + if ( ! empty( $value ) ) { + return $value; + } + $fix_show_privacy_policy_setting = edd_get_option( 'show_agree_to_privacy_policy_on_checkout', false ); + if ( ! empty( $fix_show_privacy_policy_setting ) ) { + edd_update_option( 'show_privacy_policy_on_checkout', $fix_show_privacy_policy_setting ); + edd_delete_option( 'show_agree_to_privacy_policy_on_checkout' ); + + return $fix_show_privacy_policy_setting; + } + + return $value; +}, 10, 3 ); diff --git a/includes/admin/settings/settings-compatibility.php b/includes/admin/settings/settings-compatibility.php new file mode 100644 index 00000000000..6d9df044cbe --- /dev/null +++ b/includes/admin/settings/settings-compatibility.php @@ -0,0 +1,164 @@ + -
    - - - -
    - - - -
    - - - -
    -
    - -
    - '; - $output = '' . $img . ''; + * @since 1.0 + * @return string "Insert Download" Button + */ +function edd_media_button() { + + // Bail if not a post new/edit screen + if ( ! edd_is_insertable_admin_page() ) { + return; } - return $context . $output; -} -add_filter('media_buttons_context', 'edd_media_button'); + // Setup the icon + $icon = ''; + /* translators: singular download label */ + $text = sprintf( __( 'Insert %s', 'easy-digital-downloads' ), edd_get_label_singular() ); + + // Output the thickbox button + echo '' . $icon . esc_html( $text ) . ''; +} +add_action( 'media_buttons', 'edd_media_button', 11 ); /** * Admin Footer For Thickbox * - * Prints the footer code needed for the Insert Download + * Prints the footer code needed for the Insert Download * TinyMCE button. * - * @access private - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @global $pagenow + * @global $typenow + * @return void + */ function edd_admin_footer_for_thickbox() { - global $pagenow, $typenow; - - // Only run in post/page creation and edit screens - if ( in_array( $pagenow, array( 'post.php', 'page.php', 'post-new.php', 'post-edit.php' ) ) && $typenow != 'download' ) { - $downloads = get_posts(array('post_type' => 'download', 'posts_per_page' => -1)); - - ?> - - -
  • ' . $title . ' '; - $item .= '- ' . edd_currency_filter( edd_format_amount( edd_get_cart_item_price($id, $options) ) ) . ' '; - $item .= '- ' . $remove . '
  • '; - return apply_filters('edd_cart_item', $item, $id); -} - - -/** - * Empty Cart Message - * - * Gets the message for an empty cart. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_empty_cart_message() { - return apply_filters('edd_empty_cart_message', __('Your cart is empty.', 'edd')); -} - - -/** - * Empty Checkout Cart - * - * @access private - * @since 1.0 - * @return string -*/ - -function edd_empty_checkout_cart() { - echo edd_empty_cart_message(); -} -add_action('edd_empty_cart', 'edd_empty_checkout_cart'); \ No newline at end of file diff --git a/includes/cart/actions.php b/includes/cart/actions.php new file mode 100755 index 00000000000..19c34896a71 --- /dev/null +++ b/includes/cart/actions.php @@ -0,0 +1,193 @@ +query_vars['edd-add'] ) ) { + $download_id = absint( $wp_query->query_vars['edd-add'] ); + $cart = edd_add_to_cart( $download_id, array() ); + + edd_redirect( edd_get_checkout_uri() ); + } + + // Removes an item from the cart with a /edd-remove/# URL. + if ( isset( $wp_query->query_vars['edd-remove'] ) ) { + $cart_key = absint( $wp_query->query_vars['edd-remove'] ); + $cart = edd_remove_from_cart( $cart_key ); + + edd_redirect( edd_get_checkout_uri() ); + } +} +add_action( 'template_redirect', 'edd_process_cart_endpoints', 100 ); + +/** + * Process the 'Add to Cart' request. + * + * @since 1.0 + * + * @param array $data + */ +function edd_process_add_to_cart( $data ) { + $download_id = ! empty( $data['download_id'] ) ? absint( $data['download_id'] ) : false; + $options = isset( $data['edd_options'] ) ? (array) $data['edd_options'] : array(); + + if ( ! empty( $data['edd_download_quantity'] ) ) { + $options['quantity'] = absint( $data['edd_download_quantity'] ); + } + + if ( isset( $options['price_id'] ) && is_array( $options['price_id'] ) ) { + foreach ( $options['price_id'] as $key => $price_id ) { + $options['quantity'][ $key ] = isset( $data[ 'edd_download_quantity_' . $price_id ] ) ? absint( $data[ 'edd_download_quantity_' . $price_id ] ) : 1; + } + } + + if ( ! empty( $download_id ) ) { + edd_add_to_cart( $download_id, $options ); + } + + if ( edd_straight_to_checkout() && ! edd_is_checkout() ) { + $query_args = remove_query_arg( array( 'edd_action', 'download_id', 'edd_options' ) ); + $query_part = strpos( $query_args, "?" ); + $url_parameters = ''; + + if ( false !== $query_part ) { + $url_parameters = substr( $query_args, $query_part ); + } + + edd_redirect( edd_get_checkout_uri() . $url_parameters, 303 ); + } else { + edd_redirect( remove_query_arg( array( 'edd_action', 'download_id', 'edd_options' ) ) ); + } +} +add_action( 'edd_add_to_cart', 'edd_process_add_to_cart' ); + +/** + * Process the 'Remove from Cart' request. + * + * @since 1.0 + * + * @param $data + */ +function edd_process_remove_from_cart( $data ) { + $cart_key = absint( $_GET['cart_item'] ); + + if ( ! isset( $_GET['edd_remove_from_cart_nonce'] ) ) { + edd_debug_log( __( 'Missing nonce when removing an item from the cart. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4', 'easy-digital-downloads' ), true ); + } + + $nonce = ! empty( $_GET['edd_remove_from_cart_nonce'] ) + ? sanitize_text_field( $_GET['edd_remove_from_cart_nonce'] ) + : ''; + + $nonce_verified = wp_verify_nonce( $nonce, 'edd-remove-from-cart-' . $cart_key ); + if ( false !== $nonce_verified ) { + edd_remove_from_cart( $cart_key ); + } + + edd_redirect( remove_query_arg( array( 'edd_action', 'cart_item', 'nocache' ) ) ); +} +add_action( 'edd_remove', 'edd_process_remove_from_cart' ); + +/** + * Process the Remove fee from Cart request. + * + * @since 2.0 + * + * @param $data + */ +function edd_process_remove_fee_from_cart( $data ) { + $fee = sanitize_text_field( $data['fee'] ); + EDD()->fees->remove_fee( $fee ); + edd_redirect( remove_query_arg( array( 'edd_action', 'fee', 'nocache' ) ) ); +} +add_action( 'edd_remove_fee', 'edd_process_remove_fee_from_cart' ); + +/** + * Process the Collection Purchase request. + * + * @since 1.0 + * + * @param $data + */ +function edd_process_collection_purchase( $data ) { + $taxonomy = urldecode( $data['taxonomy'] ); + $terms = urldecode( $data['terms'] ); + + edd_add_collection_to_cart( $taxonomy, $terms ); + edd_redirect( add_query_arg( 'added', '1', remove_query_arg( array( 'edd_action', 'taxonomy', 'terms' ) ) ) ); +} +add_action( 'edd_purchase_collection', 'edd_process_collection_purchase' ); + +/** + * Process cart updates, primarily for quantities. + * + * @since 1.7 + */ +function edd_process_cart_update( $data ) { + if ( ! empty( $data['edd-cart-downloads'] ) && is_array( $data['edd-cart-downloads'] ) ) { + foreach ( $data['edd-cart-downloads'] as $key => $cart_download_id ) { + $options = json_decode( stripslashes( $data[ 'edd-cart-download-' . $key . '-options' ] ), true ); + $quantity = absint( $data[ 'edd-cart-download-' . $key . '-quantity' ] ); + edd_set_cart_item_quantity( $cart_download_id, $quantity, $options ); + } + } +} +add_action( 'edd_update_cart', 'edd_process_cart_update' ); + +/** + * Process cart save. + * + * @since 1.8 + */ +function edd_process_cart_save( $data ) { + $cart = edd_save_cart(); + + if ( ! $cart ) { + edd_redirect( edd_get_checkout_uri() ); + } +} +add_action( 'edd_save_cart', 'edd_process_cart_save' ); + +/** + * Process cart save + * + * @since 1.8 + * @return void + */ +function edd_process_cart_restore( $data ) { + $cart = edd_restore_cart(); + + if ( ! is_wp_error( $cart ) ) { + edd_redirect( edd_get_checkout_uri() ); + } +} +add_action( 'edd_restore_cart', 'edd_process_cart_restore' ); diff --git a/includes/cart/class-edd-cart.php b/includes/cart/class-edd-cart.php new file mode 100644 index 00000000000..abb98474a4a --- /dev/null +++ b/includes/cart/class-edd-cart.php @@ -0,0 +1,1515 @@ +get_contents_from_session(); + $this->get_contents(); + $this->get_contents_details(); + $this->get_all_fees(); + $this->get_discounts_from_session(); + $this->get_quantity(); + } + + /** + * Retrieves the tax rate. + * + * This sets up the tax rate once so we don't have to recaculate it each time we need it. + * + * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/8455 + * + * @since 3.0 + * @return float + */ + private function get_tax_rate() { + if ( null === $this->tax_rate ) { + $this->tax_rate = edd_use_taxes() ? edd_get_tax_rate() : 0; + } + + return $this->tax_rate; + } + + /** + * Sets the tax rate. + * + * @param float $tax_rate + * + * @since 3.0 + */ + public function set_tax_rate( $tax_rate ) { + $this->tax_rate = $tax_rate; + } + + /** + * Populate the cart with the data stored in the session + * + * @since 2.7 + * @return void + */ + public function get_contents_from_session() { + $cart = EDD()->session->get( 'edd_cart' ); + $this->contents = $cart; + + do_action( 'edd_cart_contents_loaded_from_session', $this ); + } + + /** + * Populate the discounts with the data stored in the session. + * + * @since 2.7 + * @return void + */ + public function get_discounts_from_session() { + $discounts = EDD()->session->get( 'cart_discounts' ); + $this->discounts = $discounts; + + do_action( 'edd_cart_discounts_loaded_from_session', $this ); + } + + /** + * Get cart contents + * + * @since 2.7 + * @return array List of cart contents. + */ + public function get_contents() { + if ( ! did_action( 'edd_cart_contents_loaded_from_session' ) ) { + $this->get_contents_from_session(); + } + + $cart = is_array( $this->contents ) && ! empty( $this->contents ) ? array_values( $this->contents ) : array(); + $cart_count = count( $cart ); + + foreach ( $cart as $key => $item ) { + $download = new EDD_Download( $item['id'] ); + + // If the item is not a download or it's status has changed since it was added to the cart. + if ( empty( $download->ID ) || ! $download->can_purchase() ) { + unset( $cart[ $key ] ); + } + } + + // We've removed items, reset the cart session + if ( count( $cart ) < $cart_count ) { + $this->contents = $cart; + $this->update_cart(); + } + + $this->contents = apply_filters( 'edd_cart_contents', $cart ); + + do_action( 'edd_cart_contents_loaded' ); + + return (array) $this->contents; + } + + /** + * Get cart contents details + * + * @since 2.7 + * @return array + */ + public function get_contents_details() { + global $edd_is_last_cart_item, $edd_flat_discount_total; + + if ( empty( $this->contents ) ) { + return array(); + } + + $details = array(); + $length = count( $this->contents ) - 1; + + foreach ( $this->contents as $key => $item ) { + if( $key >= $length ) { + $edd_is_last_cart_item = true; + } + + $item['quantity'] = edd_item_quantities_enabled() ? absint( $item['quantity'] ) : 1; + $item['quantity'] = max( 1, $item['quantity'] ); // Force quantity to 1 + + $options = isset( $item['options'] ) ? $item['options'] : array(); + + $price_id = isset( $options['price_id'] ) ? $options['price_id'] : null; + + $item_price = $this->get_item_price( $item['id'], $options ); + $discount = $this->get_item_discount_amount( $item ); + $discount = apply_filters( 'edd_get_cart_content_details_item_discount_amount', $discount, $item ); + $quantity = $this->get_item_quantity( $item['id'], $options ); + $fees = $this->get_fees( 'fee', $item['id'], $price_id ); + $subtotal = floatval( $item_price ) * $quantity; + + // Subtotal for tax calculation must exclude fees that are greater than 0. See $this->get_tax_on_fees() + $subtotal_for_tax = $subtotal; + + foreach ( $fees as $fee ) { + + $fee_amount = (float) $fee['amount']; + $subtotal += $fee_amount; + + if( $fee_amount > 0 ) { + continue; + } + + $subtotal_for_tax += $fee_amount; + } + + $tax = $this->get_item_tax( $item['id'], $options, $subtotal_for_tax - $discount ); + + if ( edd_prices_include_tax() ) { + $subtotal -= round( $tax, edd_currency_decimal_filter() ); + } + + $total = $subtotal - $discount + $tax; + + if ( $total < 0 ) { + $total = 0; + } + + $details[ $key ] = array( + 'name' => get_the_title( $item['id'] ), + 'id' => $item['id'], + 'item_number' => $item, + 'item_price' => round( $item_price, edd_currency_decimal_filter() ), + 'quantity' => $quantity, + 'discount' => round( $discount, edd_currency_decimal_filter() ), + 'subtotal' => round( $subtotal, edd_currency_decimal_filter() ), + 'tax' => round( $tax, edd_currency_decimal_filter() ), + 'fees' => $fees, + 'price' => round( $total, edd_currency_decimal_filter() ) + ); + + if ( $edd_is_last_cart_item ) { + $edd_is_last_cart_item = false; + $edd_flat_discount_total = 0.00; + } + } + + $this->details = $details; + + return $this->details; + } + + /** + * Get Discounts. + * + * @since 2.7 + * @return array $discounts The active discount codes + */ + public function get_discounts() { + $this->get_discounts_from_session(); + $this->discounts = ! empty( $this->discounts ) ? explode( '|', $this->discounts ) : array(); + return $this->discounts; + } + + /** + * Update Cart + * + * @since 2.7 + * @return void + */ + public function update_cart() { + EDD()->session->set( 'edd_cart', $this->contents ); + } + + /** + * Checks if any discounts have been applied to the cart + * + * @since 2.7 + * @return bool + */ + public function has_discounts() { + if ( null !== $this->has_discounts ) { + return $this->has_discounts; + } + + $has_discounts = false; + + $discounts = $this->get_discounts(); + if ( ! empty( $discounts ) ) { + $has_discounts = true; + } + + $this->has_discounts = apply_filters( 'edd_cart_has_discounts', $has_discounts ); + + return $this->has_discounts; + } + + /** + * Get quantity + * + * @since 2.7 + * @return int + */ + public function get_quantity() { + $total_quantity = 0; + + $contents = $this->get_contents(); + if ( ! empty( $contents ) ) { + $quantities = wp_list_pluck( $this->contents, 'quantity' ); + $total_quantity = absint( array_sum( $quantities ) ); + } + + $this->quantity = apply_filters( 'edd_get_cart_quantity', $total_quantity, $this->contents ); + return $this->quantity; + } + + /** + * Checks if the cart is empty + * + * @since 2.7 + * @return boolean + */ + public function is_empty() { + return 0 === count( (array) $this->get_contents() ); + } + + /** + * Add to cart + * + * As of EDD 2.7, items can only be added to the cart when the object passed extends EDD_Cart_Item + * + * @since 2.7 + * @return array $cart Updated cart object + */ + public function add( $download_id, $options = array() ) { + $download = new EDD_Download( $download_id ); + + if ( empty( $download->ID ) ) { + return; // Not a download product + } + + if ( ! $download->can_purchase() ) { + return; // Do not allow draft/pending to be purchased if can't edit. Fixes #1056 + } + + do_action( 'edd_pre_add_to_cart', $download_id, $options ); + + /** + * Pre-Add to Cart Contents. + * + * Prior to adding the new item to the cart, allow filtering of the current contents + * + * @since + * @since 3.0 Added the additional $download_id and $options arguments. + * + * @param array The current cart contents. + * @param int The download ID being added to the cart. + * @param array The options for the item being added including but not limited to quantity. + */ + $this->contents = apply_filters( 'edd_pre_add_to_cart_contents', $this->contents, $download_id, $options ); + + $quantities_enabled = edd_item_quantities_enabled() && ! edd_download_quantities_disabled( $download_id ); + + if ( $download->has_variable_prices() && ! isset( $options['price_id'] ) ) { + // Forces to the default price ID if none is specified and download has variable prices + $options['price_id'] = $download->get_default_price_id(); + } + + if ( isset( $options['quantity'] ) ) { + if ( is_array( $options['quantity'] ) ) { + $quantity = array(); + foreach ( $options['quantity'] as $q ) { + $quantity[] = $quantities_enabled ? absint( preg_replace( '/[^0-9\.]/', '', $q ) ) : 1; + } + } else { + $quantity = $quantities_enabled ? absint( preg_replace( '/[^0-9\.]/', '', $options['quantity'] ) ) : 1; + } + + unset( $options['quantity'] ); + } else { + $quantity = 1; + } + + // If the price IDs are a string and is a coma separated list, make it an array (allows custom add to cart URLs) + if ( isset( $options['price_id'] ) && ! is_array( $options['price_id'] ) && false !== strpos( $options['price_id'], ',' ) ) { + $options['price_id'] = explode( ',', $options['price_id'] ); + } + + $items = array(); + + if ( isset( $options['price_id'] ) && is_array( $options['price_id'] ) ) { + // Process multiple price options at once + foreach ( $options['price_id'] as $key => $price ) { + $items[] = array( + 'id' => $download_id, + 'options' => array( + 'price_id' => preg_replace( '/[^0-9\.-]/', '', $price ) + ), + 'quantity' => is_array( $quantity ) && isset( $quantity[ $key ] ) ? $quantity[ $key ] : $quantity, + ); + } + } else { + // Sanitize price IDs + foreach( $options as $key => $option ) { + if ( 'price_id' == $key ) { + $options[ $key ] = preg_replace( '/[^0-9\.-]/', '', $option ); + } + } + + // Add a single item + $items[] = array( + 'id' => $download_id, + 'options' => $options, + 'quantity' => $quantity + ); + } + + foreach ( $items as &$item ) { + $item = apply_filters( 'edd_add_to_cart_item', $item ); + $to_add = $item; + + if ( ! is_array( $to_add ) ) { + return; + } + + if ( ! isset( $to_add['id'] ) || empty( $to_add['id'] ) ) { + return; + } + + if ( edd_item_in_cart( $to_add['id'], $to_add['options'] ) && edd_item_quantities_enabled() ) { + $key = edd_get_item_position_in_cart( $to_add['id'], $to_add['options'] ); + + if ( is_array( $quantity ) ) { + $this->contents[ $key ]['quantity'] += $quantity[ $key ]; + } else { + $this->contents[ $key ]['quantity'] += $quantity; + } + } else { + $this->contents[] = $to_add; + } + } + + unset( $item ); + + $this->update_cart(); + + do_action( 'edd_post_add_to_cart', $download_id, $options, $items ); + + // Clear all the checkout errors, if any + edd_clear_errors(); + + return count( $this->contents ) - 1; + } + + /** + * Remove from cart + * + * @since 2.7 + * + * @param int $key Cart key to remove. This key is the numerical index of the item contained within the cart array. + * @return array Updated cart contents + */ + public function remove( $key ) { + $cart = $this->get_contents(); + + do_action( 'edd_pre_remove_from_cart', $key ); + + if ( ! is_array( $cart ) ) { + return true; // Empty cart + } else { + $item_id = isset( $cart[ $key ]['id'] ) ? $cart[ $key ]['id'] : null; + unset( $cart[ $key ] ); + } + + $this->contents = $cart; + $this->update_cart(); + + do_action( 'edd_post_remove_from_cart', $key, $item_id ); + + edd_clear_errors(); + + return $this->contents; + } + + /** + * Generate the URL to remove an item from the cart. + * + * @since 2.7 + * + * @param int $cart_key Cart item key + * @return string $remove_url URL to remove the cart item + */ + public function remove_item_url( $cart_key ) { + + $current_page = edd_doing_ajax() + ? edd_get_checkout_uri() + : edd_get_current_page_url(); + + $remove_url = edd_add_cache_busting( add_query_arg( array( + 'cart_item' => urlencode( $cart_key ), + 'edd_action' => 'remove', + ), $current_page ) ); + + return apply_filters( 'edd_remove_item_url', $remove_url ); + } + + /** + * Generate the URL to remove a fee from the cart. + * + * @since 2.7 + * + * @param int $fee_id Fee ID. + * @return string $remove_url URL to remove the cart item + */ + public function remove_fee_url( $fee_id = '' ) { + + $current_page = edd_doing_ajax() + ? edd_get_checkout_uri() + : edd_get_current_page_url(); + + $remove_url = add_query_arg( array( + 'fee' => urlencode( $fee_id ), + 'edd_action' => 'remove_fee', + 'nocache' => 'true' + ), $current_page ); + + return apply_filters( 'edd_remove_fee_url', $remove_url ); + } + + /** + * Empty the cart + * + * @since 2.7 + * @return void + */ + public function empty_cart() { + + // Remove cart contents. + EDD()->session->set( 'edd_cart', NULL ); + + // Remove all cart fees. + EDD()->session->set( 'edd_cart_fees', NULL ); + + // Remove any resuming payments. + EDD()->session->set( 'edd_resume_payment', NULL ); + + // Remove any active discounts + $this->remove_all_discounts(); + $this->contents = array(); + + do_action( 'edd_empty_cart' ); + } + + /** + * Remove discount from the cart + * + * @since 2.7 + * @return array Discount codes + */ + public function remove_discount( $code = '' ) { + if ( empty( $code ) ) { + return; + } + + if ( $this->discounts ) { + $key = array_search( $code, $this->discounts ); + + if ( false !== $key ) { + unset( $this->discounts[ $key ] ); + } + + $this->discounts = implode( '|', array_values( $this->discounts ) ); + + // update the active discounts + EDD()->session->set( 'cart_discounts', $this->discounts ); + } + + do_action( 'edd_cart_discount_removed', $code, $this->discounts ); + do_action( 'edd_cart_discounts_updated', $this->discounts ); + + return $this->discounts; + } + + /** + * Remove all discount codes + * + * @since 2.7 + * @return void + */ + public function remove_all_discounts() { + EDD()->session->set( 'cart_discounts', null ); + do_action( 'edd_cart_discounts_removed' ); + } + + /** + * Get the discounted amount on a price + * + * @since 2.7 + * @since 3.0 Use `edd_get_item_discount_amount()` for calculations. + * + * @param array $item Cart item. + * @param bool|string $discount False to use the cart discounts or a string to check with a discount code. + * @return float The discounted amount + */ + public function get_item_discount_amount( $item = array(), $discount = false ) { + // Validate item. + if ( empty( $item ) || empty( $item['id'] ) ) { + return 0; + } + + if ( ! isset( $item['quantity'] ) ) { + return 0; + } + + if ( ! isset( $item['options'] ) ) { + $item['options'] = array(); + + /* + * Support for variable pricing when calling `edd_get_cart_item_discount_amount()` + * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/8246 + */ + if ( isset( $item['item_number']['options'] ) ) { + $item['options'] = $item['item_number']['options']; + } + } + + $discounts = false === $discount + ? $this->get_discounts() + : array( $discount ); + + $item_price = $this->get_item_price( $item['id'], $item['options'] ); + $discount_amount = edd_get_item_discount_amount( $item, $this->get_contents(), $discounts, $item_price ); + + $discounted_amount = ( $item_price - $discount_amount ); + + /** + * Filters the amount to be discounted from the original cart item amount. + * + * @since unknown + * + * @param float $discounted_amount Amount to be discounted from the cart item amount. + * @param string[] $discounts Discount codes applied to the Cart. + * @param array $item Cart item. + * @param float $item_price Cart item price. + */ + $discounted_amount = apply_filters( + 'edd_get_cart_item_discounted_amount', + $discounted_amount, + $discounts, + $item, + $item_price + ); + + // Recalculate using the legacy filter discounted amount. + $discount_amount = round( ( $item_price - $discounted_amount ), edd_currency_decimal_filter() ); + + return $discount_amount; + } + + /** + * Shows the fully formatted cart discount + * + * @since 2.7 + * + * @param bool $echo Echo? + * @return string $amount Fully formatted cart discount + */ + public function display_cart_discount( $echo = false ) { + $discounts = $this->get_discounts(); + + if ( empty( $discounts ) ) { + return false; + } + + $discount_id = edd_get_discount_id_by_code( $discounts[0] ); + $amount = edd_format_discount_rate( edd_get_discount_type( $discount_id ), edd_get_discount_amount( $discount_id ) ); + + if ( $echo ) { + echo esc_html( $amount ); + } + + return $amount; + } + + /** + * Checks to see if an item is in the cart. + * + * @since 2.7 + * + * @param int $download_id Download ID of the item to check. + * @param array $options + * @return bool + */ + public function is_item_in_cart( $download_id = 0, $options = array() ) { + $cart = $this->get_contents(); + + $ret = false; + + if ( is_array( $cart ) ) { + foreach ( $cart as $item ) { + if ( $item['id'] == $download_id ) { + if ( isset( $options['price_id'] ) && isset( $item['options']['price_id'] ) ) { + if ( $options['price_id'] == $item['options']['price_id'] ) { + $ret = true; + break; + } + } else { + $ret = true; + break; + } + } + } + } + + return (bool) apply_filters( 'edd_item_in_cart', $ret, $download_id, $options ); + } + + /** + * Get the position of an item in the cart + * + * @since 2.7 + * + * @param int $download_id Download ID of the item to check. + * @param array $options + * @return mixed int|false + */ + public function get_item_position( $download_id = 0, $options = array() ) { + $cart = $this->get_contents(); + + if ( ! is_array( $cart ) ) { + return false; + } else { + foreach ( $cart as $position => $item ) { + if ( $item['id'] == $download_id ) { + if ( isset( $options['price_id'] ) && isset( $item['options']['price_id'] ) ) { + if ( (int) $options['price_id'] == (int) $item['options']['price_id'] ) { + return $position; + } + } else { + return $position; + } + } + } + } + + return false; + } + + /** + * Get the quantity of an item in the cart. + * + * @since 2.7 + * + * @param int $download_id Download ID of the item + * @param array $options + * @return int Numerical index of the position of the item in the cart + */ + public function get_item_quantity( $download_id = 0, $options = array() ) { + $key = $this->get_item_position( $download_id, $options ); + + $quantity = isset( $this->contents[ $key ]['quantity'] ) && edd_item_quantities_enabled() ? $this->contents[ $key ]['quantity'] : 1; + + if ( $quantity < 1 ) { + $quantity = 1; + } + + return absint( apply_filters( 'edd_get_cart_item_quantity', $quantity, $download_id, $options ) ); + } + + /** + * Set the quantity of an item in the cart. + * + * @since 2.7 + * + * @param int $download_id Download ID of the item + * @param int $quantity Updated quantity of the item + * @param array $options + * @return array $contents Updated cart object. + */ + public function set_item_quantity( $download_id = 0, $quantity = 1, $options = array() ) { + $key = $this->get_item_position( $download_id, $options ); + + if ( false === $key ) { + return $this->contents; + } + + if ( $quantity < 1 ) { + $quantity = 1; + } + + $this->contents[ $key ]['quantity'] = $quantity; + $this->update_cart(); + + do_action( 'edd_after_set_cart_item_quantity', $download_id, $quantity, $options, $this->contents ); + + return $this->contents; + } + + /** + * Cart Item Price. + * + * @since 2.7 + * + * @param int $item_id Download (cart item) ID number + * @param array $options Optional parameters, used for defining variable prices + * @return string Fully formatted price + */ + public function item_price( $item_id = 0, $options = array() ) { + $price = $this->get_item_price( $item_id, $options ); + $label = ''; + + $price_id = isset( $options['price_id'] ) ? $options['price_id'] : false; + + if ( ! edd_is_free_download( $item_id, $price_id ) && ! edd_download_is_tax_exclusive( $item_id ) ) { + if ( edd_prices_show_tax_on_checkout() && ! edd_prices_include_tax() ) { + $price += edd_get_cart_item_tax( $item_id, $options, $price ); + } + + if ( ! edd_prices_show_tax_on_checkout() && edd_prices_include_tax() ) { + $price -= edd_get_cart_item_tax( $item_id, $options, $price ); + } + + if ( edd_display_tax_rate() ) { + $label = ' – '; + + if ( edd_prices_show_tax_on_checkout() ) { + $label .= sprintf( __( 'includes %s tax', 'easy-digital-downloads' ), edd_get_formatted_tax_rate() ); + } else { + $label .= sprintf( __( 'excludes %s tax', 'easy-digital-downloads' ), edd_get_formatted_tax_rate() ); + } + + $label = apply_filters( 'edd_cart_item_tax_description', $label, $item_id, $options ); + } + } + + $price = edd_currency_filter( edd_format_amount( $price ) ); + + return apply_filters( 'edd_cart_item_price_label', $price . $label, $item_id, $options ); + } + + /** + * Gets the price of the cart item. Always exclusive of taxes. + * + * Do not use this for getting the final price (with taxes and discounts) of an item. + * Use edd_get_cart_item_final_price() + * + * @since 2.7 + * + * @param int $download_id Download ID for the cart item + * @param array $options Optional parameters, used for defining variable prices + * @param bool $remove_tax_from_inclusive Remove the tax amount from tax inclusive priced products. + * @return float|bool Price for this item + */ + public function get_item_price( $download_id = 0, $options = array(), $remove_tax_from_inclusive = false ) { + $price = 0; + $variable_prices = edd_has_variable_prices( $download_id ); + + if ( $variable_prices ) { + $prices = edd_get_variable_prices( $download_id ); + + if ( $prices ) { + if ( ! empty( $options ) ) { + $price = isset( $prices[ $options['price_id'] ] ) ? $prices[ $options['price_id'] ]['amount'] : false; + } else { + $price = false; + } + } + } + + if ( ! $variable_prices || false === $price ) { + // Get the standard Download price if not using variable prices + $price = edd_get_download_price( $download_id ); + } + + if ( $remove_tax_from_inclusive && edd_prices_include_tax() ) { + $price -= $this->get_item_tax( $download_id, $options, $price ); + } + + return apply_filters( 'edd_cart_item_price', $price, $download_id, $options ); + } + + /** + * Final Price of Item in Cart (incl. discounts and taxes) + * + * @since 2.7 + * + * @param int $item_key Cart item key + * @return float Final price for the item + */ + public function get_item_final_price( $item_key = 0 ) { + $final_price = $this->details[ $item_key ]['price']; + + return apply_filters( 'edd_cart_item_final_price', $final_price, $item_key ); + } + + /** + * Calculate the tax for an item in the cart. + * + * @since 2.7 + * + * @param array $download_id Download ID + * @param array $options Cart item options + * @param float $subtotal Cart item subtotal + * @return float Tax amount + */ + public function get_item_tax( $download_id = 0, $options = array(), $subtotal = '' ) { + $tax = 0; + + if ( ! edd_download_is_tax_exclusive( $download_id ) ) { + $country = ! empty( $_POST['billing_country'] ) ? $_POST['billing_country'] : false; + $state = ! empty( $_POST['card_state'] ) ? $_POST['card_state'] : false; + + $tax = edd_calculate_tax( $subtotal, $country, $state, true, $this->get_tax_rate() ); + } + + $tax = max( $tax, 0 ); + + return apply_filters( 'edd_get_cart_item_tax', $tax, $download_id, $options, $subtotal ); + } + + /** + * Get Cart Fees + * + * @since 2.7 + * @return array Cart fees + */ + public function get_fees( $type = 'all', $download_id = 0, $price_id = null ) { + return EDD()->fees->get_fees( $type, $download_id, $price_id ); + } + + /** + * Get All Cart Fees. + * + * @since 2.7 + * @return array + */ + public function get_all_fees() { + $this->fees = EDD()->fees->get_fees( 'all' ); + return $this->fees; + } + + /** + * Get Cart Items Subtotal. + * + * @since 2.7 + * + * @param array $items Cart items array + * @return float items subtotal + */ + public function get_items_subtotal( $items ) { + $subtotal = 0.00; + + if ( is_array( $items ) && ! empty( $items ) ) { + $prices = wp_list_pluck( $items, 'subtotal' ); + + if ( is_array( $prices ) ) { + $subtotal = array_sum( $prices ); + } else { + $subtotal = 0.00; + } + + if ( $subtotal < 0 ) { + $subtotal = 0.00; + } + } + + $this->subtotal = apply_filters( 'edd_get_cart_items_subtotal', $subtotal ); + + return $this->subtotal; + } + + /** + * Get Discountable Subtotal. + * + * @since 2.7 + * @return float Total discountable amount before taxes + */ + public function get_discountable_subtotal( $code_id ) { + $cart_items = $this->get_contents_details(); + $items = array(); + + $excluded_products = edd_get_discount_excluded_products( $code_id ); + + if ( $cart_items ) { + foreach( $cart_items as $item ) { + if ( ! in_array( $item['id'], $excluded_products ) ) { + $items[] = $item; + } + } + } + + $subtotal = $this->get_items_subtotal( $items ); + + return apply_filters( 'edd_get_cart_discountable_subtotal', $subtotal ); + } + + /** + * Get Discounted Amount. + * + * @since 2.7 + * + * @param bool $discounts Discount codes + * @return float|mixed|void Total discounted amount + */ + public function get_discounted_amount( $discounts = false ) { + $amount = 0.00; + $items = $this->get_contents_details(); + + if ( $items ) { + $discounts = wp_list_pluck( $items, 'discount' ); + + if ( is_array( $discounts ) ) { + $discounts = array_map( 'floatval', $discounts ); + $amount = array_sum( $discounts ); + } + } + + return apply_filters( 'edd_get_cart_discounted_amount', $amount ); + } + + /** + * Get Cart Subtotal. + * + * Gets the total price amount in the cart before taxes and before any discounts. + * + * @since 2.7 + * + * @return float Total amount before taxes + */ + public function get_subtotal() { + $items = $this->get_contents_details(); + $subtotal = $this->get_items_subtotal( $items ); + + return apply_filters( 'edd_get_cart_subtotal', $subtotal ); + } + + /** + * Subtotal (before taxes). + * + * @since 2.7 + * @return float Total amount before taxes fully formatted + */ + public function subtotal() { + return esc_html( edd_currency_filter( edd_format_amount( edd_get_cart_subtotal() ) ) ); + } + + /** + * Get Total Cart Amount. + * + * @since 2.7 + * + * @param bool $discounts Array of discounts to apply (needed during AJAX calls) + * @return float Cart amount + */ + public function get_total( $discounts = false ) { + $subtotal = (float) $this->get_subtotal(); + $discounts = (float) $this->get_discounted_amount(); + $fees = (float) $this->get_total_fees(); + $cart_tax = (float) $this->get_tax(); + $total_wo_tax = $subtotal - $discounts + $fees; + $total = $subtotal - $discounts + $cart_tax + $fees; + + if ( $total < 0 || ! $total_wo_tax > 0 ) { + $total = 0.00; + } + + $this->total = (float) apply_filters( 'edd_get_cart_total', $total ); + + return $this->total; + } + + /** + * Fully Formatted Total Cart Amount. + * + * @since 2.7 + * + * @param bool $echo + * @return mixed|string|void + */ + public function total( $echo = false ) { + $total = apply_filters( 'edd_cart_total', edd_currency_filter( edd_format_amount( $this->get_total() ) ) ); + + if ( $echo ) { + echo esc_html( $total ); + } + + return $total; + } + + /** + * Get Cart Fee Total + * + * @since 2.7 + * @return double + */ + public function get_total_fees() { + $fee_total = 0.00; + + foreach ( $this->get_fees() as $fee ) { + + // Since fees affect cart item totals, we need to not count them towards the cart total if there is an association. + if ( ! empty( $fee['download_id'] ) ) { + continue; + } + + $fee_total += $fee['amount']; + } + + return apply_filters( 'edd_get_fee_total', $fee_total, $this->fees ); + } + + /** + * Get the price ID for an item in the cart. + * + * @since 2.7 + * + * @param array $item Item details + * @return string $price_id Price ID + */ + public function get_item_price_id( $item = array() ) { + if ( isset( $item['item_number'] ) ) { + $price_id = isset( $item['item_number']['options']['price_id'] ) ? $item['item_number']['options']['price_id'] : null; + } else { + $price_id = isset( $item['options']['price_id'] ) ? $item['options']['price_id'] : null; + } + + return $price_id; + } + + /** + * Get the price name for an item in the cart. + * + * @since 2.7 + * + * @param array $item Item details + * @return string $name Price name + */ + public function get_item_price_name( $item = array() ) { + $price_id = (int) $this->get_item_price_id( $item ); + $prices = edd_get_variable_prices( $item['id'] ); + $name = ! empty( $prices[ $price_id ] ) ? $prices[ $price_id ]['name'] : ''; + + return apply_filters( 'edd_get_cart_item_price_name', $name, $item['id'], $price_id, $item ); + } + + /** + * Get the name of an item in the cart. + * + * @since 2.7 + * @since 3.1.2 Updated to use edd_get_download_name() for consistency + * + * @param array $item Item details + * @return string $name Item name + */ + public function get_item_name( $item = array() ) { + $download_id = $item['id']; + $price_id = $this->get_item_price_id( $item ); + + $item_title = edd_get_download_name( $download_id, $price_id ); + + // In the event that we dont' get a name back, use the ID. + if ( empty( $item_title ) ) { + $item_title = $item['id']; + } + + return apply_filters( 'edd_get_cart_item_name', $item_title, $item['id'], $item ); + } + + /** + * Get all applicable tax for the items in the cart + * + * @since 2.7 + * @return float Total tax amount + */ + public function get_tax() { + $cart_tax = 0; + $items = $this->get_contents_details(); + + if ( $items ) { + + $taxes = wp_list_pluck( $items, 'tax' ); + + if ( is_array( $taxes ) ) { + $cart_tax = array_sum( $taxes ); + } + } + $cart_tax += $this->get_tax_on_fees(); + + $subtotal = $this->get_subtotal(); + if ( empty( $subtotal ) ) { + $cart_tax = 0; + } + + $cart_tax = apply_filters( 'edd_get_cart_tax', edd_sanitize_amount( $cart_tax ) ); + + return $cart_tax; + } + + /** + * Gets the total tax amount for the cart contents in a fully formatted way + * + * @since 2.7 + * + * @param boolean $echo Decides if the result should be returned or not + * @return string Total tax amount + */ + public function tax( $echo = false ) { + $cart_tax = $this->get_tax(); + $cart_tax = edd_currency_filter( edd_format_amount( $cart_tax ) ); + + $tax = max( $cart_tax, 0 ); + $tax = apply_filters( 'edd_cart_tax', $cart_tax ); + + if ( $echo ) { + echo esc_html( $tax ); + } + + return $tax; + } + + /** + * Get tax applicable for fees. + * + * @since 2.7 + * @return float Total taxable amount for fees + */ + public function get_tax_on_fees() { + $tax = 0; + $fees = edd_get_cart_fees(); + + if ( $fees ) { + foreach ( $fees as $fee_id => $fee ) { + if ( ! empty( $fee['no_tax'] ) || $fee['amount'] < 0 ) { + continue; + } + + /** + * Fees (at this time) must be exclusive of tax + */ + add_filter( 'edd_prices_include_tax', '__return_false' ); + $tax += edd_calculate_tax( $fee['amount'], '', '', true, $this->get_tax_rate() ); + remove_filter( 'edd_prices_include_tax', '__return_false' ); + } + } + + return apply_filters( 'edd_get_cart_fee_tax', $tax ); + } + + /** + * Is Cart Saving Enabled? + * + * @since 2.7 + * @return bool + */ + public function is_saving_enabled() { + return edd_get_option( 'enable_cart_saving', false ); + } + + /** + * Checks if the cart has been saved + * + * @since 2.7 + * @return bool + */ + public function is_saved() { + if ( ! $this->is_saving_enabled() ) { + return false; + } + + $saved_cart = get_user_meta( get_current_user_id(), 'edd_saved_cart', true ); + + if ( is_user_logged_in() ) { + if ( ! $saved_cart ) { + return false; + } + + if ( $saved_cart === EDD()->session->get( 'edd_cart' ) ) { + return false; + } + + return true; + } else { + if ( ! isset( $_COOKIE['edd_saved_cart'] ) ) { + return false; + } + + if ( json_decode( stripslashes( $_COOKIE['edd_saved_cart'] ), true ) === EDD()->session->get( 'edd_cart' ) ) { + return false; + } + + return true; + } + } + + /** + * Save Cart + * + * @since 2.7 + * @return bool + */ + public function save() { + + // Bail if carts cannot be saved + if ( ! $this->is_saving_enabled() ) { + return false; + } + + // Get cart & cart token + $cart = EDD()->session->get( 'edd_cart' ); + $token = edd_generate_cart_token(); + + if ( is_user_logged_in() ) { + $user_id = get_current_user_id(); + update_user_meta( $user_id, 'edd_saved_cart', $cart, false ); + update_user_meta( $user_id, 'edd_cart_token', $token, false ); + } else { + $cart = json_encode( $cart ); + $expires = time() + WEEK_IN_SECONDS; + @setcookie( 'edd_saved_cart', $cart, $expires, COOKIEPATH, COOKIE_DOMAIN ); + @setcookie( 'edd_cart_token', $token, $expires, COOKIEPATH, COOKIE_DOMAIN ); + } + + // Get all cart messages + $messages = EDD()->session->get( 'edd_cart_messages' ); + + // Make sure it's an array, if empty + if ( empty( $messages ) ) { + $messages = array(); + } + + $checkout_url = add_query_arg( + array( + 'edd_action' => 'restore_cart', + 'edd_cart_token' => sanitize_key( $token ), + ), + edd_get_checkout_uri() + ); + + // Add the success message + $messages['edd_cart_save_successful'] = sprintf( + '%1$s: %2$s %3$s', + __( 'Success', 'easy-digital-downloads' ), + __( 'Cart saved successfully. You can restore your cart using this URL:', 'easy-digital-downloads' ), + esc_url( edd_get_checkout_uri() . '?edd_action=restore_cart&edd_cart_token=' . urlencode( $token ) ) + ); + + // Set these messages in the session + EDD()->session->set( 'edd_cart_messages', $messages ); + + // Return if cart saved + return ! empty( $cart ); + } + + /** + * Restore Cart + * + * @since 2.7 + * @return bool + */ + public function restore() { + if ( ! $this->is_saving_enabled() ) { + return false; + } + + $user_id = get_current_user_id(); + $saved_cart = get_user_meta( $user_id, 'edd_saved_cart', true ); + $token = $this->get_token(); + + if ( is_user_logged_in() && $saved_cart ) { + $messages = EDD()->session->get( 'edd_cart_messages' ); + + if ( ! $messages ) { + $messages = array(); + } + + if ( isset( $_GET['edd_cart_token'] ) && ! hash_equals( $_GET['edd_cart_token'], $token ) ) { + $messages['edd_cart_restoration_failed'] = sprintf( '%1$s: %2$s', __( 'Error', 'easy-digital-downloads' ), __( 'Cart restoration failed. Invalid token.', 'easy-digital-downloads' ) ); + EDD()->session->set( 'edd_cart_messages', $messages ); + } + + delete_user_meta( $user_id, 'edd_saved_cart' ); + delete_user_meta( $user_id, 'edd_cart_token' ); + + if ( isset( $_GET['edd_cart_token'] ) && $_GET['edd_cart_token'] != $token ) { + return new WP_Error( 'invalid_cart_token', __( 'The cart cannot be restored. Invalid token.', 'easy-digital-downloads' ) ); + } + } elseif ( ! is_user_logged_in() && isset( $_COOKIE['edd_saved_cart'] ) && $token ) { + $saved_cart = $_COOKIE['edd_saved_cart']; + + if ( ! hash_equals( $_GET['edd_cart_token'], $token ) ) { + $messages['edd_cart_restoration_failed'] = sprintf( '%1$s: %2$s', __( 'Error', 'easy-digital-downloads' ), __( 'Cart restoration failed. Invalid token.', 'easy-digital-downloads' ) ); + EDD()->session->set( 'edd_cart_messages', $messages ); + + return new WP_Error( 'invalid_cart_token', __( 'The cart cannot be restored. Invalid token.', 'easy-digital-downloads' ) ); + } + + $saved_cart = json_decode( stripslashes( $saved_cart ), true ); + + setcookie( 'edd_saved_cart', '', time()-3600, COOKIEPATH, COOKIE_DOMAIN ); + setcookie( 'edd_cart_token', '', time()-3600, COOKIEPATH, COOKIE_DOMAIN ); + } + + $messages['edd_cart_restoration_successful'] = sprintf( '%1$s: %2$s', __( 'Success', 'easy-digital-downloads' ), __( 'Cart restored successfully.', 'easy-digital-downloads' ) ); + EDD()->session->set( 'edd_cart', $saved_cart ); + EDD()->session->set( 'edd_cart_messages', $messages ); + + // @e also have to set this instance to what the session is. + $this->contents = $saved_cart; + + return true; + } + + /** + * Retrieve a saved cart token. Used in validating saved carts + * + * @since 2.7 + * @return int + */ + public function get_token() { + $user_id = get_current_user_id(); + + if ( is_user_logged_in() ) { + $token = get_user_meta( $user_id, 'edd_cart_token', true ); + } else { + $token = isset( $_COOKIE['edd_cart_token'] ) ? $_COOKIE['edd_cart_token'] : false; + } + + return apply_filters( 'edd_get_cart_token', $token, $user_id ); + } + + /** + * Generate URL token to restore the cart via a URL + * + * @since 2.7 + * @return int + */ + public function generate_token() { + return apply_filters( 'edd_generate_cart_token', md5( mt_rand() . time() ) ); + } +} diff --git a/includes/cart/functions.php b/includes/cart/functions.php new file mode 100755 index 00000000000..8ec800d392e --- /dev/null +++ b/includes/cart/functions.php @@ -0,0 +1,676 @@ +cart->get_contents(); +} + +/** + * Retrieve the Cart Content Details + * + * Includes prices, tax, etc of all items. + * + * @since 1.0 + * @return array $details Cart content details + */ +function edd_get_cart_content_details() { + return EDD()->cart->get_contents_details(); +} + +/** + * Get Cart Quantity + * + * @since 1.0 + * @return int Sum quantity of items in the cart + */ +function edd_get_cart_quantity() { + return EDD()->cart->get_quantity(); +} + +/** + * Add To Cart + * + * Adds a download ID to the shopping cart. + * + * @since 1.0 + * + * @param int $download_id Download IDs to be added to the cart + * @param array $options Array of options, such as variable price + * + * @return string Cart key of the new item + */ +function edd_add_to_cart( $download_id, $options = array() ) { + return EDD()->cart->add( $download_id, $options ); +} + +/** + * Removes a Download from the Cart + * + * @since 1.0 + * @param int $cart_key the cart key to remove. This key is the numerical index of the item contained within the cart array. + * @return array Updated cart items + */ +function edd_remove_from_cart( $cart_key ) { + return EDD()->cart->remove( $cart_key ); +} + +/** + * Checks to see if an item is already in the cart and returns a boolean + * + * @since 1.0 + * + * @param int $download_id ID of the download to remove + * @param array $options + * @return bool Item in the cart or not? + */ +function edd_item_in_cart( $download_id = 0, $options = array() ) { + return EDD()->cart->is_item_in_cart( $download_id, $options ); +} + +/** + * Get the Item Position in Cart + * + * @since 1.0.7.2 + * + * @param int $download_id ID of the download to get position of + * @param array $options array of price options + * @return bool|int|string false if empty cart | position of the item in the cart + */ +function edd_get_item_position_in_cart( $download_id = 0, $options = array() ) { + return EDD()->cart->get_item_position( $download_id, $options ); +} + +/** + * Check if quantities are enabled + * + * @since 1.7 + * @return bool + */ +function edd_item_quantities_enabled() { + $ret = edd_get_option( 'item_quantities', false ); + return (bool) apply_filters( 'edd_item_quantities_enabled', $ret ); +} + +/** + * Set Cart Item Quantity + * + * @since 1.7 + * + * @param int $download_id Download (cart item) ID number + * @param int $quantity + * @param array $options Download options, such as price ID + * @return mixed New Cart array + */ +function edd_set_cart_item_quantity( $download_id = 0, $quantity = 1, $options = array() ) { + return EDD()->cart->set_item_quantity( $download_id, $quantity, $options ); +} + +/** + * Get Cart Item Quantity + * + * @since 1.0 + * @param int $download_id Download (cart item) ID number + * @param array $options Download options, such as price ID + * @return int $quantity Cart item quantity + */ +function edd_get_cart_item_quantity( $download_id = 0, $options = array() ) { + return EDD()->cart->get_item_quantity( $download_id, $options ); +} + +/** + * Get Cart Item Price + * + * @since 1.0 + * + * @param int $item_id Download (cart item) ID number + * @param array $options Optional parameters, used for defining variable prices + * @return string Fully formatted price + */ +function edd_cart_item_price( $item_id = 0, $options = array() ) { + return EDD()->cart->item_price( $item_id, $options ); +} + +/** + * Get Cart Item Price + * + * Gets the price of the cart item. Always exclusive of taxes + * + * Do not use this for getting the final price (with taxes and discounts) of an item. + * Use edd_get_cart_item_final_price() + * + * @since 1.0 + * @param int $download_id Download ID number + * @param array $options Optional parameters, used for defining variable prices + * @param bool $remove_tax_from_inclusive Remove the tax amount from tax inclusive priced products. + * @return float|bool Price for this item + */ +function edd_get_cart_item_price( $download_id = 0, $options = array(), $remove_tax_from_inclusive = false ) { + return EDD()->cart->get_item_price( $download_id, $options, $remove_tax_from_inclusive ); +} + +/** + * Get cart item's final price + * + * Gets the amount after taxes and discounts + * + * @since 1.9 + * @param int $item_key Cart item key + * @return float Final price for the item + */ +function edd_get_cart_item_final_price( $item_key = 0 ) { + return EDD()->cart->get_item_final_price( $item_key ); +} + +/** + * Get cart item tax + * + * @since 1.9 + * @param array $download_id Download ID + * @param array $options Cart item options + * @param float $subtotal Cart item subtotal + * @return float Tax amount + */ +function edd_get_cart_item_tax( $download_id = 0, $options = array(), $subtotal = '' ) { + return EDD()->cart->get_item_tax( $download_id, $options, $subtotal ); +} + +/** + * Get Price Name + * + * Gets the name of the specified price option, + * for variable pricing only. + * + * @since 1.0 + * + * @param $download_id Download ID number + * @param array $options Optional parameters, used for defining variable prices + * @return mixed|void Name of the price option + */ +function edd_get_price_name( $download_id = 0, $options = array() ) { + $return = false; + + if ( edd_has_variable_prices( $download_id ) && ! empty( $options ) ) { + $prices = edd_get_variable_prices( $download_id ); + $name = false; + + if ( $prices ) { + if ( isset( $prices[ $options['price_id'] ] ) ) { + $name = $prices[ $options['price_id'] ]['name']; + } + } + $return = $name; + } + + return apply_filters( 'edd_get_price_name', $return, $download_id, $options ); +} + +/** + * Get cart item price id + * + * @since 1.0 + * + * @param array $item Cart item array + * @return int Price id + */ +function edd_get_cart_item_price_id( $item = array() ) { + return EDD()->cart->get_item_price_id( $item ); +} + +/** + * Get cart item price name + * + * @since 1.8 + * @param int $item Cart item array + * @return string Price name + */ +function edd_get_cart_item_price_name( $item = array() ) { + return EDD()->cart->get_item_price_name( $item ); +} + +/** + * Get cart item title + * + * @since 2.4.3 + * @param array $item Cart item array + * @return string item title + */ +function edd_get_cart_item_name( $item = array() ) { + return EDD()->cart->get_item_name( $item ); +} + +/** + * Cart Subtotal + * + * Shows the subtotal for the shopping cart (no taxes) + * + * @since 1.4 + * @return float Total amount before taxes fully formatted + */ +function edd_cart_subtotal() { + return EDD()->cart->subtotal(); +} + +/** + * Get Cart Subtotal + * + * Gets the total price amount in the cart before taxes and before any discounts + * uses edd_get_cart_contents(). + * + * @since 1.3.3 + * @return float Total amount before taxes + */ +function edd_get_cart_subtotal() { + return EDD()->cart->get_subtotal(); +} + +/** + * Get Cart Discountable Subtotal. + * + * @return float Total discountable amount before taxes + */ +function edd_get_cart_discountable_subtotal( $code_id ) { + return EDD()->cart->get_discountable_subtotal( $code_id ); +} + +/** + * Get cart items subtotal + * @param array $items Cart items array + * + * @return float items subtotal + */ +function edd_get_cart_items_subtotal( $items ) { + return EDD()->cart->get_items_subtotal( $items ); +} +/** + * Get Total Cart Amount + * + * Returns amount after taxes and discounts + * + * @since 1.4.1 + * @param bool $discounts Array of discounts to apply (needed during AJAX calls) + * @return float Cart amount + */ +function edd_get_cart_total( $discounts = false ) { + return EDD()->cart->get_total( $discounts ); +} + + +/** + * Get Total Cart Amount + * + * Gets the fully formatted total price amount in the cart. + * uses edd_get_cart_amount(). + * + * @since 1.3.3 + * + * @param bool $echo + * @return mixed|string|void + */ +function edd_cart_total( $echo = true ) { + return EDD()->cart->total( $echo ); +} + +/** + * Check if cart has fees applied + * + * Just a simple wrapper function for EDD_Fees::has_fees() + * + * @since 1.5 + * @param string $type + * @uses EDD()->fees->has_fees() + * @return bool Whether the cart has fees applied or not + */ +function edd_cart_has_fees( $type = 'all' ) { + return EDD()->fees->has_fees( $type ); +} + +/** + * Get Cart Fees + * + * Just a simple wrapper function for EDD_Fees::get_fees() + * + * @since 1.5 + * @param string $type + * @param int $download_id + * @uses EDD()->fees->get_fees() + * @return array All the cart fees that have been applied + */ +function edd_get_cart_fees( $type = 'all', $download_id = 0, $price_id = NULL ) { + return EDD()->cart->get_fees( $type, $download_id, $price_id ); +} + +/** + * Get Cart Fee Total + * + * Just a simple wrapper function for EDD_Fees::total() + * + * @since 1.5 + * @uses EDD()->fees->total() + * @return float Total Cart Fees + */ +function edd_get_cart_fee_total() { + return EDD()->cart->get_total_fees(); +} + +/** + * Get cart tax on Fees + * + * @since 2.0 + * @uses EDD()->fees->get_fees() + * @return float Total Cart tax on Fees + */ +function edd_get_cart_fee_tax() { + return EDD()->cart->get_tax_on_fees(); +} + +/** + * Is the cart empty? + * + * @since 3.0 + * @uses EDD()->cart->is_empty() + * @return bool Is the cart empty? + */ +function edd_is_cart_empty() { + return EDD()->cart->is_empty(); +} + +/** + * Get Purchase Summary + * + * Retrieves the purchase summary. + * + * @since 1.0 + * + * @param $purchase_data + * @param bool $email + * @return string + */ +function edd_get_purchase_summary( $purchase_data, $email = true ) { + $summary = ''; + + if ( $email ) { + $summary .= $purchase_data['user_email'] . ' - '; + } + + if ( ! empty( $purchase_data['downloads'] ) ) { + foreach ( $purchase_data['downloads'] as $download ) { + $summary .= get_the_title( $download['id'] ) . ', '; + } + + $summary = substr( $summary, 0, -2 ); + } + + return apply_filters( 'edd_get_purchase_summary', $summary, $purchase_data, $email ); +} + +/** + * Gets the total tax amount for the cart contents + * + * @since 1.2.3 + * + * @return mixed|void Total tax amount + */ +function edd_get_cart_tax() { + return EDD()->cart->get_tax(); +} + +/** + * Gets the tax rate charged on the cart. + * + * @since 2.7 + * @param string $country Country code for tax rate. + * @param string $state State for tax rate. + * @param string $postal_code Postal code for tax rate. Not used by core, but for developers. + * @return float Tax rate. + */ +function edd_get_cart_tax_rate( $country = '', $state = '', $postal_code = '' ) { + $rate = edd_get_tax_rate( $country, $state ); + + return (float) apply_filters( 'edd_get_cart_tax_rate', $rate, $country, $state, $postal_code ); +} + +/** + * Gets the total tax amount for the cart contents in a fully formatted way + * + * @since 1.2.3 + * @param bool $echo Whether to echo the tax amount or not (default: false) + * @return string Total tax amount (if $echo is set to true) + */ +function edd_cart_tax( $echo = false ) { + return EDD()->cart->tax( $echo ); +} + +/** + * Add Collection to Cart + * + * Adds all downloads within a taxonomy term to the cart. + * + * @since 1.0.6 + * @param string $taxonomy Name of the taxonomy + * @param mixed $terms Slug or ID of the term from which to add | An array of terms + * @return array Array of IDs for each item added to the cart + */ +function edd_add_collection_to_cart( $taxonomy, $terms ) { + + // Bail if taxonomy is not a string + if ( ! is_string( $taxonomy ) ) { + return false; + } + + if ( is_numeric( $terms ) ) { + $terms = get_term( $terms, $taxonomy ); + $terms = $terms->slug; + } + + $cart_item_ids = array(); + + $items = get_posts( array( + 'post_type' => 'download', + 'posts_per_page' => -1, + $taxonomy => $terms + ) ); + + if ( ! empty( $items ) ) { + foreach ( $items as $item ) { + edd_add_to_cart( $item->ID ); + $cart_item_ids[] = $item->ID; + } + } + + return $cart_item_ids; +} + +/** + * Returns the URL to remove an item from the cart + * + * @since 1.0 + * @global $post + * @param int $cart_key Cart item key + * @return string $remove_url URL to remove the cart item + */ +function edd_remove_item_url( $cart_key ) { + return EDD()->cart->remove_item_url( $cart_key ); +} + +/** + * Returns the URL to remove an item from the cart + * + * @since 1.0 + * @global $post + * @param string $fee_id Fee ID + * @return string $remove_url URL to remove the cart item + */ +function edd_remove_cart_fee_url( $fee_id = '') { + return EDD()->cart->remove_fee_url( $fee_id ); +} + +/** + * Empties the Cart + * + * @since 1.0 + * @uses EDD()->session->set() + * @return void + */ +function edd_empty_cart() { + EDD()->cart->empty_cart(); +} + +/** + * Store Purchase Data in Sessions + * + * Used for storing info about purchase + * + * @since 1.1.5 + * + * @param $purchase_data + * + * @uses EDD()->session->set() + */ +function edd_set_purchase_session( $purchase_data = array() ) { + EDD()->session->set( 'edd_purchase', $purchase_data ); +} + +/** + * Retrieve Purchase Data from Session + * + * Used for retrieving info about purchase + * after completing a purchase + * + * @since 1.1.5 + * @uses EDD()->session->get() + * @return mixed array | false + */ +function edd_get_purchase_session() { + return EDD()->session->get( 'edd_purchase' ); +} + +/** + * Checks if cart saving has been disabled + * + * @since 1.8 + * @return bool Whether or not cart saving has been disabled + */ +function edd_is_cart_saving_disabled() { + return ! EDD()->cart->is_saving_enabled(); +} + +/** + * Checks if a cart has been saved + * + * @since 1.8 + * @return bool + */ +function edd_is_cart_saved() { + return EDD()->cart->is_saved(); +} + +/** + * Process the Cart Save + * + * @since 1.8 + * @return bool + */ +function edd_save_cart() { + return EDD()->cart->save(); +} + + +/** + * Process the Cart Restoration + * + * @since 1.8 + * @return mixed || false Returns false if cart saving is disabled + */ +function edd_restore_cart() { + return EDD()->cart->restore(); +} + +/** + * Retrieve a saved cart token. Used in validating saved carts + * + * @since 1.8 + * @return int + */ +function edd_get_cart_token() { + return EDD()->cart->get_token(); +} + +/** + * Delete Saved Carts after one week + * + * This function is only intended to be used by WordPress cron. + * + * @since 1.8 + * @global $wpdb + * @return void + */ +function edd_delete_saved_carts() { + global $wpdb; + + // Bail if not in WordPress cron + if ( ! edd_doing_cron() ) { + return; + } + + $start = date( 'Y-m-d', strtotime( '-7 days' ) ); + $carts = $wpdb->get_results( + " + SELECT user_id, meta_key, FROM_UNIXTIME(meta_value, '%Y-%m-%d') AS date + FROM {$wpdb->usermeta} + WHERE meta_key = 'edd_cart_token' + ", ARRAY_A + ); + + if ( $carts ) { + foreach ( $carts as $cart ) { + $user_id = $cart['user_id']; + $meta_value = $cart['date']; + + if ( strtotime( $meta_value ) < strtotime( '-1 week' ) ) { + $wpdb->delete( + $wpdb->usermeta, + array( + 'user_id' => $user_id, + 'meta_key' => 'edd_cart_token' + ) + ); + + $wpdb->delete( + $wpdb->usermeta, + array( + 'user_id' => $user_id, + 'meta_key' => 'edd_saved_cart' + ) + ); + } + } + } +} +add_action( 'edd_weekly_scheduled_events', 'edd_delete_saved_carts' ); + +/** + * Generate URL token to restore the cart via a URL + * + * @since 1.8 + * @return string UNIX timestamp + */ +function edd_generate_cart_token() { + return EDD()->cart->generate_token(); +} diff --git a/includes/cart/template.php b/includes/cart/template.php new file mode 100755 index 00000000000..d0a5ec3d7ef --- /dev/null +++ b/includes/cart/template.php @@ -0,0 +1,289 @@ +'; + echo '
    '; + do_action( 'edd_checkout_cart_top' ); + edd_get_template_part( 'checkout_cart' ); + do_action( 'edd_checkout_cart_bottom' ); + echo '
    '; + echo ''; + do_action( 'edd_after_checkout_cart' ); +} + +/** + * Renders the Shopping Cart + * + * @since 1.0 + * + * @param bool $echo + * @return string Fully formatted cart + */ +function edd_shopping_cart( $echo = false ) { + ob_start(); + + do_action( 'edd_before_cart' ); + + edd_get_template_part( 'widget', 'cart' ); + + do_action( 'edd_after_cart' ); + + if ( $echo ) { + echo ob_get_clean(); + } else { + return ob_get_clean(); + } +} + +/** + * Get Cart Item Template + * + * @since 1.0 + * @param int $cart_key Cart key + * @param array $item Cart item + * @param bool $ajax AJAX? + * @return string Cart item +*/ +function edd_get_cart_item_template( $cart_key, $item, $ajax = false ) { + global $post; + + $id = is_array( $item ) ? $item['id'] : $item; + + $remove_url = edd_remove_item_url( $cart_key ); + $title = get_the_title( $id ); + $options = !empty( $item['options'] ) ? $item['options'] : array(); + $quantity = edd_get_cart_item_quantity( $id, $options ); + $price = edd_get_cart_item_price( $id, $options ); + + if ( ! empty( $options ) ) { + $title .= ( edd_has_variable_prices( $item['id'] ) ) ? ' - ' . edd_get_price_name( $id, $item['options'] ) : edd_get_price_name( $id, $item['options'] ); + } + + ob_start(); + + edd_get_template_part( 'widget', 'cart-item' ); + + $item = ob_get_clean(); + + $item = str_replace( '{item_title}', $title, $item ); + $item = str_replace( '{item_amount}', edd_currency_filter( edd_format_amount( $price ) ), $item ); + $item = str_replace( '{cart_item_id}', absint( $cart_key ), $item ); + $item = str_replace( '{item_id}', absint( $id ), $item ); + $item = str_replace( '{item_quantity}', absint( $quantity ), $item ); + $item = str_replace( '{remove_url}', esc_url( $remove_url ), $item ); + $subtotal = ''; + if ( $ajax ){ + $subtotal = edd_currency_filter( edd_format_amount( edd_get_cart_subtotal() ) ) ; + } + $item = str_replace( '{subtotal}', $subtotal, $item ); + + return apply_filters( 'edd_cart_item', $item, $id ); +} + +/** + * Returns the Empty Cart Message + * + * @since 1.0 + * @return string Cart is empty message + */ +function edd_empty_cart_message() { + return apply_filters( 'edd_empty_cart_message', '' . __( 'Your cart is empty.', 'easy-digital-downloads' ) . '' ); +} + +/** + * Echoes the Empty Cart Message + * + * @since 1.0 + * @return void + */ +function edd_empty_checkout_cart() { + echo edd_empty_cart_message(); +} +add_action( 'edd_cart_empty', 'edd_empty_checkout_cart' ); + +/* + * Calculate the number of columns in the cart table dynamically. + * + * @since 1.8 + * @return int The number of columns + */ +function edd_checkout_cart_columns() { + global $wp_filter, $wp_version; + + $columns_count = 3; + + if ( ! empty( $wp_filter['edd_checkout_table_header_first'] ) ) { + $header_first_count = 0; + $callbacks = $wp_filter['edd_checkout_table_header_first']->callbacks; + + foreach ( $callbacks as $callback ) { + $header_first_count += count( $callback ); + } + $columns_count += $header_first_count; + } + + if ( ! empty( $wp_filter['edd_checkout_table_header_last'] ) ) { + $header_last_count = 0; + $callbacks = $wp_filter['edd_checkout_table_header_last']->callbacks; + + foreach ( $callbacks as $callback ) { + $header_last_count += count( $callback ); + } + $columns_count += $header_last_count; + } + + return apply_filters( 'edd_checkout_cart_columns', $columns_count ); +} + +/** + * Display the "Save Cart" button on the checkout + * + * @since 1.8 + * @return void + */ +function edd_save_cart_button() { + if ( edd_is_cart_saving_disabled() ) { + return; + } + + $color = edd_get_button_color_class(); + + if ( edd_is_cart_saved() ) : ?> + + + + 'restore_cart', 'edd_cart_token' => urlencode( edd_get_cart_token() ) ) ) ) . '">' . esc_html__( 'Restore Previous Cart.', 'easy-digital-downloads' ) . ''; + } +} +add_action( 'edd_cart_empty', 'edd_empty_cart_restore_cart_link' ); + +/** + * Display the "Save Cart" button on the checkout + * + * @since 1.8 + * @return void + */ +function edd_update_cart_button() { + if ( ! edd_item_quantities_enabled() ) + return; + + $color = edd_get_button_color_class(); +?> + + +session->get( 'edd_cart_messages' ); + + if ( $messages ) { + foreach ( $messages as $message_id => $message ) { + + // Try and detect what type of message this is + if ( strpos( strtolower( $message ), 'error' ) ) { + $type = 'error'; + } elseif ( strpos( strtolower( $message ), 'success' ) ) { + $type = 'success'; + } else { + $type = 'info'; + } + + $classes = apply_filters( 'edd_' . $type . '_class', array( + 'edd_errors', 'edd-alert', 'edd-alert-' . $type + ) ); + + echo '
    '; + // Loop message codes and display messages + echo '

    ' . $message . '

    '; + echo '
    '; + + } + + // Remove all of the cart saving messages + EDD()->session->set( 'edd_cart_messages', null ); + } +} +add_action( 'edd_before_checkout_cart', 'edd_display_cart_messages' ); + +/** + * Show Added To Cart Messages + * + * @since 1.0 + * @param int $download_id Download (Post) ID + * @return void + */ +function edd_show_added_to_cart_messages( $download_id ) { + if ( isset( $_POST['edd_action'] ) && $_POST['edd_action'] == 'add_to_cart' ) { + if ( $download_id != absint( $_POST['download_id'] ) ) + $download_id = absint( $_POST['download_id'] ); + + $alert = '
    ' + . sprintf( __('You have successfully added %s to your shopping cart.','easy-digital-downloads' ), get_the_title( $download_id ) ) + . ' ' . __('Checkout.','easy-digital-downloads' ) . '' + . '
    '; + + echo apply_filters( 'edd_show_added_to_cart_messages', $alert ); + } +} +add_action('edd_after_download_content', 'edd_show_added_to_cart_messages'); diff --git a/includes/checkout-template.php b/includes/checkout-template.php deleted file mode 100755 index d1ecd50eb81..00000000000 --- a/includes/checkout-template.php +++ /dev/null @@ -1,557 +0,0 @@ - - - - - - -
    - - 1 && !isset($_GET['payment-mode'])) { - $show_gateways = true; - if(edd_get_cart_amount() <= 0) { - $show_gateways = false; - } - } - if($show_gateways) { - do_action( 'edd_payment_payment_mode_select', $gateways ); - } else { - - if(count($gateways) >= 1 && !isset($_GET['payment-mode'])) { - foreach($gateways as $gateway_id => $gateway) : - $enabled_gateway = $gateway_id; - if(edd_get_cart_amount() <= 0) { - $enabled_gateway = 'manual'; // this allows a free download by filling in the info - } - endforeach; - } else if(edd_get_cart_amount() <= 0) { - $enabled_gateway = 'manual'; - } else { - $enabled_gateway = 'none'; - } - $payment_mode = isset($_GET['payment-mode']) ? urldecode($_GET['payment-mode']) : $enabled_gateway; - ?> - - -
    - - - - - - -
    - -
    - - - -
    - - -

    - - -

    - -

    - - -

    -

    - - -

    - -
    - - -
    -

    -

    -
    - - -
    - - -

    -
    - -
    -

    - - - - - - - - - - - - -

    - -

    - -
    - -

    - - -
    - - -
    - - - - -
    - -

    - - -

    -

    - - -

    -

    - - -

    - - - -

    - - / - - -

    - - - -
    - - - - -
    - -

    - - -

    -

    - - -

    -

    - - -

    -

    - - -

    -

    - - - - -

    -

    - - -

    - -
    - -
    -

    -
    - - -

    - - -

    -

    - - -

    -

    - - -

    - -
    -

    - - -

    -

    - - -

    -

    - - -

    - - -
    - -
    - - -

    - - -

    - - - -
    -

    - - -

    - -
    -
    - -

    - '; - foreach($gateways as $gateway_id => $gateway) : - echo ''; - endforeach; - echo ''; - echo ''; - ?> -

    - -
    -
    -

    - -

    -
    -
    - -
    -

    - - -

    -
    - - - - - - - - - - - - - 0 ? $edd_options['checkout_label'] : __('Purchase', 'edd'); ?> - - - - - '; - foreach($edd_options['accepted_cards'] as $key => $card) { - if( edd_string_is_image_url($key)) { - echo ''; - } else { - echo ''; - } - } - echo ''; - } -} -add_action('edd_payment_mode_top', 'edd_show_payment_icons'); -add_action('edd_before_purchase_form', 'edd_show_payment_icons'); - - -/** - * Agree To Terms JS - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_agree_to_terms_js() { - global $edd_options; - if(isset($edd_options['show_agree_to_terms'])) { ?> - - queried_object ); + $is_object_id_set = isset( $wp_query->queried_object_id ); + $is_checkout = is_page( edd_get_option( 'purchase_page' ) ); + + if ( ! $is_object_set ) { + unset( $wp_query->queried_object ); + } elseif ( is_singular() ) { + $content = $wp_query->queried_object->post_content; + } + + if ( ! $is_object_id_set ) { + unset( $wp_query->queried_object_id ); + } + + // If we know this isn't the primary checkout page, check other methods. + if ( ! $is_checkout && isset( $content ) ) { + if ( has_shortcode( $content, 'download_checkout' ) || ( edd_has_core_blocks() && has_block( 'edd/checkout', $content ) ) ) { + $is_checkout = true; + } + } + + return apply_filters( 'edd_is_checkout', $is_checkout ); +} + +/** + * Determines if a user can checkout or not + * + * @since 1.3.3 + * @return bool Can user checkout? + */ +function edd_can_checkout() { + $can_checkout = true; // Always true for now + + return (bool) apply_filters( 'edd_can_checkout', $can_checkout ); +} + +/** + * Get the URL of the Checkout page + * + * @since 1.0.8 + * @param array $args Extra query args to add to the URI + * @return mixed Full URL to the checkout page, if present | null if it doesn't exist + */ +function edd_get_checkout_uri( $args = array() ) { + $uri = false; + + if ( edd_is_checkout() ) { + global $post; + $uri = $post instanceof WP_Post ? get_permalink( $post->ID ) : NULL; + } + + // If we are not on a checkout page, determine the URI from the default. + if ( empty( $uri ) ) { + $uri = edd_get_option( 'purchase_page', false ); + $uri = isset( $uri ) ? get_permalink( $uri ) : NULL; + } + + if ( ! empty( $args ) ) { + // Check for backward compatibility + if ( is_string( $args ) ) { + $args = str_replace( '?', '', $args ); + } + + $args = wp_parse_args( $args ); + + $uri = add_query_arg( $args, $uri ); + } + + $scheme = defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN ? 'https' : 'admin'; + + $ajax_url = admin_url( 'admin-ajax.php', $scheme ); + + if ( ( ! preg_match( '/^https/', $uri ) && preg_match( '/^https/', $ajax_url ) && edd_is_ajax_enabled() ) || edd_is_ssl_enforced() ) { + $uri = preg_replace( '/^http:/', 'https:', $uri ); + } + + if ( edd_get_option( 'no_cache_checkout', false ) ) { + $uri = edd_add_cache_busting( $uri ); + } + + return apply_filters( 'edd_get_checkout_uri', $uri ); +} + +/** + * Send back to checkout. + * + * Used to redirect a user back to the purchase + * page if there are errors present. + * + * @param array $args + * @since 1.0 + * @return Void + */ +function edd_send_back_to_checkout( $args = array() ) { + $redirect = edd_get_checkout_uri(); + + if ( ! empty( $args ) ) { + // Check for backward compatibility + if ( is_string( $args ) ) { + $args = str_replace( '?', '', $args ); + } + + $args = wp_parse_args( $args ); + + $redirect = add_query_arg( $args, $redirect ); + } + + edd_redirect( apply_filters( 'edd_send_back_to_checkout', $redirect, $args ) ); +} + +/** + * Get the URL of the Transaction Failed page + * + * @since 1.3.4 + * @param bool $extras Extras to append to the URL + * @return mixed|void Full URL to the Transaction Failed page, if present, home page if it doesn't exist + */ +function edd_get_failed_transaction_uri( $extras = false ) { + $uri = edd_get_option( 'failure_page', '' ); + $uri = ! empty( $uri ) ? trailingslashit( get_permalink( $uri ) ) : home_url(); + + if ( $extras ) { + $uri .= $extras; + } + + return apply_filters( 'edd_get_failed_transaction_uri', $uri ); +} + +/** + * Determines if we're currently on the Failed Transaction page. + * + * @since 2.1 + * @return bool True if on the Failed Transaction page, false otherwise. + */ +function edd_is_failed_transaction_page() { + $ret = edd_get_option( 'failure_page', false ); + $ret = isset( $ret ) ? is_page( $ret ) : false; + + return apply_filters( 'edd_is_failure_page', $ret ); +} + +/** + * Mark payments as Failed when returning to the Failed Transaction page + * + * @since 1.9.9 + * @return void +*/ +function edd_listen_for_failed_payments() { + + $failed_page = edd_get_option( 'failure_page', 0 ); + + if( ! empty( $failed_page ) && is_page( $failed_page ) && ! empty( $_GET['payment-id'] ) ) { + + $payment_id = absint( $_GET['payment-id'] ); + $payment = get_post( $payment_id ); + $status = edd_get_payment_status( $payment ); + + if ( $status && 'pending' === strtolower( $status ) ) { + edd_update_payment_status( $payment_id, 'failed' ); + } + } +} +add_action( 'template_redirect', 'edd_listen_for_failed_payments' ); + +/** + * Check if a field is required + * + * @param string $field + * @since 1.7 + * @return bool +*/ +function edd_field_is_required( $field = '' ) { + $required_fields = edd_purchase_form_required_fields(); + return array_key_exists( $field, $required_fields ); +} + +/** + * Retrieve an array of banned_emails + * + * @since 2.0 + * @return array + */ +function edd_get_banned_emails() { + $banned = edd_get_option( 'banned_emails', array() ); + $emails = ! is_array( $banned ) + ? explode( "\n", $banned ) + : $banned; + + $emails = array_map( 'trim', $emails ); + + return apply_filters( 'edd_get_banned_emails', $emails ); +} + +/** + * Determines if an email is banned + * + * @since 2.0 + * @param string $email Email to check if is banned. + * @return bool + */ +function edd_is_email_banned( $email = '' ) { + + $email = trim( $email ); + if( empty( $email ) ) { + return false; + } + + $email = strtolower( $email ); + $banned_emails = edd_get_banned_emails(); + + if( ! is_array( $banned_emails ) || empty( $banned_emails ) ) { + return false; + } + + $return = false; + foreach( $banned_emails as $banned_email ) { + + $banned_email = strtolower( $banned_email ); + + if( is_email( $banned_email ) ) { + + // Complete email address + $return = ( $banned_email == $email ? true : false ); + + } elseif ( strpos( $banned_email, '.' ) === 0 ) { + + // TLD block + $return = ( substr( $email, ( strlen( $banned_email ) * -1 ) ) == $banned_email ) ? true : false; + + } else { + + // Domain block + $return = ( stristr( $email, $banned_email ) ? true : false ); + + } + + if( true === $return ) { + break; + } + } + + return apply_filters( 'edd_is_email_banned', $return, $email ); +} + +/** + * Determines if secure checkout pages are enforced + * + * @since 2.0 + * @return bool True if enforce SSL is enabled, false otherwise + */ +function edd_is_ssl_enforced() { + $ssl_enforced = edd_get_option( 'enforce_ssl', false ); + return (bool) apply_filters( 'edd_is_ssl_enforced', $ssl_enforced ); +} + +/** + * Handle redirections for SSL enforced checkouts + * + * @since 2.0 + * @return void + */ +function edd_enforced_ssl_redirect_handler() { + + if ( ! edd_is_ssl_enforced() || ! edd_is_checkout() || is_admin() || is_ssl() ) { + return; + } + + if ( edd_is_checkout() && false !== strpos( edd_get_current_page_url(), 'https://' ) ) { + return; + } + + $uri = "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; + + edd_redirect( $uri ); +} +add_action( 'template_redirect', 'edd_enforced_ssl_redirect_handler' ); + +/** + * Handle rewriting asset URLs for SSL enforced checkouts + * + * @since 2.0 + * @return void + */ +function edd_enforced_ssl_asset_handler() { + if ( ! edd_is_ssl_enforced() || ! edd_is_checkout() || is_admin() ) { + return; + } + + $filters = array( + 'post_thumbnail_html', + 'wp_get_attachment_url', + 'wp_get_attachment_image_attributes', + 'wp_get_attachment_url', + 'option_stylesheet_url', + 'option_template_url', + 'script_loader_src', + 'style_loader_src', + 'template_directory_uri', + 'stylesheet_directory_uri', + 'site_url' + ); + + $filters = apply_filters( 'edd_enforced_ssl_asset_filters', $filters ); + + foreach ( $filters as $filter ) { + add_filter( $filter, 'edd_enforced_ssl_asset_filter', 1 ); + } +} +add_action( 'template_redirect', 'edd_enforced_ssl_asset_handler' ); + +/** + * Filter filters and convert http to https + * + * @since 2.0 + * @param mixed $content + * @return mixed + */ +function edd_enforced_ssl_asset_filter( $content ) { + + if ( is_array( $content ) ) { + $content = array_map( 'edd_enforced_ssl_asset_filter', $content ); + + } else { + + // Detect if URL ends in a common domain suffix. We want to only affect assets + $extension = untrailingslashit( edd_get_file_extension( $content ) ); + $suffixes = array( + 'br', + 'ca', + 'cn', + 'com', + 'de', + 'dev', + 'edu', + 'fr', + 'in', + 'info', + 'jp', + 'local', + 'mobi', + 'name', + 'net', + 'nz', + 'org', + 'ru', + ); + + if ( ! in_array( $extension, $suffixes ) ) { + $content = str_replace( 'http:', 'https:', $content ); + } + } + + return $content; +} + +/** + * Given a number and algorithm, determine if we have a valid credit card format + * + * @since 2.4 + * @param integer $number The Credit Card Number to validate + * @return bool If the card number provided matches a specific format of a valid card + */ +function edd_validate_card_number_format( $number = 0 ) { + + $number = trim( $number ); + if ( empty( $number ) ) { + return false; + } + + if ( ! is_numeric( $number ) ) { + return false; + } + + $is_valid_format = false; + + // First check if it passes with the passed method, Luhn by default + $is_valid_format = edd_validate_card_number_format_luhn( $number ); + + // Run additional checks before we start the regexing and looping by type + $is_valid_format = apply_filters( 'edd_valiate_card_format_pre_type', $is_valid_format, $number ); + + if ( true === $is_valid_format ) { + // We've passed our method check, onto card specific checks + $card_type = edd_detect_cc_type( $number ); + $is_valid_format = ! empty( $card_type ) ? true : false; + } + + return apply_filters( 'edd_cc_is_valid_format', $is_valid_format, $number ); +} + +/** + * Validate credit card number based on the luhn algorithm + * + * @since 2.4 + * @param string $number + * @return bool + */ +function edd_validate_card_number_format_luhn( $number ) { + + // Strip any non-digits (useful for credit card numbers with spaces and hyphens) + $number = preg_replace( '/\D/', '', $number ); + + // Set the string length and parity + $length = strlen( $number ); + $parity = $length % 2; + + // Loop through each digit and do the math + $total = 0; + for ( $i = 0; $i < $length; $i++ ) { + $digit = $number[ $i ]; + + // Multiply alternate digits by two + if ( $i % 2 == $parity ) { + $digit *= 2; + + // If the sum is two digits, add them together (in effect) + if ( $digit > 9 ) { + $digit -= 9; + } + } + + // Total up the digits + $total += $digit; + } + + // If the total mod 10 equals 0, the number is valid + return ( $total % 10 == 0 ) ? true : false; + +} + +/** + * Detect credit card type based on the number and return an + * array of data to validate the credit card number + * + * @since 2.4 + * @param string $number + * @return string|bool + */ +function edd_detect_cc_type( $number ) { + + $return = false; + + $card_types = array( + array( + 'name' => 'amex', + 'pattern' => '/^3[4|7]/', + 'valid_length' => array( 15 ), + ), + array( + 'name' => 'diners_club_carte_blanche', + 'pattern' => '/^30[0-5]/', + 'valid_length' => array( 14 ), + ), + array( + 'name' => 'diners_club_international', + 'pattern' => '/^36/', + 'valid_length' => array( 14 ), + ), + array( + 'name' => 'jcb', + 'pattern' => '/^35(2[89]|[3-8][0-9])/', + 'valid_length' => array( 16 ), + ), + array( + 'name' => 'laser', + 'pattern' => '/^(6304|670[69]|6771)/', + 'valid_length' => array( 16, 17, 18, 19 ), + ), + array( + 'name' => 'visa_electron', + 'pattern' => '/^(4026|417500|4508|4844|491(3|7))/', + 'valid_length' => array( 16 ), + ), + array( + 'name' => 'visa', + 'pattern' => '/^4/', + 'valid_length' => array( 16 ), + ), + array( + 'name' => 'mastercard', + 'pattern' => '/^5[1-5]/', + 'valid_length' => array( 16 ), + ), + array( + 'name' => 'maestro', + 'pattern' => '/^(5018|5020|5038|6304|6759|676[1-3])/', + 'valid_length' => array( 12, 13, 14, 15, 16, 17, 18, 19 ), + ), + array( + 'name' => 'discover', + 'pattern' => '/^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/', + 'valid_length' => array( 16 ), + ), + ); + + $card_types = apply_filters( 'edd_cc_card_types', $card_types ); + + if ( ! is_array( $card_types ) ) { + return false; + } + + foreach ( $card_types as $card_type ){ + + if ( preg_match( $card_type['pattern'], $number ) ) { + + $number_length = strlen( $number ); + if ( in_array( $number_length, $card_type['valid_length'] ) ) { + $return = $card_type['name']; + break; + } + } + } + + return apply_filters( 'edd_cc_found_card_type', $return, $number, $card_types ); +} + +/** + * Validate credit card expiration date + * + * @since 2.4 + * @param string $exp_month + * @param string $exp_year + * @return bool + */ +function edd_purchase_form_validate_cc_exp_date( $exp_month, $exp_year ) { + + $month_name = date( 'M', mktime( 0, 0, 0, $exp_month, 10 ) ); + $expiration = strtotime( date( 't', strtotime( $month_name . ' ' . $exp_year ) ) . ' ' . $month_name . ' ' . $exp_year . ' 11:59:59PM' ); + + return $expiration >= time(); +} + +/** + * Print the payment icons on the checkout page footer. + * + * @since 3.0 + */ +function edd_print_payment_icons_on_checkout() { + + // Only load icons at EDD Checkout. + if ( ! edd_is_checkout() ) { + return; + } + + // Get payment methods. + $methods = (array) edd_get_option( 'accepted_cards', array() ); + $icons = array_keys( $methods ); + + if ( is_ssl() ) { + $icons[] = 'lock'; + } + + // Bail if no icons. + if ( empty( $icons ) ) { + return; + } + + // Output icons. + edd_print_payment_icons( $icons ); +} +add_action( 'wp_print_footer_scripts', 'edd_print_payment_icons_on_checkout', 9999 ); diff --git a/includes/checkout/pages.php b/includes/checkout/pages.php new file mode 100644 index 00000000000..bd8786449dd --- /dev/null +++ b/includes/checkout/pages.php @@ -0,0 +1,92 @@ + $order_id, + 'order' => urlencode( md5( $order_id . $order->payment_key . $order->email ) ), + ); + + return add_query_arg( $query_args, get_permalink( $page_id ) ); +} diff --git a/includes/checkout/template.php b/includes/checkout/template.php new file mode 100755 index 00000000000..1002f63a1d3 --- /dev/null +++ b/includes/checkout/template.php @@ -0,0 +1,1101 @@ +'; + if ( edd_get_cart_contents() || edd_cart_has_fees() ) : + edd_checkout_cart(); ?> +
    + +
    + +
    + +
    + '; + return ob_get_clean(); +} + +/** + * Renders the Purchase Form, hooks are provided to add to the purchase form. + * The default Purchase Form rendered displays a list of the enabled payment + * gateways, a user registration form (if enable) and a credit card info form + * if credit cards are enabled + * + * @since 1.4 + * @return string + */ +function edd_show_purchase_form() { + $payment_mode = edd_get_chosen_gateway(); + + /** + * Hooks in at the top of the purchase form. + * + * @since 1.4 + */ + do_action( 'edd_purchase_form_top' ); + + // Maybe load purchase form. + if ( edd_can_checkout() ) { + + /** + * Fires before the register/login form. + * + * @since 1.4 + */ + do_action( 'edd_purchase_form_before_register_login' ); + + $show_register_form = edd_get_option( 'show_register_form', 'none' ); + if ( ( 'registration' === $show_register_form || ( 'both' === $show_register_form && ! isset( $_GET['login'] ) ) ) && ! is_user_logged_in() ) : ?> +
    + +
    + +
    + +
    + + + 0 ) { + + // Load the credit card form and allow gateways to load their own if they wish. + if ( has_action( 'edd_' . $payment_mode . '_cc_form' ) ) { + do_action( 'edd_' . $payment_mode . '_cc_form' ); + } else { + do_action( 'edd_cc_form' ); + } + } + + /** + * Hooks in after the credit card form. + * + * @since 1.4 + */ + do_action( 'edd_purchase_form_after_cc_form' ); + + // Can't checkout. + } else { + do_action( 'edd_purchase_form_no_access' ); + } + + /** + * Hooks in at the bottom of the purchase form. + * + * @since 1.4 + */ + do_action( 'edd_purchase_form_bottom' ); +} +add_action( 'edd_purchase_form', 'edd_show_purchase_form' ); + +/** + * Shows the User Info fields in the Personal Info box, more fields can be added + * via the hooks provided. + * + * @since 1.3.3 + * @return void + */ +function edd_user_info_fields() { + $customer = EDD()->session->get( 'customer' ); + $customer = wp_parse_args( $customer, array( 'first_name' => '', 'last_name' => '', 'email' => '' ) ); + + if ( is_user_logged_in() ) { + $user_data = get_userdata( get_current_user_id() ); + foreach ( $customer as $key => $field ) { + if ( 'email' === $key && empty( $field ) ) { + $customer[ $key ] = $user_data->user_email; + } elseif ( empty( $field ) ) { + $customer[ $key ] = $user_data->$key; + } + } + } + + $customer = array_map( 'sanitize_text_field', $customer ); + ?> +
    + + +

    + + + /> +

    + +

    + + + aria-describedby="edd-first-description" /> +

    +

    + + + aria-describedby="edd-last-description"/> +

    + + +
    + + + + +
    + + +
    + 'lock', + 'width' => 16, + 'height' => 16, + 'title' => __( 'Secure SSL encrypted payment', 'easy-digital-downloads' ), + 'classes' => array( 'edd-icon', 'edd-icon-lock' ) + ) + ); + ?> + +
    + +

    + + + +

    +

    + + + +

    +

    + + + +

    + +

    + + + + / + +

    + + +
    + session->get( 'customer' ); + + $customer = wp_parse_args( $customer, array( + 'address' => array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'zip' => '', + 'state' => '', + 'country' => '', + ), + ) ); + + $customer['address'] = array_map( 'sanitize_text_field', $customer['address'] ); + + if ( $logged_in ) { + $user_address = edd_get_customer_address(); + + foreach ( $customer['address'] as $key => $field ) { + if ( empty( $field ) && ! empty( $user_address[ $key ] ) ) { + $customer['address'][ $key ] = $user_address[ $key ]; + } else { + $customer['address'][ $key ] = ''; + } + } + } + + /** + * Filter the billing address details that will be pre-populated on the checkout form.. + * + * @since 2.8 + * + * @param array $address The customer address. + * @param array $customer The customer data from the session + */ + $customer['address'] = apply_filters( 'edd_checkout_billing_details_address', $customer['address'], $customer ); + + ob_start(); ?> +
    + + +

    + + + /> +

    +

    + + + /> +

    +

    + + + /> +

    +

    + + + /> +

    +

    + + + +

    +

    + + + + + + + +

    + + +
    + +
    + + +

    + +

    + + + + +
    + + +

    + + + +

    +

    + + + +

    +

    + + + +

    + +
    + + + + + + + + +
    + +
    + +

    + + +

    + + + + +

    + + +

    + +

    + + +

    + + +
    + +
    + + + +
    + + +
    + + +
    + $gateway ) { + $label = apply_filters( 'edd_gateway_checkout_label_' . $gateway_id, $gateway['checkout_label'] ); + $checked = checked( $gateway_id, $chosen_gateway, false ); + $checked_class = $checked ? 'edd-gateway-option-selected' : ''; + $nonce = ' data-' . esc_attr( $gateway_id ) . '-nonce="' . wp_create_nonce( 'edd-gateway-selected-' . esc_attr( $gateway_id ) ) .'"'; + + echo ''; + } + + do_action( 'edd_payment_mode_after_gateways' ); + ?> +
    + + +
    + +
    +

    + +

    +
    + + +
    + + +
    +
    + + '; + + foreach ( $payment_methods as $key => $option ) { + echo edd_get_payment_image( $key, $option ); + } + + echo ''; +} +add_action( 'edd_payment_mode_top', 'edd_show_payment_icons' ); +add_action( 'edd_checkout_form_top', 'edd_show_payment_icons' ); + +/** + * Renders the Discount Code field which allows users to enter a discount code. + * This field is only displayed if there are any active discounts on the site else + * it's not displayed. + * + * @since 1.2.2 + * @return void +*/ +function edd_discount_field() { + if ( isset( $_GET['payment-mode'] ) && edd_is_ajax_disabled() ) { + return; // Only show before a payment method has been selected if ajax is disabled + } + + if ( ! edd_is_checkout() ) { + return; + } + + if ( edd_has_active_discounts() && edd_get_cart_total() ) : + $color = edd_get_button_color_class(); + $style = edd_get_option( 'button_style', 'button' ); ?> +
    + +

    + + + + + + + + +

    +
    + +
    + + + + +
    + + +
    + + +
    + + +
    +
    + + + +
    + + + +
    + + +
    + +
    + + +
    + + +
    + + +

    + + +

    + +
    + + + + + + + + + +

    + +
    + + + + + + + + + + + + + order_id ) ) { + $order = edd_get_order_by( 'id', $order_item->order_id ); + $cart = edd_get_payment_meta_cart_details( $order->id, true ); + $item = $cart[ $item->cart_index ]; + } + $ret = apply_filters( 'edd_receipt_show_download_files', $ret, $item_id, $receipt_args, $item ); + } + + // If the $order_item is an array, get the order item object instead. + if ( is_array( $order_item ) && ! empty( $order_item['order_item_id'] ) ) { + $order_item = edd_get_order_item( $order_item['order_item_id'] ); + } + + /** + * Modifies whether the receipt should show download files. + * + * @since 3.0 + * @param bool $ret True if the download files should be shown. + * @param int $item_id The download ID. + * @param array $receipt_args Args specified in the [edd_receipt] shortcode. + * @param \EDD\Orders\Order_Item $item The order item object. + */ + return apply_filters( 'edd_order_receipt_show_download_files', $ret, $item_id, $receipt_args, $order_item ); +} diff --git a/includes/class-base-object.php b/includes/class-base-object.php new file mode 100644 index 00000000000..393f9fe6afb --- /dev/null +++ b/includes/class-base-object.php @@ -0,0 +1,131 @@ +set_vars( $args ); + } + + /** + * Magic isset'ter for immutability. + * + * @since 3.0 + * + * @param string $key + * @return mixed + */ + public function __isset( $key = '' ) { + + // No more uppercase ID properties ever + if ( 'ID' === $key ) { + $key = 'id'; + } + + // Class method to try and call + $method = "get_{$key}"; + + // Return property if exists + if ( method_exists( $this, $method ) ) { + return true; + + // Return get method results if exists + } elseif ( property_exists( $this, $key ) ) { + return true; + } + + // Return false if not exists + return false; + } + + /** + * Magic getter for immutability. + * + * @since 3.0 + * + * @param string $key + * @return mixed + */ + public function __get( $key = '' ) { + + // No more uppercase ID properties ever + if ( 'ID' === $key ) { + $key = 'id'; + } + + // Class method to try and call + $method = "get_{$key}"; + + // Return property if exists + if ( method_exists( $this, $method ) ) { + return call_user_func( array( $this, $method ) ); + + // Return get method results if exists + } elseif ( property_exists( $this, $key ) ) { + return $this->{$key}; + } + + // Return null if not exists + return null; + } + + /** + * Converts the given object to an array. + * + * @since 3.0 + * + * @return array Array version of the given object. + */ + public function to_array() { + return get_object_vars( $this ); + } + + /** + * Set class variables from arguments. + * + * @since 3.0 + * @param array $args + */ + protected function set_vars( $args = array() ) { + + // Bail if empty or not an array + if ( empty( $args ) ) { + return; + } + + // Cast to an array + if ( ! is_array( $args ) ) { + $args = (array) $args; + } + + // Set all properties + foreach ( $args as $key => $value ) { + $this->{$key} = $value; + } + } +} \ No newline at end of file diff --git a/includes/class-component.php b/includes/class-component.php new file mode 100644 index 00000000000..189b23a4b20 --- /dev/null +++ b/includes/class-component.php @@ -0,0 +1,148 @@ + false, + 'table' => false, + 'query' => false, + 'object' => false, + 'meta' => false + ); + + /** + * Construct an EDD component + * + * @since 3.0 + * @param array $args + */ + public function __construct( $args = array() ) { + parent::__construct( $args ); + } + + /** + * Return an interface object + * + * @since 3.0 + * + * @param string $name + * @return object + */ + public function get_interface( $name = '' ) { + return isset( $this->interfaces[ $name ] ) + ? $this->interfaces[ $name ] + : false; + } + + /** + * Setup an EDD component based on parsing in constructor + * + * @since 3.0 + * @param array $args + */ + protected function set_vars( $args = array() ) { + + // Get the interface keys + $keys = array_keys( $this->interface_keys ); + + // Loop through args... + foreach ( $args as $key => $value ) { + + // Set arg as a Component Interface + if ( in_array( $key, $keys, true ) && class_exists( $value ) ) { + $this->interfaces[ $key ] = new $value; + + // Set arg as a Component property + } else { + $this->{$key} = $value; + } + } + } +} diff --git a/includes/class-easy-digital-downloads.php b/includes/class-easy-digital-downloads.php new file mode 100644 index 00000000000..33190275c6b --- /dev/null +++ b/includes/class-easy-digital-downloads.php @@ -0,0 +1,912 @@ +setup_constants(); + self::$instance->setup_files(); + self::$instance->setup_application(); + self::$instance->setup_compat(); + + // APIs + self::$instance->roles = new EDD_Roles(); + self::$instance->fees = new EDD_Fees(); + self::$instance->api = new EDD_API(); + self::$instance->debug_log = new EDD_Logging(); + self::$instance->utils = new EDD\Utilities(); + self::$instance->session = new EDD_Session(); + self::$instance->html = new EDD_HTML_Elements(); + self::$instance->emails = new EDD_Emails(); + self::$instance->email_tags = new EDD_Email_Template_Tags(); + self::$instance->email_summary_cron = new EDD_Email_Summary_Cron(); + self::$instance->payment_stats = new EDD_Payment_Stats(); + self::$instance->cart = new EDD_Cart(); + self::$instance->structured_data = new EDD\Structured_Data(); + self::$instance->notifications = new \EDD\Database\NotificationsDB(); + self::$instance->extensionRegistry = new \EDD\Extensions\ExtensionRegistry(); + + // Admin APIs + if ( is_admin() ) { + self::$instance->notices = new EDD_Notices(); + self::$instance->email_summary_admin = new EDD_Email_Summary_Admin(); + } + + // Parachute + self::$instance->backcompat_globals(); + + self::$instance->registerApiEndpoints(); + + // Check if the pro code is present. + if ( class_exists( '\\EDD\\Pro\\Core' ) ) { + self::$instance->pro = true; + if ( edd_is_pro() ) { + new EDD\Pro\Core(); + } + } + if ( ! edd_is_pro() && class_exists( '\\EDD\\Lite\\Core' ) ) { + new EDD\Lite\Core(); + } + + self::$instance->tracking = edd_is_pro() ? new EDD\Pro\Telemetry\Tracking() : new EDD_Tracking(); + + // Return the instance + return self::$instance; + } + + /** + * Throw error on object clone. + * + * The whole idea of the singleton design pattern is that there is a single + * object therefore, we don't want the object to be cloned. + * + * @since 1.6 + * @access protected + * @return void + */ + public function __clone() { + // Cloning instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'easy-digital-downloads' ), '1.6' ); + } + + /** + * Disable un-serializing of the class. + * + * @since 1.6 + * @access protected + * @return void + */ + public function __wakeup() { + // Unserializing instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'easy-digital-downloads' ), '1.6' ); + } + + /** + * Backwards compatibility for some database properties + * + * This is probably still not working right, so don't count on it yet. + * + * @since 3.0 + * + * @param string $key + * @return mixed + */ + public function __get( $key = '' ) { + switch ( $key ) { + case 'customers' : + return new EDD\Compat\Customer(); + case 'customermeta' : + case 'customer_meta' : + return new EDD\Compat\CustomerMeta(); + + default : + return isset( $this->{$key} ) + ? $this->{$key} + : null; + } + } + + /** + * Whether the current install is a pro install. + * + * @since 3.1.1 + * @return bool + */ + public function is_pro() { + return $this->pro; + } + + /** + * Return whether the main loading class has been instantiated or not. + * + * @since 3.0 + * + * @return boolean True if instantiated. False if not. + */ + private static function is_instantiated() { + + // Return true if instance is correct class + if ( ! empty( self::$instance ) && ( self::$instance instanceof Easy_Digital_Downloads ) ) { + return true; + } + + // Return false if not instantiated correctly + return false; + } + + /** + * Setup the singleton instance + * + * @since 3.0 + * @param string $file + */ + private static function setup_instance( $file = '' ) { + if ( empty( $file ) && defined( EDD_PLUGIN_FILE ) ) { + $file = EDD_PLUGIN_FILE; + } + self::$instance = new Easy_Digital_Downloads(); + self::$instance->file = $file; + } + + /** + * Setup plugin constants. + * + * @access private + * @since 1.4 + * @return void + */ + private function setup_constants() { + + // Plugin version. + if ( ! defined( 'EDD_VERSION' ) ) { + define( 'EDD_VERSION', '3.1.5' ); + } + + // Make sure CAL_GREGORIAN is defined. + if ( ! defined( 'CAL_GREGORIAN' ) ) { + define( 'CAL_GREGORIAN', 1 ); + } + } + + /** + * Include required files. + * + * @access private + * @since 1.4 + * @return void + */ + private function setup_files() { + $this->include_options(); + $this->include_utilities(); + $this->include_reports(); + $this->include_components(); + $this->include_backcompat(); + $this->include_objects(); + $this->include_functions(); + + // Admin + if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) { + $this->include_admin(); + } else { + $this->include_frontend(); + } + } + + /** + * Setup backwards compatibility hooks + * + * This method exists to setup the bridges between EDD versions, most + * notably between versions less than 2.9 and greater than 3.0. + * + * Compatibility classes are not set up during EDD uninstall in order to allow us to use WordPress functions + * to cleanly delete the old custom post types from pre-3.0. + * + * @access private + * @since 3.0 + * @return void + */ + private function setup_compat() { + if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) || plugin_basename( $this->file ) !== WP_UNINSTALL_PLUGIN ) { + new EDD\Compat\Discount(); + new EDD\Compat\Customer(); + new EDD\Compat\Log(); + new EDD\Compat\Payment(); + new EDD\Compat\Tax(); + new EDD\Compat\Template(); + } + } + + /** + * Setup the rest of the application + * + * @since 3.0 + */ + private function setup_application() { + add_action( 'plugins_loaded', 'edd_setup_components', 100 ); + + $GLOBALS['edd_options'] = edd_get_settings(); + + // Load Amazon Payments. + PayWithAmazon\EDD_Amazon_Payments::getInstance(); + + // Load cache helper. + new EDD_Cache_Helper(); + } + + /** Includes **************************************************************/ + + /** + * Setup all of the custom database tables + * + * This method invokes all of the classes for each custom database table, + * and returns them in an array for easier testing. + * + * In a normal request, this method is called extremely early in EDD's load + * order, to ensure these tables have been created & upgraded before any + * other utility occurs on them (query, migration, etc...) + * + * @access public + * @since 3.0 + * @return array + */ + private function include_components() { + + // Component helpers are loaded before everything + require_once EDD_PLUGIN_DIR . 'includes/interface-edd-exception.php'; + require_once EDD_PLUGIN_DIR . 'includes/component-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-component.php'; + + // Database Engine + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-base.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-column.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-schema.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-query.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-row.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-table.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-date.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/engine/class-compare.php'; + + // Database Schemas + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-adjustments.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-customer-addresses.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-customer-email-addresses.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-customers.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-logs.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-logs-api-requests.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-logs-file-downloads.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-notes.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-orders.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-order-addresses.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-order-adjustments.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-order-items.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/schemas/class-order-transactions.php'; + + // Database Objects + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-adjustment.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-customer.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-customer-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-customer-email-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-log.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-log-api-request.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-log-file-download.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-note.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-order.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-order-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-order-adjustment.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-order-item.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/rows/class-order-transaction.php'; + + // Database Tables + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-adjustments.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-customer-addresses.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-customer-email-addresses.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-customers.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-logs.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-logs-api-requests.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-logs-api-request-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-logs-file-downloads.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-logs-file-download-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-notes.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-orders.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-order-addresses.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-order-adjustments.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-order-adjustment-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-order-items.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-order-transactions.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-customer-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-adjustment-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-log-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-note-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-order-meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/tables/class-order-item-meta.php'; + + // Database Table Query Interfaces + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-adjustment.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-customer.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-customer-email-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-customer-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-log.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-log-api-request.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-log-file-download.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-note.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-order.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-order-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-order-adjustment.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-order-item.php'; + require_once EDD_PLUGIN_DIR . 'includes/database/queries/class-order-transaction.php'; + + // Old Database Components + require_once EDD_PLUGIN_DIR . 'includes/class-edd-db.php'; + } + + /** + * Setup all EDD settings & options + * + * @since 3.0 + */ + private function include_options() { + require_once EDD_PLUGIN_DIR . 'includes/admin/settings/register-settings.php'; + } + + /** + * Setup utilities + * + * @since 3.0 + */ + private function include_utilities() { + require_once EDD_PLUGIN_DIR . 'includes/class-utilities.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-base-object.php'; + } + + /** + * Setup all EDD settings & options + * + * @since 3.0 + */ + private function include_reports() { + require_once EDD_PLUGIN_DIR . 'includes/reports/class-init.php'; + } + + /** + * Setup backwards compatibility + * + * @since 3.0 + */ + private function include_backcompat() { + + // PHP functions + require_once EDD_PLUGIN_DIR . 'includes/compat-functions.php'; + + // Backwards Compatibility + require_once EDD_PLUGIN_DIR . 'includes/compat/class-base.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-customer.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-customermeta.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-discount.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-discount-query.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-log.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-payment.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-tax.php'; + require_once EDD_PLUGIN_DIR . 'includes/compat/class-template.php'; + + // Original Classes + require_once EDD_PLUGIN_DIR . 'includes/class-edd-customer.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-customer-query.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-discount.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-download.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-cache-helper.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-register-meta.php'; + + // Classes + require_once EDD_PLUGIN_DIR . 'includes/class-edd-cron.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-fees.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-html-elements.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-license-handler.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-logging.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-session.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-stats.php'; + require_once EDD_PLUGIN_DIR . 'includes/class-edd-roles.php'; + + // Deprecated Functions + if ( file_exists( EDD_PLUGIN_DIR . 'includes/deprecated-functions.php' ) ) { + require_once EDD_PLUGIN_DIR . 'includes/deprecated-functions.php'; + } + require_once EDD_PLUGIN_DIR . 'includes/deprecated-hooks.php'; + } + + /** + * Setup objects + * + * @since 3.0 + */ + private function include_objects() { + + // CLI + if ( defined( 'WP_CLI' ) && WP_CLI ) { + require_once EDD_PLUGIN_DIR . 'includes/class-edd-cli.php'; + } + + // Traits + require_once EDD_PLUGIN_DIR . 'includes/traits/trait-refundable-item.php'; + + // Adjustments + require_once EDD_PLUGIN_DIR . 'includes/adjustments/class-adjustment.php'; + require_once EDD_PLUGIN_DIR . 'includes/adjustments/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/adjustments/meta.php'; + + // API + require_once EDD_PLUGIN_DIR . 'includes/api/class-edd-api.php'; + require_once EDD_PLUGIN_DIR . 'includes/api/v3/Endpoint.php'; + + // Checkout + require_once EDD_PLUGIN_DIR . 'includes/checkout/template.php'; + require_once EDD_PLUGIN_DIR . 'includes/checkout/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/checkout/pages.php'; + + // Customers + require_once EDD_PLUGIN_DIR . 'includes/customers/class-customer-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/customers/class-customer-email-address.php'; + + // Cart + require_once EDD_PLUGIN_DIR . 'includes/cart/class-edd-cart.php'; + require_once EDD_PLUGIN_DIR . 'includes/cart/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/cart/template.php'; + require_once EDD_PLUGIN_DIR . 'includes/cart/actions.php'; + + // Currency + require_once EDD_PLUGIN_DIR . 'includes/currency/functions.php'; + + // Gateways + require_once EDD_PLUGIN_DIR . 'includes/gateways/actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/gateways/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/gateways/amazon-payments.php'; + require_once EDD_PLUGIN_DIR . 'includes/gateways/paypal-standard.php'; + require_once EDD_PLUGIN_DIR . 'includes/gateways/paypal/paypal.php'; + require_once EDD_PLUGIN_DIR . 'includes/gateways/manual.php'; + + $stripe = EDD_PLUGIN_DIR . 'includes/gateways/stripe/edd-stripe.php'; + + if ( file_exists( $stripe ) ) { + require_once( $stripe ); + } + + // Logs + require_once EDD_PLUGIN_DIR . 'includes/logs/api-request-log/class-api-request-log.php'; + require_once EDD_PLUGIN_DIR . 'includes/logs/api-request-log/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/logs/api-request-log/meta.php'; + + require_once EDD_PLUGIN_DIR . 'includes/logs/file-download-log/class-file-download-log.php'; + require_once EDD_PLUGIN_DIR . 'includes/logs/file-download-log/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/logs/file-download-log/meta.php'; + + require_once EDD_PLUGIN_DIR . 'includes/logs/log/class-log.php'; + require_once EDD_PLUGIN_DIR . 'includes/logs/log/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/logs/log/meta.php'; + + // Notes + require_once EDD_PLUGIN_DIR . 'includes/notes/class-note.php'; + require_once EDD_PLUGIN_DIR . 'includes/notes/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/notes/meta.php'; + + // Orders + require_once EDD_PLUGIN_DIR . 'includes/orders/classes/class-order.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/classes/class-order-address.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/classes/class-order-adjustment.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/classes/class-order-item.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/classes/class-order-transaction.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/classes/class-refund-validator.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/types.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/orders.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/meta.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/items.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/refunds.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/addresses.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/adjustments.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/transactions.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/ui.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/transitions.php'; + require_once EDD_PLUGIN_DIR . 'includes/orders/functions/statuses.php'; + + // Payments + require_once EDD_PLUGIN_DIR . 'includes/payments/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/payments/actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/payments/class-payment-stats.php'; + require_once EDD_PLUGIN_DIR . 'includes/payments/class-payments-query.php'; + require_once EDD_PLUGIN_DIR . 'includes/payments/class-edd-payment.php'; + + // Emails + require_once EDD_PLUGIN_DIR . 'includes/emails/class-edd-emails.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/class-edd-email-tags.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/tags.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/tags-inserter.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/template.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/email-summary/class-edd-email-summary-cron.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/email-summary/class-edd-email-summary.php'; + require_once EDD_PLUGIN_DIR . 'includes/emails/email-summary/class-edd-email-summary-blurb.php'; + + // Stats + require_once EDD_PLUGIN_DIR . 'includes/class-stats.php'; + + // Downloads + require_once EDD_PLUGIN_DIR . 'includes/models/Download.php'; + + // Blocks + $blocks = EDD_PLUGIN_DIR . 'includes/blocks/edd-blocks.php'; + + if ( file_exists( $blocks ) ) { + require_once $blocks; + } + } + + /** + * Setup functions + * + * @since 3.0 + */ + private function include_functions() { + require_once EDD_PLUGIN_DIR . 'includes/actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/mime-types.php'; + require_once EDD_PLUGIN_DIR . 'includes/formatting.php'; + require_once EDD_PLUGIN_DIR . 'includes/widgets.php'; + require_once EDD_PLUGIN_DIR . 'includes/scripts.php'; + require_once EDD_PLUGIN_DIR . 'includes/post-types.php'; + require_once EDD_PLUGIN_DIR . 'includes/plugin-compatibility.php'; + require_once EDD_PLUGIN_DIR . 'includes/error-tracking.php'; + require_once EDD_PLUGIN_DIR . 'includes/ajax-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/template-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/template-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/country-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/extensions/licensing-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/date-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/misc-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/discount-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/download-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/downloads/recalculations.php'; + require_once EDD_PLUGIN_DIR . 'includes/customer-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/customers/customer-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/privacy-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/utils/class-tokenizer.php'; + require_once EDD_PLUGIN_DIR . 'includes/user-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/query-filters.php'; + require_once EDD_PLUGIN_DIR . 'includes/tax-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/refund-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/process-purchase.php'; + require_once EDD_PLUGIN_DIR . 'includes/users/login.php'; + require_once EDD_PLUGIN_DIR . 'includes/users/lost-password.php'; + require_once EDD_PLUGIN_DIR . 'includes/users/register.php'; + require_once EDD_PLUGIN_DIR . 'includes/shortcodes.php'; + require_once EDD_PLUGIN_DIR . 'includes/install.php'; + require_once EDD_PLUGIN_DIR . 'includes/upgrades/functions.php'; + + // Admin files to load globally (cron, bar, etc...) + require_once EDD_PLUGIN_DIR . 'includes/admin/tracking.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/admin-bar.php'; + } + + /** + * Setup administration + * + * @since 3.0 + */ + private function include_admin() { + require_once EDD_PLUGIN_DIR . 'includes/admin/admin-footer.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/admin-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/class-edd-notices.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/class-edd-heartbeat.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/class-list-table.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/class-sections.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/admin-pages.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/dashboard-widgets.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/thickbox.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/upload-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/downloads/dashboard-columns.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/adjustments/adjustment-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/customers/customers.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/customers/customer-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/customers/customer-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/notes/note-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/notes/note-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/downloads/metabox.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/downloads/contextual-help.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/discounts/contextual-help.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/discounts/discount-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/discounts/discount-codes.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/emails/email-summary/class-edd-email-summary-admin.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/import/import-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/import/import-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/refunds.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/orders.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/class-order-sections.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/payments-history.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/payments/contextual-help.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/contextual-help.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/class-reports-sections.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/export/export-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/reports.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/reports-callbacks.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/class-edd-graph.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/class-edd-pie-graph.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/reporting/graphing.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/settings/contextual-help.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/settings/display-settings.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/tools.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/plugins.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/deprecated-upgrade-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/downgrades.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/upgrade-functions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/upgrades.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/upgrade-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/tools/tools-actions.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/settings/settings-compatibility.php'; + require_once EDD_PLUGIN_DIR . 'includes/admin/admin-deprecated-functions.php'; + + require_once EDD_PLUGIN_DIR . 'includes/libraries/class-persistent-dismissible.php'; + } + + /** + * Setup front-end specific code + * + * @since 3.0 + */ + private function include_frontend() { + require_once EDD_PLUGIN_DIR . 'includes/process-download.php'; + require_once EDD_PLUGIN_DIR . 'includes/theme-compatibility.php'; + } + + /** + * Backwards compatibility for old global values + * + * @since 3.0 + */ + private function backcompat_globals() { + + // The $edd_logs global + $GLOBALS['edd_logs'] = self::$instance->debug_log; + } + + /** + * Registers REST API endpoints. + * + * @todo move this somewhere better + * + * @since 2.11.4 + */ + private function registerApiEndpoints() { + add_action( 'rest_api_init', function() { + $endpoints = array( + '\\EDD\\API\\v3\\Notifications', + ); + + foreach( $endpoints as $endpointClassName ) { + $endpointNamePieces = explode( '\\', $endpointClassName ); + $endpointName = end( $endpointNamePieces ); + + require_once EDD_PLUGIN_DIR . 'includes/api/v3/' . $endpointName . '.php'; + + if ( class_exists( $endpointClassName ) ) { + $endpoint = new $endpointClassName(); + $endpoint->register(); + } + } + } ); + } +} +endif; // End if class_exists check. + +/** + * Returns the instance of Easy_Digital_Downloads. + * + * The main function responsible for returning the one true Easy_Digital_Downloads + * instance to functions everywhere. + * + * Use this function like you would a global variable, except without needing + * to declare the global. + * + * Example: + * + * @since 1.4 + * @return Easy_Digital_Downloads The one true Easy_Digital_Downloads instance. + */ +function EDD() { + return Easy_Digital_Downloads::instance(); +} diff --git a/includes/class-edd-cache-helper.php b/includes/class-edd-cache-helper.php new file mode 100755 index 00000000000..5e9226ff552 --- /dev/null +++ b/includes/class-edd-cache-helper.php @@ -0,0 +1,144 @@ +post_name; + } + + if ( ! is_null( $success_page ) ) { + $page_uris[] = '/' . $success_page->post_name; + } + + set_transient( 'edd_cache_excluded_uris', $page_uris ); + } + + if ( is_array( $page_uris ) ) { + foreach ( $page_uris as $uri ) { + if ( strstr( $_SERVER['REQUEST_URI'], $uri ) ) { + $this->nocache(); + break; + } + } + } + } + + /** + * Set nocache constants and headers. + * + * @since 1.7 + * @access private + */ + private function nocache() { + if ( ! defined( 'DONOTCACHEPAGE' ) ) { + define( 'DONOTCACHEPAGE', 'true' ); + } + + nocache_headers(); + } + + /** + * Admin notices. + * + * @since 1.7 + */ + public function notices() { + + // W3 Total Cache. + if ( function_exists( 'w3tc_pgcache_flush' ) && function_exists( 'w3_instance' ) ) { + $config = w3_instance( 'W3_Config' ); + $enabled = $config->get_integer( 'dbcache.enabled' ); + $settings = $config->get_array( 'dbcache.reject.sql' ); + + if ( $enabled && ! in_array( '_wp_session_', $settings, true ) ) { + ?> +
    +

    + database caching to work with Easy Digital Downloads you must add _wp_session_ to the "Ignored query stems" option in W3 Total Cache settings here.', 'easy-digital-downloads' ), + esc_url( admin_url( 'admin.php?page=w3tc_dbcache' ) ) + ); + ?> +

    +
    + api = new EDD_API(); + edd_do_automatic_upgrades(); + } + + + /** + * Get EDD details + * + * ## OPTIONS + * + * None. Returns basic info regarding your EDD instance. + * + * ## EXAMPLES + * + * wp edd details + * + * @param array $args + * @param array $assoc_args + */ + public function details( $args, $assoc_args ) { + $symlink_file_downloads = edd_get_option( 'symlink_file_downloads', false ); + $purchase_page = edd_get_option( 'purchase_page', '' ); + $success_page = edd_get_option( 'success_page', '' ); + $failure_page = edd_get_option( 'failure_page', '' ); + + WP_CLI::line( sprintf( __( 'You are running EDD version: %s', 'easy-digital-downloads' ), EDD_VERSION ) ); + WP_CLI::line( "\n" . sprintf( __( 'Test mode is: %s', 'easy-digital-downloads' ), ( edd_is_test_mode() ? __( 'Enabled', 'easy-digital-downloads' ) : __( 'Disabled', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( sprintf( __( 'AJAX is: %s', 'easy-digital-downloads' ), ( edd_is_ajax_enabled() ? __( 'Enabled', 'easy-digital-downloads' ) : __( 'Disabled', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( sprintf( __( 'Guest checkouts are: %s', 'easy-digital-downloads' ), ( edd_no_guest_checkout() ? __( 'Disabled', 'easy-digital-downloads' ) : __( 'Enabled', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( sprintf( __( 'Symlinks are: %s', 'easy-digital-downloads' ), ( apply_filters( 'edd_symlink_file_downloads', isset( $symlink_file_downloads ) ) && function_exists( 'symlink' ) ? __( 'Enabled', 'easy-digital-downloads' ) : __( 'Disabled', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( "\n" . sprintf( __( 'Checkout page is: %s', 'easy-digital-downloads' ), ( ! edd_get_option( 'purchase_page', false ) ) ? __( 'Valid', 'easy-digital-downloads' ) : __( 'Invalid', 'easy-digital-downloads' ) ) ); + WP_CLI::line( sprintf( __( 'Checkout URL is: %s', 'easy-digital-downloads' ), ( ! empty( $purchase_page ) ? get_permalink( $purchase_page ) : __( 'Undefined', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( sprintf( __( 'Success URL is: %s', 'easy-digital-downloads' ), ( ! empty( $success_page ) ? get_permalink( $success_page ) : __( 'Undefined', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( sprintf( __( 'Failure URL is: %s', 'easy-digital-downloads' ), ( ! empty( $failure_page ) ? get_permalink( $failure_page ) : __( 'Undefined', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( sprintf( __( 'Downloads slug is: %s', 'easy-digital-downloads' ), ( defined( 'EDD_SLUG' ) ? '/' . EDD_SLUG : '/downloads' ) ) ); + WP_CLI::line( "\n" . sprintf( __( 'Taxes are: %s', 'easy-digital-downloads' ), ( edd_use_taxes() ? __( 'Enabled', 'easy-digital-downloads' ) : __( 'Disabled', 'easy-digital-downloads' ) ) ) ); + WP_CLI::line( sprintf( __( 'Tax rate is: %s', 'easy-digital-downloads' ), edd_get_formatted_tax_rate() ) ); + + $rates = edd_get_tax_rates(); + if ( ! empty( $rates ) ) { + foreach ( $rates as $rate ) { + WP_CLI::line( sprintf( __( 'Country: %s, State: %s, Rate: %s', 'easy-digital-downloads' ), $rate['country'], $rate['state'], $rate['rate'] ) ); + } + } + } + + + /** + * Get stats for your EDD site + * + * ## OPTIONS + * + * --product=: The ID of a specific product to retrieve stats for, or all + * --date=[range|this_month|last_month|today|yesterday|this_quarter|last_quarter|this_year|last_year]: A specific + * date range to retrieve stats for + * --startdate=: The start date of a date range to retrieve stats for + * --enddate=: The end date of a date range to retrieve stats for + * + * ## EXAMPLES + * + * wp edd stats --date=this_month + * wp edd stats --start-date=01/02/2014 --end-date=02/23/2014 + * wp edd stats --date=last_year + * wp edd stats --date=last_year --product=15 + */ + public function stats( $args, $assoc_args ) { + + $stats = new EDD_Payment_Stats(); + $date = isset( $assoc_args ) && array_key_exists( 'date', $assoc_args ) ? $assoc_args['date'] : false; + $start_date = isset( $assoc_args ) && array_key_exists( 'startdate', $assoc_args ) ? $assoc_args['startdate'] : false; + $end_date = isset( $assoc_args ) && array_key_exists( 'enddate', $assoc_args ) ? $assoc_args['enddate'] : false; + $download = isset( $assoc_args ) && array_key_exists( 'product', $assoc_args ) ? $assoc_args['product'] : 0; + + if ( ! empty( $date ) ) { + $start_date = $date; + $end_date = false; + } elseif ( empty( $date ) && empty( $start_date ) ) { + $start_date = 'this_month'; + $end_date = false; + } + + $earnings = $stats->get_earnings( $download, $start_date, $end_date ); + $sales = $stats->get_sales( $download, $start_date, $end_date ); + + WP_CLI::line( sprintf( __( 'Earnings: %s', 'easy-digital-downloads' ), $earnings ) ); + WP_CLI::line( sprintf( __( 'Sales: %s', 'easy-digital-downloads' ), $sales ) ); + + } + + + /** + * Get the products currently posted on your EDD site + * + * ## OPTIONS + * + * --id=: A specific product ID to retrieve + * + * + * ## EXAMPLES + * + * wp edd products --id=103 + */ + public function products( $args, $assoc_args ) { + $product_id = isset( $assoc_args ) && array_key_exists( 'id', $assoc_args ) ? absint( $assoc_args['id'] ) : false; + $products = $this->api->get_products( $product_id ); + + if ( isset( $products['error'] ) ) { + WP_CLI::error( $products['error'] ); + } + + if ( empty( $products ) ) { + WP_CLI::error( __( 'No Downloads found', 'easy-digital-downloads' ) ); + + return; + } + + foreach ( $products['products'] as $product ) { + $categories = ''; + $tags = ''; + $pricing = array(); + + if ( is_array( $product['info']['category'] ) ) { + $categories = array(); + foreach ( $product['info']['category'] as $category ) { + $categories[] = $category->name; + } + + $categories = implode( ', ', $categories ); + } + + if ( is_array( $product['info']['tags'] ) ) { + $tags = array(); + foreach ( $product['info']['tags'] as $tag ) { + $tags[] = $tag->name; + } + + $tags = implode( ', ', $tags ); + } + + foreach ( $product['pricing'] as $price => $value ) { + if ( 'amount' !== $price ) { + $price = $price . ' - '; + } + + $pricing[] = $price . ': ' . edd_format_amount( $value ) . ' ' . edd_get_currency(); + } + + $pricing = implode( ', ', $pricing ); + + WP_CLI::line( WP_CLI::colorize( '%G' . $product['info']['title'] . '%N' ) ); + WP_CLI::line( sprintf( __( 'ID: %d', 'easy-digital-downloads' ), $product['info']['id'] ) ); + WP_CLI::line( sprintf( __( 'Status: %s', 'easy-digital-downloads' ), $product['info']['status'] ) ); + WP_CLI::line( sprintf( __( 'Posted: %s', 'easy-digital-downloads' ), $product['info']['create_date'] ) ); + WP_CLI::line( sprintf( __( 'Categories: %s', 'easy-digital-downloads' ), $categories ) ); + WP_CLI::line( sprintf( __( 'Tags: %s', 'easy-digital-downloads' ), ( is_array( $tags ) ? '' : $tags ) ) ); + WP_CLI::line( sprintf( __( 'Pricing: %s', 'easy-digital-downloads' ), $pricing ) ); + WP_CLI::line( sprintf( __( 'Sales: %s', 'easy-digital-downloads' ), $product['stats']['total']['sales'] ) ); + WP_CLI::line( sprintf( __( 'Earnings: %s', 'easy-digital-downloads' ), edd_format_amount( $product['stats']['total']['earnings'] ) ) ) . ' ' . edd_get_currency(); + WP_CLI::line( '' ); + WP_CLI::line( sprintf( __( 'Slug: %s', 'easy-digital-downloads' ), $product['info']['slug'] ) ); + WP_CLI::line( sprintf( __( 'Permalink: %s', 'easy-digital-downloads' ), $product['info']['link'] ) ); + + if ( array_key_exists( 'files', $product ) ) { + WP_CLI::line( '' ); + WP_CLI::line( __( 'Download Files:', 'easy-digital-downloads' ) ); + + foreach ( $product['files'] as $file ) { + WP_CLI::line( ' ' . sprintf( __( 'File: %s (%s)', 'easy-digital-downloads' ), $file['name'], $file['file'] ) ); + + if ( isset( $file['condition'] ) && 'all' !== $file['condition'] ) { + WP_CLI::line( ' ' . sprintf( __( 'Price Assignment: %s', 'easy-digital-downloads' ), $file['condition'] ) ); + } + } + } + + WP_CLI::line( '' ); + } + + } + + + /** + * Get the customers currently on your EDD site. Can also be used to create customers records + * + * ## OPTIONS + * + * --id=: A specific customer ID to retrieve + * --email=: The email address of the customer to retrieve + * --create=: The number of arbitrary customers to create. Leave as 1 or blank to create a customer with a + * speciific email + * + * ## EXAMPLES + * + * wp edd customers --id=103 + * wp edd customers --email=john@test.com + * wp edd customers --create=1 --email=john@test.com + * wp edd customers --create=1 --email=john@test.com --name="John Doe" + * wp edd customers --create=1 --email=john@test.com --name="John Doe" user_id=1 + * wp edd customers --create=1000 + */ + public function customers( $args, $assoc_args ) { + $customer_id = isset( $assoc_args ) && array_key_exists( 'id', $assoc_args ) ? absint( $assoc_args['id'] ) : false; + $email = isset( $assoc_args ) && array_key_exists( 'email', $assoc_args ) ? $assoc_args['email'] : false; + $name = isset( $assoc_args ) && array_key_exists( 'name', $assoc_args ) ? $assoc_args['name'] : null; + $user_id = isset( $assoc_args ) && array_key_exists( 'user_id', $assoc_args ) ? $assoc_args['user_id'] : null; + $create = isset( $assoc_args ) && array_key_exists( 'create', $assoc_args ) ? $assoc_args['create'] : false; + $start = time(); + + if ( $create ) { + $number = 1; + + // Create one or more customers + if ( ! $email ) { + + // If no email is specified, look to see if we are generating arbitrary customer accounts + $number = is_numeric( $create ) ? absint( $create ) : 1; + } + + for ( $i = 0; $i < $number; $i ++ ) { + if ( ! $email ) { + + // Generate fake email + $email = 'customer-' . uniqid() . '@test.com'; + } + + $args = array( + 'email' => $email, + 'name' => $name, + 'user_id' => $user_id, + ); + + $customer_id = edd_add_customer( $args ); + + if ( $customer_id ) { + WP_CLI::line( sprintf( __( 'Customer %d created successfully', 'easy-digital-downloads' ), $customer_id ) ); + } else { + WP_CLI::error( __( 'Failed to create customer', 'easy-digital-downloads' ) ); + } + + // Reset email to false so it is generated on the next loop (if creating customers) + $email = false; + + } + + WP_CLI::line( WP_CLI::colorize( '%G' . sprintf( __( '%d customers created in %d seconds', 'easy-digital-downloads' ), $create, time() - $start ) . '%N' ) ); + } else { + // Search for customers + $search = false; + + // Checking if search is being done by id, email or user_id fields. + if ( $customer_id || $email || ( 'null' !== $user_id ) ) { + $search = array(); + $customer_details = array(); + + if ( $customer_id ) { + $customer_details['id'] = $customer_id; + } elseif ( $email ) { + $customer_details['email'] = $email; + } elseif ( null !== $user_id ) { + $customer_details['user_id'] = $user_id; + } + + $search['customer'] = $customer_details; + } + + $customers = $this->api->get_customers( $search ); + + if ( isset( $customers['error'] ) ) { + WP_CLI::error( $customers['error'] ); + } + + if ( empty( $customers ) ) { + WP_CLI::error( __( 'No customers found', 'easy-digital-downloads' ) ); + + return; + } + + foreach ( $customers['customers'] as $customer ) { + WP_CLI::line( WP_CLI::colorize( '%G' . $customer['info']['email'] . '%N' ) ); + WP_CLI::line( sprintf( __( 'Customer User ID: %s', 'easy-digital-downloads' ), $customer['info']['id'] ) ); + WP_CLI::line( sprintf( __( 'Username: %s', 'easy-digital-downloads' ), $customer['info']['username'] ) ); + WP_CLI::line( sprintf( __( 'Display Name: %s', 'easy-digital-downloads' ), $customer['info']['display_name'] ) ); + + if ( array_key_exists( 'first_name', $customer ) ) { + WP_CLI::line( sprintf( __( 'First Name: %s', 'easy-digital-downloads' ), $customer['info']['first_name'] ) ); + } + + if ( array_key_exists( 'last_name', $customer ) ) { + WP_CLI::line( sprintf( __( 'Last Name: %s', 'easy-digital-downloads' ), $customer['info']['last_name'] ) ); + } + + WP_CLI::line( sprintf( __( 'Email: %s', 'easy-digital-downloads' ), $customer['info']['email'] ) ); + + WP_CLI::line( '' ); + WP_CLI::line( sprintf( __( 'Purchases: %s', 'easy-digital-downloads' ), $customer['stats']['total_purchases'] ) ); + WP_CLI::line( sprintf( __( 'Total Spent: %s', 'easy-digital-downloads' ), edd_format_amount( $customer['stats']['total_spent'] ) . ' ' . edd_get_currency() ) ); + WP_CLI::line( sprintf( __( 'Total Downloads: %s', 'easy-digital-downloads' ), $customer['stats']['total_downloads'] ) ); + + WP_CLI::line( '' ); + } + + } + + } + + + /** + * Get the recent sales for your EDD site + * + * ## OPTIONS + * + * --email=: The email address of the customer to retrieve + * + * ## EXAMPLES + * + * wp edd sales + * wp edd sales --email=john@test.com + */ + public function sales( $args, $assoc_args ) { + $email = isset( $assoc_args ) && array_key_exists( 'email', $assoc_args ) ? $assoc_args['email'] : ''; + + global $wp_query; + + $wp_query->query_vars['email'] = $email; + + $sales = $this->api->get_recent_sales(); + + if ( empty( $sales ) ) { + WP_CLI::error( __( 'No sales found', 'easy-digital-downloads' ) ); + + return; + } + + foreach ( $sales['sales'] as $sale ) { + WP_CLI::line( WP_CLI::colorize( '%G' . $sale['ID'] . '%N' ) ); + WP_CLI::line( sprintf( __( 'Purchase Key: %s', 'easy-digital-downloads' ), $sale['key'] ) ); + WP_CLI::line( sprintf( __( 'Email: %s', 'easy-digital-downloads' ), $sale['email'] ) ); + WP_CLI::line( sprintf( __( 'Date: %s', 'easy-digital-downloads' ), $sale['date'] ) ); + WP_CLI::line( sprintf( __( 'Subtotal: %s', 'easy-digital-downloads' ), edd_format_amount( $sale['subtotal'] ) . ' ' . edd_get_currency() ) ); + WP_CLI::line( sprintf( __( 'Tax: %s', 'easy-digital-downloads' ), edd_format_amount( $sale['tax'] ) . ' ' . edd_get_currency() ) ); + + if ( array_key_exists( 0, $sale['fees'] ) ) { + WP_CLI::line( __( 'Fees:', 'easy-digital-downloads' ) ); + + foreach ( $sale['fees'] as $fee ) { + WP_CLI::line( sprintf( __( ' Fee: %s - %s', 'easy-digital-downloads' ), edd_format_amount( $fee['amount'] ), edd_get_currency() ) ); + } + } + + WP_CLI::line( sprintf( __( 'Total: %s', 'easy-digital-downloads' ), edd_format_amount( $sale['total'] ) . ' ' . edd_get_currency() ) ); + WP_CLI::line( '' ); + WP_CLI::line( sprintf( __( 'Gateway: %s', 'easy-digital-downloads' ), $sale['gateway'] ) ); + + if ( array_key_exists( 0, $sale['products'] ) ) { + WP_CLI::line( __( 'Products:', 'easy-digital-downloads' ) ); + + foreach ( $sale['products'] as $product ) { + $price_name = ! empty( $product['price_name'] ) ? ' (' . $product['price_name'] . ')' : ''; + WP_CLI::line( sprintf( __( ' Product: %s - %s', 'easy-digital-downloads' ), $product['name'], edd_format_amount( $product['price'] ) . ' ' . edd_get_currency() . $price_name ) ); + } + } + + WP_CLI::line( '' ); + } + } + + + /** + * Get discount details for on your EDD site + * + * ## OPTIONS + * + * --id=: A specific discount ID to retrieve + * + * ## EXAMPLES + * + * wp edd discounts --id=103 + */ + public function discounts( $args, $assoc_args ) { + + $discount_id = isset( $assoc_args ) && array_key_exists( 'id', $assoc_args ) ? absint( $assoc_args['id'] ) : false; + + $discounts = $this->api->get_discounts( $discount_id ); + + if ( isset( $discounts['error'] ) ) { + WP_CLI::error( $discounts['error'] ); + } + + if ( empty( $discounts ) ) { + WP_CLI::error( __( 'No discounts found', 'easy-digital-downloads' ) ); + + return; + } + + foreach ( $discounts['discounts'] as $discount ) { + WP_CLI::line( WP_CLI::colorize( '%G' . $discount['ID'] . '%N' ) ); + WP_CLI::line( sprintf( __( 'Name: %s', 'easy-digital-downloads' ), $discount['name'] ) ); + WP_CLI::line( sprintf( __( 'Code: %s', 'easy-digital-downloads' ), $discount['code'] ) ); + + if ( $discount['type'] == 'percent' ) { + $amount = $discount['amount'] . '%'; + } else { + $amount = edd_format_amount( $discount['amount'] ) . ' ' . edd_get_currency(); + } + + WP_CLI::line( sprintf( __( 'Amount: %s', 'easy-digital-downloads' ), $amount ) ); + WP_CLI::line( sprintf( __( 'Uses: %s', 'easy-digital-downloads' ), $discount['uses'] ) ); + WP_CLI::line( sprintf( __( 'Max Uses: %s', 'easy-digital-downloads' ), ( $discount['max_uses'] == '0' ? __( 'Unlimited', 'easy-digital-downloads' ) : $discount['max_uses'] ) ) ); + WP_CLI::line( sprintf( __( 'Start Date: %s', 'easy-digital-downloads' ), ( empty( $discount['start_date'] ) ? __( 'No Start Date', 'easy-digital-downloads' ) : $discount['start_date'] ) ) ); + WP_CLI::line( sprintf( __( 'Expiration Date: %s', 'easy-digital-downloads' ), ( empty( $discount['exp_date'] ) ? __( 'No Expiration', 'easy-digital-downloads' ) : $discount['exp_date'] ) ) ); + WP_CLI::line( sprintf( __( 'Status: %s', 'easy-digital-downloads' ), ucwords( $discount['status'] ) ) ); + + WP_CLI::line( '' ); + + if ( array_key_exists( 0, $discount['product_requirements'] ) ) { + WP_CLI::line( __( 'Product Requirements:', 'easy-digital-downloads' ) ); + + foreach ( $discount['product_requirements'] as $req => $req_id ) { + WP_CLI::line( sprintf( __( ' Product: %s', 'easy-digital-downloads' ), $req_id ) ); + } + } + + WP_CLI::line( '' ); + + WP_CLI::line( sprintf( __( 'Global Discount: %s', 'easy-digital-downloads' ), ( empty( $discount['global_discount'] ) ? 'False' : 'True' ) ) ); + WP_CLI::line( sprintf( __( 'Single Use: %s', 'easy-digital-downloads' ), ( empty( $discount['single_use'] ) ? 'False' : 'True' ) ) ); + + WP_CLI::line( '' ); + } + } + + /** + * Create sample purchase data for your EDD site + * + * ## OPTIONS + * + * --number: The number of purchases to create + * --status=: The status to create purchases as + * --id=: A specific product to create purchase data for + * --price_id=: A price ID of the specified product + * + * ## EXAMPLES + * + * wp edd payments create --number=10 --status=complete + * wp edd payments create --number=10 --id=103 + */ + public function payments( $args, $assoc_args ) { + + $error = false; + + // At some point we'll likely add another action for payments + if ( ! isset( $args ) || 0 === count( $args ) ) { + $error = __( 'No action specified, did you mean', 'easy-digital-downloads' ); + } elseif ( isset( $args ) && ! in_array( 'create', $args, true ) ) { + $error = __( 'Invalid action specified, did you mean', 'easy-digital-downloads' ); + } + + if ( $error ) { + $query = ''; + foreach ( $assoc_args as $key => $value ) { + $query .= ' --' . $key . '=' . $value; + } + + WP_CLI::error( + sprintf( $error . ' %s?', 'wp edd payments create' . $query ) + ); + + return; + } + + + // Setup some defaults + $number = 1; + $status = 'complete'; + $id = false; + $price_id = null; + $tax = 0; + $email = 'guest@edd.local'; + $fname = 'Pippin'; + $lname = 'Williamson'; + $date = false; + $range = 30; + $currency = edd_get_currency(); + $gateway = 'manual'; + + $generate_users = false; + + if ( count( $assoc_args ) > 0 ) { + $number = ( array_key_exists( 'number', $assoc_args ) ) ? absint( $assoc_args['number'] ) : $number; + $id = ( array_key_exists( 'id', $assoc_args ) ) ? absint( $assoc_args['id'] ) : $id; + $price_id = ( array_key_exists( 'price_id', $assoc_args ) ) ? absint( $assoc_args['price_id'] ) : $price_id; + $tax = ( array_key_exists( 'tax', $assoc_args ) ) ? floatval( $assoc_args['tax'] ) : $tax; + $email = ( array_key_exists( 'email', $assoc_args ) ) ? sanitize_email( $assoc_args['email'] ) : $email; + $fname = ( array_key_exists( 'fname', $assoc_args ) ) ? sanitize_text_field( $assoc_args['fname'] ) : $fname; + $lname = ( array_key_exists( 'lname', $assoc_args ) ) ? sanitize_text_field( $assoc_args['lname'] ) : $lname; + $date = ( array_key_exists( 'date', $assoc_args ) ) ? sanitize_text_field( $assoc_args['date'] ) : $date; + $range = ( array_key_exists( 'range', $assoc_args ) ) ? absint( $assoc_args['range'] ) : $range; + + $generate_users = ( array_key_exists( 'generate_users', $assoc_args ) ) ? (bool) absint( $assoc_args['generate_users'] ) : $generate_users; + + // Status requires a bit more validation. + if ( array_key_exists( 'status', $assoc_args ) ) { + $statuses = array_keys( edd_get_payment_statuses() ); + + if ( in_array( $assoc_args['status'], $statuses, true ) ) { + $status = ( 'publish' === $assoc_args['status'] ) + ? 'complete' + : $assoc_args['status']; + } else { + WP_CLI::warning( sprintf( + __( "Invalid status '%s', defaulting to 'complete'", 'easy-digital-downloads' ), + $assoc_args['status'] + ) ); + } + } + } + + // Build the user info array. + $user_info = array( + 'id' => 0, + 'email' => $email, + 'first_name' => $fname, + 'last_name' => $lname, + 'discount' => 'none', + ); + + $progress = \WP_CLI\Utils\make_progress_bar( 'Creating Orders', $number ); + + for ( $i = 0; $i < $number; $i ++ ) { + $products = array(); + $total = 0; + + // No specified product + if ( ! $id ) { + $products = get_posts( array( + 'post_type' => 'download', + 'orderby' => 'rand', + 'order' => 'ASC', + 'posts_per_page' => rand( 1, 3 ), + ) ); + } else { + $product = get_post( $id ); + + if ( 'download' !== $product->post_type ) { + WP_CLI::error( __( 'Specified ID is not a product', 'easy-digital-downloads' ) ); + + return; + } + + $products[] = $product; + } + + $cart_details = array(); + + // Add each download to the order. + foreach ( $products as $key => $download ) { + if ( ! $download instanceof WP_Post ) { + continue; + } + + $options = array(); + $final_downloads = array(); + + // Variable price. + if ( edd_has_variable_prices( $download->ID ) ) { + $prices = edd_get_variable_prices( $download->ID ); + + if ( false === $price_id || ! array_key_exists( $price_id, (array) $prices ) ) { + $item_price_id = array_rand( $prices ); + } else { + $item_price_id = $price_id; + } + + $item_price = $prices[ $item_price_id ]['amount']; + $options['price_id'] = $item_price_id; + + // Flat price. + } else { + $item_price = edd_get_download_price( $download->ID ); + } + + $item_number = array( + 'id' => $download->ID, + 'quantity' => 1, + 'options' => $options, + ); + + $cart_details[ $key ] = array( + 'name' => edd_get_download_name( $download->ID, $price_id ), + 'id' => $download->ID, + 'item_number' => $item_number, + 'item_price' => edd_sanitize_amount( $item_price ), + 'subtotal' => edd_sanitize_amount( $item_price ), + 'price' => edd_sanitize_amount( $item_price ), + 'quantity' => 1, + 'discount' => 0, + 'tax' => edd_calculate_tax( $item_price ), + ); + + $final_downloads[ $key ] = $item_number; + + $total += $item_price; + } + + // Generate random date. + if ( 'random' === $date ) { + // Randomly grab a date from the current past 30 days + $oldest_time = strtotime( '-' . $range . ' days', current_time( 'timestamp' ) ); + $newest_time = current_time( 'timestamp' ); + + $timestamp = rand( $oldest_time, $newest_time ); + $timestring = date( "Y-m-d H:i:s", $timestamp ); + } elseif ( empty( $date ) ) { + $timestring = false; + } else { + if ( is_numeric( $date ) ) { + $timestring = date( "Y-m-d H:i:s", $date ); + } else { + $parsed_time = strtotime( $date ); + $timestring = date( "Y-m-d H:i:s", $parsed_time ); + } + } + + // Maybe generate users. + if ( $generate_users ) { + $fname = $this->get_fname(); + $lname = $this->get_lname(); + $domain = $this->get_domain(); + $tld = $this->get_tld(); + + $email = $fname . '.' . $lname . '@' . $domain . '.' . $tld; + + $user_info = array( + 'id' => 0, + 'email' => $email, + 'first_name' => $fname, + 'last_name' => $lname, + 'discount' => 'none', + ); + } + + // Allow random currencies. + if ( ! empty( $assoc_args['currency'] ) && 'random' === $assoc_args['currency'] ) { + $currencies = array( 'USD', 'EUR', 'GBP' ); + $currency = $currencies[ array_rand( $currencies ) ]; + } + + // Allow random gateways. + if ( ! empty( $assoc_args['gateway'] ) && 'random' === $assoc_args['gateway'] ) { + $gateways = array_keys( edd_get_payment_gateways() ); + $gateway = $gateways[ array_rand( $gateways ) ]; + } + + // Build purchase data. + $purchase_data = array( + 'price' => edd_sanitize_amount( $total ), + 'tax' => edd_calculate_tax( $total ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $email, + 'user_info' => $user_info, + 'currency' => $currency, + 'downloads' => $final_downloads, + 'cart_details' => $cart_details, + 'status' => 'pending', + ); + + if ( ! empty( $timestring ) ) { + $purchase_data['date_created'] = $timestring; + } + + $order_id = edd_build_order( $purchase_data ); + + // Ensure purchase receipts do not get sent. + remove_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999 ); + + // Trigger payment status actions. + if ( 'pending' !== $status ) { + edd_update_order_status( $order_id, $status ); + } + + if ( ! empty( $timestring ) ) { + $payment = new EDD_Payment( $order_id ); + $payment->completed_date = $timestring; + $payment->gateway = $gateway; + $payment->save(); + } + + $progress->tick(); + } + + $progress->finish(); + + WP_CLI::success( sprintf( __( 'Created %s orders', 'easy-digital-downloads' ), $number ) ); + + return; + } + + /** + * Create discount codes for your EDD site + * + * ## OPTIONS + * + * --legacy: Create legacy discount codes using pre-3.0 schema + * --number: The number of discounts to create + * + * ## EXAMPLES + * + * wp edd create_discounts --number=100 + * wp edd create_discounts --number=50 --legacy + */ + public function create_discounts( $args, $assoc_args ) { + $number = array_key_exists( 'number', $assoc_args ) ? absint( $assoc_args['number'] ) : 1; + $legacy = array_key_exists( 'legacy', $assoc_args ) ? true : false; + + $progress = \WP_CLI\Utils\make_progress_bar( 'Creating Discount Codes', $number ); + + for ( $i = 0; $i < $number; $i ++ ) { + if ( $legacy ) { + $discount_id = wp_insert_post( array( + 'post_type' => 'edd_discount', + 'post_title' => 'Auto-Generated Legacy Discount #' . $i, + 'post_status' => 'active', + ) ); + + $download_ids = get_posts( array( + 'post_type' => 'download', + 'posts_per_page' => 2, + 'fields' => 'ids', + 'orderby' => 'rand', + ) ); + + $meta = array( + 'code' => 'LEGACY' . $i, + 'status' => 'active', + 'uses' => 10, + 'max_uses' => 20, + 'name' => 'Auto-Generated Legacy Discount #' . $i, + 'amount' => 20, + 'start' => '01/01/2000 00:00:00', + 'expiration' => '12/31/2050 23:59:59', + 'type' => 'percent', + 'min_price' => '10.50', + 'product_reqs' => array( $download_ids[0] ), + 'product_condition' => 'all', + 'excluded_products' => array( $download_ids[1] ), + 'is_not_global' => true, + 'is_single_use' => true, + ); + + remove_action( 'pre_get_posts', '_edd_discount_get_post_doing_it_wrong', 99, 1 ); + remove_filter( 'add_post_metadata', '_edd_discount_update_meta_backcompat', 99 ); + + foreach ( $meta as $key => $value ) { + add_post_meta( $discount_id, '_edd_discount_' . $key, $value ); + } + + add_filter( 'add_post_metadata', '_edd_discount_update_meta_backcompat', 99, 5 ); + add_action( 'pre_get_posts', '_edd_discount_get_post_doing_it_wrong', 99, 1 ); + } else { + $type = array( 'flat', 'percent' ); + $status = array( 'active', 'inactive' ); + $product_condition = array( 'any', 'all' ); + + $type_index = array_rand( $type, 1 ); + $status_index = array_rand( $status, 1 ); + $product_condition_index = array_rand( $product_condition, 1 ); + + $post = array( + 'code' => md5( time() ), + 'uses' => mt_rand( 0, 100 ), + 'max' => mt_rand( 0, 100 ), + 'name' => 'Auto-Generated Discount #' . $i, + 'type' => $type[ $type_index ], + 'amount' => mt_rand( 10, 95 ), + 'start' => '12/12/2010 00:00:00', + 'expiration' => '12/31/2050 23:59:59', + 'min_price' => mt_rand( 30, 255 ), + 'status' => $status[ $status_index ], + 'product_condition' => $product_condition[ $product_condition_index ], + ); + + edd_store_discount( $post ); + + $progress->tick(); + } + } + + $progress->finish(); + + WP_CLI::success( sprintf( __( 'Created %s discounts', 'easy-digital-downloads' ), $number ) ); + + return; + } + + /** + * Run the EDD 3.0 Migration via WP-CLI + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * @param $args + * @param $assoc_args + */ + public function v30_migration( $args, $assoc_args ) { + + // Suspend the cache addition while we're migrating. + wp_suspend_cache_addition( true ); + + $this->maybe_install_v3_tables(); + update_option( 'edd_v30_cli_migration_running', true ); + $this->migrate_tax_rates( $args, $assoc_args ); + $this->migrate_discounts( $args, $assoc_args ); + $this->migrate_payments( $args, $assoc_args ); + $this->migrate_customer_data( $args, $assoc_args ); + $this->migrate_logs( $args, $assoc_args ); + $this->migrate_order_notes( $args, $assoc_args ); + $this->migrate_customer_notes( $args, $assoc_args ); + edd_v30_is_migration_complete(); + $this->remove_legacy_data( $args, $assoc_args ); + } + + /** + * Installs any new 3.0 database tables that haven't yet been installed + * + * @access private + * @since 3.0 + */ + private function maybe_install_v3_tables() { + static $installed = false; + + if ( $installed ) { + return; + } + + foreach ( EDD()->components as $component ) { + // Install the main component table. + $table = $component->get_interface( 'table' ); + if ( $table instanceof EDD\Database\Table && ! $table->exists() ) { + $table->install(); + } + + // Install the associated meta table, if there is one. + $meta = $component->get_interface( 'meta' ); + if ( $meta instanceof EDD\Database\Table && ! $meta->exists() ) { + $meta->install(); + } + } + + // Only need to do this once. + $installed = true; + } + + /** + * Migrate Discounts to the custom tables + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * ## EXAMPLES + * + * wp edd migrate_discounts + * wp edd migrate_discounts --force + */ + public function migrate_discounts( $args, $assoc_args ) { + global $wpdb; + + $this->maybe_install_v3_tables(); + + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/class-data-migrator.php'; + + $force = isset( $assoc_args['force'] ) + ? true + : false; + + $destroy = (bool) ( $force && isset( $assoc_args['destroy'] ) ); + + if ( $destroy ) { + WP_CLI::confirm( __( 'This process will remove and recreate discounts in your database. Please make sure you\'ve backed up your EDD database tables. Are you sure you want to delete discounts that have already been migrated and run the migration again?', 'easy-digital-downloads' ) ); + } + + $upgrade_completed = edd_has_upgrade_completed( 'migrate_discounts' ); + + if ( ! $force && $upgrade_completed ) { + WP_CLI::error( __( 'The discounts custom database migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } + + $sql = "SELECT * FROM {$wpdb->posts} WHERE post_type = 'edd_discount'"; + $results = $wpdb->get_results( $sql ); + $total = count( $results ); + + if ( ! empty( $total ) ) { + + $progress = new \cli\progress\Bar( 'Migrating Discounts', $total ); + + foreach ( $results as $result ) { + if ( $destroy ) { + edd_delete_discount( $result->ID ); + } + \EDD\Admin\Upgrades\v3\Data_Migrator::discounts( $result ); + + $progress->tick(); + } + + $progress->finish(); + + WP_CLI::line( __( 'Migration complete: Discounts', 'easy-digital-downloads' ) ); + $new_count = edd_get_discount_count(); + $old_count = $wpdb->get_col( "SELECT count(ID) FROM $wpdb->posts WHERE post_type ='edd_discount'", 0 ); + WP_CLI::line( __( 'Old Records: ', 'easy-digital-downloads' ) . $old_count[0] ); + WP_CLI::line( __( 'New Records: ', 'easy-digital-downloads' ) . $new_count ); + + edd_set_upgrade_complete( 'migrate_discounts' ); + + } else { + + WP_CLI::line( __( 'No discount records found.', 'easy-digital-downloads' ) ); + edd_set_upgrade_complete( 'migrate_discounts' ); + + } + } + + /** + * Migrate logs to the custom tables. + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * ## EXAMPLES + * + * wp edd migrate_logs + * wp edd migrate_logs --force + */ + public function migrate_logs( $args, $assoc_args ) { + global $wpdb; + + $this->maybe_install_v3_tables(); + + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/class-data-migrator.php'; + + $force = isset( $assoc_args['force'] ) + ? true + : false; + + $upgrade_completed = edd_has_upgrade_completed( 'migrate_logs' ); + + if ( ! $force && $upgrade_completed ) { + WP_CLI::error( __( 'The logs custom table migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } + + WP_CLI::line( __( 'Preparing to migrate logs (this can take several minutes).', 'easy-digital-downloads' ) ); + + // New Progress indicator. + $progress = new \cli\notify\Spinner( __( 'Migrating Logs', 'easy-digital-downloads' ) ); + $progress->tick(); + + // Base SQL to get legacy logs (LIMIT added below) + $sql_base = " + SELECT p.*, t.slug + FROM {$wpdb->posts} AS p + LEFT JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id) + LEFT JOIN {$wpdb->term_taxonomy} AS tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) + LEFT JOIN {$wpdb->terms} AS t ON (tt.term_id = t.term_id) + WHERE + p.post_type = 'edd_log' + AND + t.slug != 'sale' + ORDER BY p.ID ASC + "; + + // Query & count. + $sql = $sql_base . " LIMIT 1"; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + // Setup base iteration variables. + $step = 0; + $offset = 0; + $number = isset( $assoc_args['number'] ) && is_numeric( $assoc_args['number'] ) + ? (int) $assoc_args['number'] + : 1000; + + // Maximum 10,000 - this ain't no VTEC. + if ( $number > 10000 ) { + $number = 10000; + } + + // Starting total. + $total = 0; + + while ( $has_results ) { + $progress->tick(); + + // Query & count. + $sql = $sql_base . " LIMIT {$offset}, {$number}"; + $results = $wpdb->get_results( $sql ); + + // Not empty, so lets chug through them! + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + \EDD\Admin\Upgrades\v3\Data_Migrator::logs( $result ); + + // Tick the spinner... + $progress->tick(); + + // Bump the total... + $total++; + } + + // Increment step for the next offset... + $step++; + + // EG: 1 * 1000 = 1000, 2 * 1000 = 2000. + $offset = ( $step * $number ); + + // Done! + } else { + $has_results = false; + } + } + + $progress->finish(); + + if ( 0 === $step ) { + WP_CLI::line( __( 'No log records found.', 'easy-digital-downloads' ) ); + } else { + // This migration is completed on a data set. + WP_CLI::line( __( 'Migration complete: Logs', 'easy-digital-downloads' ) ); + $new_count = edd_count_logs() + edd_count_file_download_logs() + edd_count_api_request_logs(); + WP_CLI::line( __( 'Old Records: ', 'easy-digital-downloads' ) . $total ); + WP_CLI::line( __( 'New Records: ', 'easy-digital-downloads' ) . $new_count ); + } + + edd_set_upgrade_complete( 'migrate_logs' ); + + } + + /** + * Migrate order notes to the custom tables. + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * ## EXAMPLES + * + * wp edd migrate_notes + * wp edd migrate_notes --force + */ + public function migrate_order_notes( $args, $assoc_args ) { + global $wpdb; + + $this->maybe_install_v3_tables(); + + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/class-data-migrator.php'; + + $force = isset( $assoc_args['force'] ) + ? true + : false; + + $upgrade_completed = edd_has_upgrade_completed( 'migrate_order_notes' ); + + if ( ! $force && $upgrade_completed ) { + WP_CLI::error( __( 'The order notes custom table migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } + + WP_CLI::line( __( 'Preparing to migrate order notes.', 'easy-digital-downloads' ) ); + + $progress = new \cli\notify\Spinner( __( 'Migrating Order Notes', 'easy-digital-downloads' ) ); + $progress->tick(); + + $sql_base = " + SELECT * FROM {$wpdb->comments} + WHERE comment_type = 'edd_payment_note' + ORDER BY comment_ID ASC + "; + + // Query & count. + $sql = $sql_base . " LIMIT 1"; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + // Setup base iteration variables. + $step = 0; + $offset = 0; + $number = isset( $assoc_args['number'] ) && is_numeric( $assoc_args['number'] ) + ? (int) $assoc_args['number'] + : 1000; + + // Maximum 10,000 - this ain't no VTEC. + if ( $number > 10000 ) { + $number = 10000; + } + + $total = 0; + + while ( $has_results ) { + $progress->tick(); + + // Query & count. + $sql = $sql_base . " LIMIT {$offset}, {$number}"; + $results = $wpdb->get_results( $sql ); + + // Not empty, so lets process the order notes. + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + $result->object_id = $result->comment_post_ID; + \EDD\Admin\Upgrades\v3\Data_Migrator::order_notes( $result ); + + // Tick the spinner... + $progress->tick(); + + // Bump the total... + $total++; + } + + // Increment step for the next offset... + $step++; + + // EG: 1 * 1000 = 1000, 2 * 1000 = 2000. + $offset = ( $step * $number ); + + // Done! + } else { + $has_results = false; + } + } + + $progress->finish(); + + if ( 0 === $step ) { + WP_CLI::line( __( 'No order notes found.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Migration complete: Order Notes', 'easy-digital-downloads' ) ); + $new_count = edd_count_notes( array( 'object_type' => 'order' ) ); + WP_CLI::line( __( 'Old order notes: ', 'easy-digital-downloads' ) . $total ); + WP_CLI::line( __( 'New order notes: ', 'easy-digital-downloads' ) . $new_count ); + } + + edd_set_upgrade_complete( 'migrate_order_notes' ); + } + + /** + * Migrate customer notes to the custom tables. + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * ## EXAMPLES + * + * wp edd migrate_notes + * wp edd migrate_notes --force + */ + public function migrate_customer_notes( $args, $assoc_args ) { + global $wpdb; + + $this->maybe_install_v3_tables(); + + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/class-data-migrator.php'; + + $force = isset( $assoc_args['force'] ) + ? true + : false; + + $upgrade_completed = edd_has_upgrade_completed( 'migrate_customer_notes' ); + + if ( ! $force && $upgrade_completed ) { + WP_CLI::error( __( 'The customer notes custom table migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Preparing to migrate customer notes.', 'easy-digital-downloads' ) ); + + $progress = new \cli\notify\Spinner( __( 'Migrating Customer Notes', 'easy-digital-downloads' ) ); + $progress->tick(); + + $sql_base = "SELECT * FROM {$wpdb->edd_customers}"; + + // Query & count. + $sql = $sql_base . " LIMIT 1"; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + // Setup base iteration variables. + $step = 0; + $offset = 0; + $number = isset( $assoc_args['number'] ) && is_numeric( $assoc_args['number'] ) + ? (int) $assoc_args['number'] + : 1000; + + // Maximum 10,000 - this ain't no VTEC. + if ( $number > 10000 ) { + $number = 10000; + } + + $total = 0; + + while ( $has_results ) { + $progress->tick(); + + // Query & count. + $sql = $sql_base . " LIMIT {$offset}, {$number}"; + $results = $wpdb->get_results( $sql ); + + // Not empty, so lets process the customer notes. + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + \EDD\Admin\Upgrades\v3\Data_Migrator::customer_notes( $result ); + + + // Tick the spinner... + $progress->tick(); + + // Bump the total... + $total++; + } + + // Increment step for the next offset... + $step++; + + // EG: 1 * 1000 = 1000, 2 * 1000 = 2000. + $offset = ( $step * $number ); + + // Done! + } else { + $has_results = false; + } + } + + $progress->finish(); + + if ( 0 === $step ) { + WP_CLI::line( __( 'No customer notes found.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Migration complete: Customer Notes', 'easy-digital-downloads' ) ); + $new_count = edd_count_notes( array( 'object_type' => 'customer' ) ); + WP_CLI::line( __( 'Old customer notes: ', 'easy-digital-downloads' ) . $total ); + WP_CLI::line( __( 'New customer notes: ', 'easy-digital-downloads' ) . $new_count ); + } + + edd_set_upgrade_complete( 'migrate_customer_notes' ); + } + } + + /** + * Migrate customer data to the custom tables. + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * ## EXAMPLES + * + * wp edd migrate_customer_data + * wp edd migrate_customer_data --force + */ + public function migrate_customer_data( $args, $assoc_args ) { + global $wpdb; + + $this->maybe_install_v3_tables(); + + $customers = new EDD\Database\Tables\Customers(); + $customers->maybe_upgrade(); + + $meta = new EDD\Database\Tables\Customer_Meta(); + $meta->maybe_upgrade(); + + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/class-data-migrator.php'; + + $force = isset( $assoc_args['force'] ) + ? true + : false; + + WP_CLI::line( __( 'Preparing to migrate additional customer data.', 'easy-digital-downloads' ) ); + + // Create the tables if they do not exist. + $components = array( + array( 'order', 'table' ), + array( 'order', 'meta' ), + array( 'customer', 'table' ), + array( 'customer', 'meta' ), + array( 'customer_address', 'table' ), + array( 'customer_email_address', 'table' ), + ); + + foreach ( $components as $component ) { + /** @var EDD\Database\Tables\Base $table */ + $table = edd_get_component_interface( $component[0], $component[1] ); + + if ( $table instanceof EDD\Database\Tables\Base && ! $table->exists() ) { + @$table->create(); + } + } + + // Migrate Customer Addresses. + $customer_addresses_complete = edd_has_upgrade_completed( 'migrate_customer_addresses' ); + + if ( ! $force && $customer_addresses_complete ) { + WP_CLI::warning( __( 'The user addresses custom table migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Preparing to migrate customer address data.', 'easy-digital-downloads' ) ); + $progress = new \cli\notify\Spinner( __( 'Migrating Customer Addresses', 'easy-digital-downloads' ) ); + $progress->tick(); + + // Migrate user addresses first. + $sql_base = " + SELECT * + FROM {$wpdb->usermeta} + WHERE meta_key = '_edd_user_address' + "; + + // Query & count. + $sql = $sql_base . " LIMIT 1"; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + // Setup base iteration variables. + $step = 0; + $offset = 0; + $number = isset( $assoc_args['number'] ) && is_numeric( $assoc_args['number'] ) + ? (int) $assoc_args['number'] + : 1000; + + // Maximum 10,000 - this ain't no VTEC. + if ( $number > 10000 ) { + $number = 10000; + } + + $total = 0; + + while ( $has_results ) { + $progress->tick(); + + // Query & count. + $sql = $sql_base . " LIMIT {$offset}, {$number}"; + $results = $wpdb->get_results( $sql ); + + // Not empty, so lets process the customer address. + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + \EDD\Admin\Upgrades\v3\Data_Migrator::customer_addresses( $result, 'billing' ); + + // Tick the spinner... + $progress->tick(); + + // Bump the total... + $total++; + } + + // Increment step for the next offset... + $step++; + + // EG: 1 * 1000 = 1000, 2 * 1000 = 2000. + $offset = ( $step * $number ); + + // Done! + } else { + $has_results = false; + } + } + + $progress->tick(); + $progress->finish(); + + edd_set_upgrade_complete( 'migrate_customer_addresses' ); + } + + // Migrate Customer Email Addresses. + $customer_email_addresses_complete = edd_has_upgrade_completed( 'migrate_customer_email_addresses' ); + + if ( ! $force && $customer_email_addresses_complete ) { + WP_CLI::warning( __( 'The user email addresses custom table migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Preparing to migrate customer email addresses (this can take several minutes).', 'easy-digital-downloads' ) ); + $progress = new \cli\notify\Spinner( __( 'Migrating Customer Email Addresses', 'easy-digital-downloads' ) ); + $progress->tick(); + + // Migrate email addresses next. + $sql = " + SELECT * + FROM {$wpdb->edd_customermeta} + WHERE meta_key = 'additional_email' + "; + + // Query & count. + $sql = $sql_base . " LIMIT 1"; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + // Setup base iteration variables. + $step = 0; + $offset = 0; + $number = isset( $assoc_args['number'] ) && is_numeric( $assoc_args['number'] ) + ? (int) $assoc_args['number'] + : 1000; + + // Maximum 10,000 - this ain't no VTEC. + if ( $number > 10000 ) { + $number = 10000; + } + + $total = 0; + + while ( $has_results ) { + $progress->tick(); + + // Query & count. + $sql = $sql_base . " LIMIT {$offset}, {$number}"; + $results = $wpdb->get_results( $sql ); + + // Not empty, so lets process the customer email addresses. + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + \EDD\Admin\Upgrades\v3\Data_Migrator::customer_email_addresses( $result ); + + // Tick the spinner... + $progress->tick(); + + // Bump the total... + $total++; + } + + // Increment step for the next offset... + $step++; + + // EG: 1 * 1000 = 1000, 2 * 1000 = 2000. + $offset = ( $step * $number ); + + // Done! + } else { + $has_results = false; + } + } + + $progress->finish(); + edd_set_upgrade_complete( 'migrate_customer_email_addresses' ); + WP_CLI::line( __( 'Migration complete: Customer Email Addresses', 'easy-digital-downloads' ) ); + } + + } + + /** + * Migrate tax rates. + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * ## EXAMPLES + * + * wp edd migrate_tax_rates + * wp edd migrate_tax_rates --force + */ + public function migrate_tax_rates( $args, $assoc_args ) { + global $wpdb; + + $this->maybe_install_v3_tables(); + + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/class-data-migrator.php'; + + $force = isset( $assoc_args['force'] ) + ? true + : false; + + $upgrade_completed = edd_has_upgrade_completed( 'migrate_tax_rates' ); + + if ( ! $force && $upgrade_completed ) { + WP_CLI::error( __( 'The tax rates custom table migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } + + WP_CLI::line( __( 'Checking for default tax rate', 'easy-digital-downloads' ) ); + $default_tax_rate = edd_get_option( 'tax_rate', false ); + if ( ! empty( $default_tax_rate ) ) { + WP_CLI::line( __( 'Migrating default tax rate', 'easy-digital-downloads' ) ); + edd_add_tax_rate( + array( + 'scope' => 'global', + 'amount' => floatval( $default_tax_rate ), + ) + ); + } + + // Migrate user addresses first. + $tax_rates = get_option( 'edd_tax_rates', array() ); + + if ( ! empty( $tax_rates ) ) { + $progress = new \cli\progress\Bar( 'Migrating Tax Rates', count( $tax_rates ) ); + + foreach ( $tax_rates as $result ) { + \EDD\Admin\Upgrades\v3\Data_Migrator::tax_rates( $result ); + + $progress->tick(); + } + + $progress->finish(); + } + + WP_CLI::line( __( 'Migration complete: Tax Rates', 'easy-digital-downloads' ) ); + $new_count = edd_count_adjustments( array( 'type' => 'tax_rate' ) ); + WP_CLI::line( __( 'Old Records: ', 'easy-digital-downloads' ) . count( $tax_rates ) ); + WP_CLI::line( __( 'New Records: ', 'easy-digital-downloads' ) . $new_count ); + + edd_set_upgrade_complete( 'migrate_tax_rates' ); + } + + /** + * Migrate payments to the custom tables. + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * --id=: Run the migration for a specific order. + * --start=: Run the migration beginning with a specific order ID. + * --end=: Run the migration ending with a specific order ID. + * --destroy=: Destroy existing orders that have already been migrated. + * + * ## EXAMPLES + * + * wp edd migrate_payments + * wp edd migrate_payments --force + * wp edd migrate_payments --force --id=3 Migrate payment ID 3. + * wp edd migrate_payments --force --start=3 Migrate payments beginning with and including ID 3. + * wp edd migrate_payments --force --end=3 Migrate payments up to and including ID 3, but not higher. + * wp edd migrate_payments --force --destroy Destroy existing orders and migrate them again. + */ + public function migrate_payments( $args, $assoc_args ) { + global $wpdb; + + $this->maybe_install_v3_tables(); + + require_once EDD_PLUGIN_DIR . 'includes/admin/upgrades/v3/class-data-migrator.php'; + + $force = isset( $assoc_args['force'] ) + ? true + : false; + + $destroy = (bool) ( $force && isset( $assoc_args['destroy'] ) ); + + if ( $destroy ) { + WP_CLI::confirm( __( 'This process will remove and recreate orders in your database. Please make sure you\'ve backed up your EDD database tables. Are you sure you want to delete orders that have already been migrated and run the migration again?', 'easy-digital-downloads' ) ); + } + + $upgrade_completed = edd_has_upgrade_completed( 'migrate_orders' ); + + if ( ! $force && $upgrade_completed ) { + WP_CLI::error( __( 'The payments custom table migration has already been run. To do this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } + + WP_CLI::line( __( 'Preparing to migrate payments.', 'easy-digital-downloads' ) ); + + // New Progress indicator. + $progress = new \cli\notify\Spinner( __( 'Migrating Payments', 'easy-digital-downloads' ) ); + $progress->tick(); + + $sql_base = " + SELECT * + FROM {$wpdb->posts} + WHERE post_type = 'edd_payment' + "; + + // Query & count. + $sql = $sql_base . " LIMIT 1"; + $check_result = $wpdb->get_results( $sql ); + $check_total = count( $check_result ); + $has_results = ! empty( $check_total ); + + // Setup base iteration variables. + $step = 0; + $offset = 0; + $full_migration = true; + + // Migrate one specific order. + if ( ! empty( $assoc_args['id'] ) ) { + if ( is_numeric( $assoc_args['id'] ) ) { + $id = absint( $assoc_args['id'] ); + $sql_base .= " AND ID = {$id}"; + $full_migration = false; + if ( ! $wpdb->get_results( $sql_base ) ) { + WP_CLI::error( __( 'An EDD Payment could not be found for that ID.', 'easy-digital-downloads' ) ); + } + } else { + WP_CLI::error( __( 'The payment ID must be an integer from the post_id column.', 'easy-digital-downloads' ) ); + } + } elseif ( ! empty( $assoc_args['start'] ) || ! empty( $assoc_args['end'] ) ) { + + // Begin the order migration at a specific payment ID. + if ( ! empty( $assoc_args['start'] ) ) { + if ( is_numeric( $assoc_args['start'] ) ) { + $start = absint( $assoc_args['start'] ); + $sql_base .= " AND ID >= {$start}"; + $full_migration = false; + } else { + WP_CLI::error( __( 'The starting ID must be an integer from the post_id column.', 'easy-digital-downloads' ) ); + } + } + // Stop the order migration at a specific payment ID. + if ( ! empty( $assoc_args['end'] ) ) { + if ( is_numeric( $assoc_args['end'] ) ) { + $end = absint( $assoc_args['end'] ); + $sql_base .= " AND ID <= {$end}"; + $full_migration = false; + } else { + WP_CLI::error( __( 'The ending ID must be an integer from the post_id column.', 'easy-digital-downloads' ) ); + } + } + } + + // Confirm any partial migrations if the upgrade hasn't been completed. + if ( ! $full_migration && ! $upgrade_completed ) { + WP_CLI::confirm( __( 'Are you sure you want to run a partial order migration?', 'easy-digital-downloads' ) ); + } + + $sql_base .= ' ORDER BY ID ASC'; + + $number = isset( $assoc_args['number'] ) && is_numeric( $assoc_args['number'] ) + ? (int) $assoc_args['number'] + : 1000; + + // Maximum 10,000 - this ain't no VTEC. + if ( $number > 10000 ) { + $number = 10000; + } + + // Starting total. + $total = 0; + + while ( $has_results ) { + $orders = new \EDD\Database\Queries\Order(); + $progress->tick(); + + // Query & count. + $sql = $sql_base . " LIMIT {$offset}, {$number}"; + $results = $wpdb->get_results( $sql ); + + // Not empty, so lets chug through them! + if ( ! empty( $results ) ) { + foreach ( $results as $result ) { + + // Check if order has already been migrated. + $migrated = $orders->get_item( $result->ID ); + $parent_id = false; + + // Delete the existing order to re-run the migration fresh. + if ( $destroy ) { + $parent_id = 'refund' === $migrated->type && ! empty( $migrated->parent ) ? $migrated->parent : false; + + // EDD has detected a collision between a refund ID and a payment ID. + if ( ! empty( $parent_id ) ) { + WP_CLI::line( + sprintf( + /* translators: 1. the refund order ID; 2. the original payment ID. */ + __( '%1$d is a refund order. EDD will delete the refund and migrate payment %1$d, then re-migrate payment %2$d.', 'easy-digital-downloads' ), + $result->ID, + $parent_id + ) + ); + } elseif ( $result->post_date_gmt !== $migrated->date_created ) { + // The migrated order does not appear to be the same as the original order, so let's confirm. + WP_CLI::confirm( + sprintf( + /* translators: 1. the order/payment ID. */ + __( 'Order ID %1$d appears to be a different record from Payment ID %1$d. Are you sure you want to destroy this order and overwrite it?', 'easy-digital-downloads' ), + $result->ID + ) + ); + } + edd_destroy_order( $result->ID ); + $migrated = false; + } + + if ( $migrated ) { + $progress->tick(); + continue; + } + + $success = \EDD\Admin\Upgrades\v3\Data_Migrator::orders( $result ); + + /** + * We detected that a refund order ID collided with an edd_payment post ID. + * We deleted the refund already; now delete the original order and re-migrate it to regenerate the refund. + */ + if ( $parent_id ) { + edd_destroy_order( $parent_id ); + $result = $wpdb->get_row( + $wpdb->prepare( + "SELECT * + FROM {$wpdb->posts} + WHERE post_type = 'edd_payment' + AND ID = %d", + $parent_id + ) + ); + \EDD\Admin\Upgrades\v3\Data_Migrator::orders( $result ); + } + if ( ! $full_migration && empty( $success ) ) { + /* translators: payment ID. */ + WP_CLI::line( sprintf( __( 'Migration failed for payment %d.', 'easy-digital-downloads' ), $result->ID ) ); + } + + // Tick the spinner... + $progress->tick(); + + // Bump the total... + $total++; + } + + // Increment step for the next offset... + $step++; + + // EG: 1 * 1000 = 1000, 2 * 1000 = 2000. + $offset = ( $step * $number ); + + // Done! + } else { + $has_results = false; + } + } + + if ( 0 === $step ) { + WP_CLI::line( __( 'No payment records found.', 'easy-digital-downloads' ) ); + if ( $full_migration ) { + edd_set_upgrade_complete( 'migrate_orders' ); + edd_set_upgrade_complete( 'remove_legacy_payments' ); + } + } else { + if ( ! $full_migration ) { + WP_CLI::line( __( 'Partial order migration complete. Orders Processed: ', 'easy-digital-downloads' ) . $total ); + WP_CLI::line( __( 'To recalculate all download sales and earnings, run `wp edd recalculate_download_sales_earnings`.', 'easy-digital-downloads' ) ); + WP_CLI::line( __( 'To recalculate all customer sales and earnings, run `wp edd recalculate_customer_values`.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Migration complete: Orders', 'easy-digital-downloads' ) ); + $new_count = edd_count_orders( array( 'type' => 'sale' ) ); + $old_count = $wpdb->get_col( "SELECT count(ID) FROM {$wpdb->posts} WHERE post_type = 'edd_payment'", 0 ); + WP_CLI::line( __( 'Old Records: ', 'easy-digital-downloads' ) . $old_count[0] ); + WP_CLI::line( __( 'New Records: ', 'easy-digital-downloads' ) . $new_count ); + + $refund_count = edd_count_orders( array( 'type' => 'refund' ) ); + WP_CLI::line( __( 'Refund Records Created: ', 'easy-digital-downloads' ) . $refund_count ); + + edd_set_upgrade_complete( 'migrate_orders' ); + + $progress->tick(); + $this->recalculate_download_sales_earnings(); + $this->recalculate_customer_values(); + } + } + + $progress->finish(); + } + + /** + * Display the legacy data for an EDD_Payment. + * + * @param array $args + * @return void + */ + public function display_legacy_payment_data( $args ) { + $id = ! empty( $args[0] ) ? (int) $args[0] : false; + if ( ! $id ) { + WP_CLI::error( __( 'You must enter a payment ID to display legacy data.', 'easy-digital-downloads' ) ); + } + + global $wpdb; + + $results = $wpdb->get_results( + $wpdb->prepare( + "SELECT * + FROM {$wpdb->postmeta} + WHERE post_id = %d", + $id + ) + ); + + if ( empty( $results ) ) { + WP_CLI::error( __( 'The legacy payment data could not be found.', 'easy-digital-downloads' ) ); + } + + foreach ( $results as $result ) { + $meta_value = maybe_unserialize( $result->meta_value ); + if ( is_array( $meta_value ) ) { + WP_CLI::line( $result->meta_key . ' =>' ); + foreach ( $meta_value as $key => $value ) { + WP_CLI::line( $key . ' => ' . print_r( maybe_unserialize( $value ), true ) ); + } + WP_CLI::line( '/' . $result->meta_key ); + } else { + WP_CLI::line( $result->meta_key . ' => ' . print_r( $meta_value, true ) ); + } + } + } + + /** + * Recalculates the sales and earnings for all downloads. + * + * @since 3.0 + * @return void + * + * wp edd recalculate_download_sales_earnings + */ + public function recalculate_download_sales_earnings() { + global $wpdb; + + $downloads = $wpdb->get_results( + "SELECT ID + FROM {$wpdb->posts} + WHERE post_type = 'download' + ORDER BY ID ASC" + ); + $total = count( $downloads ); + if ( ! empty( $total ) ) { + $progress = new \cli\progress\Bar( 'Recalculating Download Sales and Earnings', $total ); + foreach ( $downloads as $download ) { + edd_recalculate_download_sales_earnings( $download->ID ); + $progress->tick(); + } + $progress->finish(); + } + WP_CLI::line( __( 'Sales and Earnings successfully recalculated for all downloads.', 'easy-digital-downloads' ) ); + WP_CLI::line( __( 'Downloads Updated: ', 'easy-digital-downloads' ) . $total ); + } + + /** + * Recalculates all customer values. + * + * @since 3.1.2 + * @return void + */ + public function recalculate_customer_values() { + $customers = edd_get_customers( + array( + 'number' => 9999999, + ) + ); + $total = count( $customers ); + + if ( ! empty( $total ) ) { + $progress = new \cli\progress\Bar( 'Recalculating Customer Values', $total ); + foreach ( $customers as $customer ) { + $customer->recalculate_stats(); + $progress->tick(); + } + $progress->finish(); + } + + WP_CLI::line( __( 'Sales and Earnings successfully recalculated for all customers.', 'easy-digital-downloads' ) ); + WP_CLI::line( __( 'Customers Updated: ', 'easy-digital-downloads' ) . $total ); + } + + /** + * Removes legacy data from 2.9 and earlier that has been migrated to 3.0. + * + * ## OPTIONS + * + * --force=: If the routine should be run even if the upgrade routine has been run already + * + * ## EXAMPLES + * + * wp edd remove_legacy_data + * wp edd remove_legacy_data --force + */ + public function remove_legacy_data( $args, $assoc_args ) { + global $wpdb; + + WP_CLI::confirm( __( 'Do you want to remove legacy data? This will permanently remove legacy discounts, logs, and order notes.', 'easy-digital-downloads' ) ); + + $force = isset( $assoc_args['force'] ) ? true : false; + + /** + * Discounts + */ + if ( ! $force && edd_has_upgrade_completed( 'remove_legacy_discounts' ) ) { + WP_CLI::warning( __( 'Legacy discounts have already been removed. To run this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Removing old discount data.', 'easy-digital-downloads' ) ); + + $discount_ids = $wpdb->get_results( "SELECT ID FROM $wpdb->posts WHERE post_type = 'edd_discount'" ); + $discount_ids = wp_list_pluck( $discount_ids, 'ID' ); + $discount_ids = implode( ', ', $discount_ids ); + + if ( ! empty( $discount_ids ) ) { + $delete_posts_query = "DELETE FROM $wpdb->posts WHERE ID IN ({$discount_ids})"; + $wpdb->query( $delete_posts_query ); + + $delete_postmeta_query = "DELETE FROM $wpdb->postmeta WHERE post_id IN ({$discount_ids})"; + $wpdb->query( $delete_postmeta_query ); + } + + edd_set_upgrade_complete( 'remove_legacy_discounts' ); + } + + /** + * Logs + */ + if ( ! $force && edd_has_upgrade_completed( 'remove_legacy_logs' ) ) { + WP_CLI::warning( __( 'Legacy logs have already been removed. To run this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Removing old logs.', 'easy-digital-downloads' ) ); + + $log_ids = $wpdb->get_results( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'edd_log'" ); + $log_ids = wp_list_pluck( $log_ids, 'ID' ); + $log_ids = implode( ', ', $log_ids ); + + if ( ! empty( $log_ids ) ) { + $delete_query = "DELETE FROM {$wpdb->posts} WHERE post_type = 'edd_log'"; + $wpdb->query( $delete_query ); + + $delete_postmeta_query = "DELETE FROM {$wpdb->posts} WHERE ID IN ({$log_ids})"; + $wpdb->query( $delete_postmeta_query ); + } + + edd_set_upgrade_complete( 'remove_legacy_logs' ); + } + + /** + * Order notes + */ + if ( ! $force && edd_has_upgrade_completed( 'remove_legacy_order_notes' ) ) { + WP_CLI::warning( __( 'Legacy order notes have already been removed. To run this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Removing old order notes.', 'easy-digital-downloads' ) ); + + $note_ids = $wpdb->get_results( "SELECT comment_ID FROM {$wpdb->comments} WHERE comment_type = 'edd_payment_note'" ); + $note_ids = wp_list_pluck( $note_ids, 'comment_ID' ); + $note_ids = implode( ', ', $note_ids ); + + if ( ! empty( $note_ids ) ) { + $delete_query = "DELETE FROM {$wpdb->comments} WHERE comment_type = 'edd_payment_note'"; + $wpdb->query( $delete_query ); + + $delete_postmeta_query = "DELETE FROM {$wpdb->commentmeta} WHERE comment_id IN ({$note_ids})"; + $wpdb->query( $delete_postmeta_query ); + } + + edd_set_upgrade_complete( 'remove_legacy_order_notes' ); + } + + /** + * Customers + * + * @var \EDD\Database\Tables\Customers|false $customer_table + */ + $customer_table = edd_get_component_interface( 'customer', 'table' ); + if ( $customer_table instanceof \EDD\Database\Tables\Customers ) { + WP_CLI::line( __( 'Updating customers database table.', 'easy-digital-downloads' ) ); + + if ( $customer_table->column_exists( 'payment_ids' ) ) { + WP_CLI::line( __( 'Removing Payment IDs column.', 'easy-digital-downloads' ) ); + + $wpdb->query( "ALTER TABLE {$wpdb->edd_customers} DROP `payment_ids`" ); + } + + if ( $customer_table->column_exists( 'notes' ) ) { + WP_CLI::line( __( 'Removing notes column.', 'easy-digital-downloads' ) ); + + $wpdb->query( "ALTER TABLE {$wpdb->edd_customers} DROP `notes`" ); + } + } + + /** + * Customer emails + */ + if ( ! $force && edd_has_upgrade_completed( 'remove_legacy_customer_emails' ) ) { + WP_CLI::warning( __( 'Legacy customer emails have already been removed. To run this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Removing old customer emails.', 'easy-digital-downloads' ) ); + + $wpdb->query( "DELETE FROM {$wpdb->edd_customermeta} WHERE meta_key = 'additional_email'" ); + + edd_set_upgrade_complete( 'remove_legacy_customer_emails' ); + } + + /** + * Customer addresses + */ + if ( ! $force && edd_has_upgrade_completed( 'remove_legacy_customer_addresses' ) ) { + WP_CLI::warning( __( 'Legacy customer addresses have already been removed. To run this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Removing old customer addresses.', 'easy-digital-downloads' ) ); + + $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = '_edd_user_address'" ); + + edd_set_upgrade_complete( 'remove_legacy_customer_addresses' ); + } + + /** + * Orders + */ + if ( ! $force && edd_has_upgrade_completed( 'remove_legacy_orders' ) ) { + WP_CLI::warning( __( 'Legacy orders have already been removed. To run this anyway, use the --force argument.', 'easy-digital-downloads' ) ); + } else { + WP_CLI::line( __( 'Removing old orders.', 'easy-digital-downloads' ) ); + + $wpdb->query( + "DELETE orders, order_meta FROM {$wpdb->posts} orders + LEFT JOIN {$wpdb->postmeta} order_meta ON( orders.ID = order_meta.post_id ) + WHERE orders.post_type = 'edd_payment'" + ); + + edd_set_upgrade_complete( 'remove_legacy_orders' ); + } + } + + /* + * Create sample file download log data for your EDD site + * + * ## OPTIONS + * + * --number: The number of download logs to create + * + * ## EXAMPLES + * + * wp edd download_logs create --number=10 + */ + public function download_logs( $args, $assoc_args ) { + global $wpdb, $edd_logs; + + $error = false; + + // At some point we'll likely add another action for payments + if ( ! isset( $args ) || count( $args ) == 0 ) { + $error = __( 'No action specified, did you mean', 'easy-digital-downloads' ); + } elseif ( isset( $args ) && ! in_array( 'create', $args ) ) { + $error = __( 'Invalid action specified, did you mean', 'easy-digital-downloads' ); + } + + if ( $error ) { + $query = ''; + foreach ( $assoc_args as $key => $value ) { + $query .= ' --' . $key . '=' . $value; + } + + WP_CLI::error( + sprintf( $error . ' %s?', 'wp edd download_logs create' . $query ) + ); + + return; + } + + // Setup some defaults + $number = 1; + + if ( count( $assoc_args ) > 0 ) { + $number = ( array_key_exists( 'number', $assoc_args ) ) ? absint( $assoc_args['number'] ) : $number; + } + + + // First we need to find all downloads that have files associated. + $download_ids_with_file_meta = $wpdb->get_results( "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = 'edd_download_files'" ); + $download_ids_with_files = array(); + foreach ( $download_ids_with_file_meta as $meta_item ) { + if ( empty( $meta_item->meta_value ) ) { + continue; + } + $files = maybe_unserialize( $meta_item->meta_value ); + + // We have an empty array; + if ( empty( $files ) ) { + continue; + } + + $download_ids_with_files[ $meta_item->post_id ] = array_keys( $files ); + } + + global $wpdb; + $product_ids = implode('","', array_keys( $download_ids_with_files ) ); + $table = $wpdb->prefix . 'edd_order_items'; + $sql = 'SELECT order_id, product_id, price_id, uuid FROM ' . $table . ' WHERE product_id IN ( "' . $product_ids . '")'; + $results = $wpdb->get_results( $sql ); + + // Now generate some download logs for the files. + $progress = \WP_CLI\Utils\make_progress_bar( 'Creating File Download Logs', $number ); + $i = 1; + while ( $i <= $number ) { + $found_item = array_rand( $results, 1 ); + $item = $results[ $found_item ]; + + $order_id = (int) $item->order_id; + $order = edd_get_order( $order_id ); + $product_id = (int) $item->product_id; + + if ( edd_has_variable_prices( $product_id ) ) { + $price_id = (int) $item->price_id; + } else { + $price_id = false; + } + + $customer = new EDD_Customer( $order->customer_id ); + + $user_info = array( + 'email' => $order->email, + 'id' => $order->user_id, + 'name' => $order->name, + ); + + if ( empty( $download_ids_with_files[ $product_id ] ) ) { + continue; + } + + $file_id_key = array_rand( $download_ids_with_files[ $product_id ], 1 ); + $file_key = $download_ids_with_files[ $product_id ][ $file_id_key ]; + edd_record_download_in_log( + absint( $product_id ), + absint( $file_key ), + array(), + edd_get_ip(), + absint( $order_id ), + absint( $price_id ), + 'EDD; WPCLI; download_logs;' + ); + + $progress->tick(); + $i ++; + } + $progress->finish(); + } + + protected function get_fname() { + $names = array( + 'Ilse', + 'Emelda', + 'Aurelio', + 'Chiquita', + 'Cheryl', + 'Norbert', + 'Neville', + 'Wendie', + 'Clint', + 'Synthia', + 'Tobi', + 'Nakita', + 'Marisa', + 'Maybelle', + 'Onie', + 'Donnette', + 'Henry', + 'Sheryll', + 'Leighann', + 'Wilson', + ); + + return $names[ rand( 0, ( count( $names ) - 1 ) ) ]; + } + + protected function get_lname() { + $names = array( + 'Warner', + 'Roush', + 'Lenahan', + 'Theiss', + 'Sack', + 'Troutt', + 'Vanderburg', + 'Lisi', + 'Lemons', + 'Christon', + 'Kogut', + 'Broad', + 'Wernick', + 'Horstmann', + 'Schoenfeld', + 'Dolloff', + 'Murph', + 'Shipp', + 'Hursey', + 'Jacobi', + ); + + return $names[ rand( 0, ( count( $names ) - 1 ) ) ]; + } + + protected function get_domain() { + $domains = array( + 'example', + 'edd', + 'rcp', + 'affwp', + ); + + return $domains[ rand( 0, ( count( $domains ) - 1 ) ) ]; + } + + protected function get_tld() { + $tlds = array( + 'local', + 'test', + 'example', + 'localhost', + 'invalid', + ); + + return $tlds[ rand( 0, ( count( $tlds ) - 1 ) ) ]; + } +} diff --git a/includes/class-edd-cron.php b/includes/class-edd-cron.php new file mode 100755 index 00000000000..e6957bc01b7 --- /dev/null +++ b/includes/class-edd-cron.php @@ -0,0 +1,90 @@ + 604800, + 'display' => __( 'Once Weekly', 'easy-digital-downloads' ) + ); + + return $schedules; + } + + /** + * Schedules our events + * + * @since 1.6 + * @return void + */ + public function schedule_events() { + $this->weekly_events(); + $this->daily_events(); + } + + /** + * Schedule weekly events + * + * @access private + * @since 1.6 + * @return void + */ + private function weekly_events() { + if ( ! wp_next_scheduled( 'edd_weekly_scheduled_events' ) ) { + wp_schedule_event( current_time( 'timestamp', true ), 'weekly', 'edd_weekly_scheduled_events' ); + } + } + + /** + * Schedule daily events + * + * @access private + * @since 1.6 + * @return void + */ + private function daily_events() { + if ( ! wp_next_scheduled( 'edd_daily_scheduled_events' ) ) { + wp_schedule_event( current_time( 'timestamp', true ), 'daily', 'edd_daily_scheduled_events' ); + } + } + +} +$edd_cron = new EDD_Cron; diff --git a/includes/class-edd-customer-query.php b/includes/class-edd-customer-query.php new file mode 100644 index 00000000000..f2516d26347 --- /dev/null +++ b/includes/class-edd-customer-query.php @@ -0,0 +1,103 @@ +setup_customer( $customer ); + } + + /** + * Given the customer data, let's set the variables + * + * @since 2.3 + * + * @param object $customer Customer object. + * @return bool True if the object was setup correctly, false otherwise. + */ + private function setup_customer( $customer ) { + if ( ! is_object( $customer ) ) { + return false; + } + + foreach ( $customer as $key => $value ) { + switch ( $key ) { + case 'purchase_value': + $this->$key = floatval( $value ); + break; + case 'purchase_count': + $this->$key = absint( $value ); + break; + default: + $this->$key = $value; + break; + } + } + + // Customer ID and email are the only things that are necessary, make sure they exist + if ( ! empty( $this->id ) && ! empty( $this->email ) ) { + return true; + } + + return false; + } + + /** + * Magic __get method to dispatch a call to retrieve a protected property. + * + * @since 3.0 + * + * @param string $key + * @return mixed + */ + public function __get( $key = '' ) { + switch ( $key ) { + case 'emails': + return $this->get_emails(); + case 'payment_ids': + $payment_ids = $this->get_payment_ids(); + $payment_ids = implode( ',', $payment_ids ); + return $payment_ids; + default: + return isset( $this->{$key} ) + ? $this->{$key} + : edd_get_customer_meta( $this->id, $key ); + } + } + + /** + * Magic __set method to dispatch a call to update a protected property. + * + * @since 3.0 + * + * @param string $key Property name. + * @param mixed $value Property value. + * + * @return mixed Return value of setter being dispatched to. + */ + public function __set( $key, $value ) { + $key = sanitize_key( $key ); + + // Only real properties can be saved. + $keys = array_keys( get_class_vars( get_called_class() ) ); + + if ( ! in_array( $key, $keys, true ) ) { + return false; + } + + // Dispatch to setter method if value needs to be sanitized. + if ( method_exists( $this, 'set_' . $key ) ) { + return call_user_func( array( $this, 'set_' . $key ), $key, $value ); + } else { + $this->{$key} = $value; + } + } + + /** + * Creates a customer based on class vars. + * + * @since 2.3 + * + * @param array $data Array of attributes for a customer + * @return mixed False if not a valid creation, Customer ID if user is found or valid creation + */ + public function create( $data = array() ) { + if ( 0 !== $this->id || empty( $data ) ) { + return false; + } + + $defaults = array( + 'payment_ids' => '', + ); + + $args = wp_parse_args( $data, $defaults ); + $args = $this->sanitize_columns( $args ); + + if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) { + return false; + } + + /** + * Fires before a customer is created + * + * @param array $args Contains customer information such as payment ID, name, and email. + */ + do_action( 'edd_customer_pre_create', $args ); + + $created = false; + + // Add the customer + $customer_id = edd_add_customer( $args ); + + if ( ! empty( $customer_id ) ) { + + // Add the primary email address for this customer + edd_add_customer_email_address( array( + 'customer_id' => $customer_id, + 'email' => $args['email'], + 'type' => 'primary' + ) ); + + // Maybe add payments + if ( ! empty( $args['payment_ids'] ) && is_array( $args['payment_ids'] ) ) { + $payment_ids = array_unique( array_values( $args['payment_ids'] ) ); + + foreach ( $payment_ids as $payment_id ) { + edd_update_order( $payment_id, array( + 'customer_id' => $customer_id + ) ); + } + } + + // We've successfully added/updated the customer, reset the class vars with the new data + $customer = edd_get_customer( $customer_id ); + + // Setup the customer data with the values from DB + $this->setup_customer( $customer ); + + $created = $this->id; + } + + /** + * Fires after a customer is created + * + * @param int $created If created successfully, the customer ID. Defaults to false. + * @param array $args Contains customer information such as payment ID, name, and email. + */ + do_action( 'edd_customer_post_create', $created, $args ); + + return $created; + } + + /** + * Update a customer record. + * + * @since 2.3 + * + * @param array $data Array of data attributes for a customer (checked via whitelist) + * @return bool True if update was successful, false otherwise. + */ + public function update( $data = array() ) { + if ( empty( $data ) ) { + return false; + } + + $data = $this->sanitize_columns( $data ); + + do_action( 'edd_customer_pre_update', $this->id, $data ); + + $updated = false; + + if ( edd_update_customer( $this->id, $data ) ) { + $customer = edd_get_customer( $this->id ); + $this->setup_customer( $customer ); + + $updated = true; + } + + do_action( 'edd_customer_post_update', $updated, $this->id, $data ); + + return $updated; + } + + /** + * Attach an email address to the customer. + * + * @since 2.6 + * @since 3.0.1 This method will return customer email ID or false, instead of bool + * + * @param string $email The email address to remove from the customer. + * @param bool $primary Allows setting the email added as the primary. + * + * @return int|false ID of newly created customer email address, false on error. + */ + public function add_email( $email = '', $primary = false ) { + if ( ! is_email( $email ) ) { + return false; + } + + // Bail if email exists in the universe. + if ( $this->email_exists( $email ) ) { + return false; + } + + do_action( 'edd_customer_pre_add_email', $email, $this->id, $this ); + + // Primary or secondary + $type = ( true === $primary ) + ? 'primary' + : 'secondary'; + + // Update is used to ensure duplicate emails are not added. + $ret = edd_add_customer_email_address( + array( + 'customer_id' => $this->id, + 'email' => $email, + 'type' => $type, + ) + ); + + do_action( 'edd_customer_post_add_email', $email, $this->id, $this ); + + if ( $ret && true === $primary ) { + $this->set_primary_email( $email ); + } + + return $ret; + } + + /** + * Remove an email address from the customer. + * + * @since 2.6 + * @since 3.0 Updated to use custom table. + * + * @param string $email The email address to remove from the customer. + * @return bool True if the email was removed successfully, false otherwise. + */ + public function remove_email( $email = '' ) { + if ( ! is_email( $email ) ) { + return false; + } + + do_action( 'edd_customer_pre_remove_email', $email, $this->id, $this ); + + $email_address = edd_get_customer_email_address_by( 'email', $email ); + + $ret = $email_address + ? (bool) edd_delete_customer_email_address( $email_address->id ) + : false; + + do_action( 'edd_customer_post_remove_email', $email, $this->id, $this ); + + return $ret; + } + + /** + * Check if an email address already exists somewhere in the known universe + * of WordPress Users, or EDD customer email addresses. + * + * We intentionally skip the edd_customers table, to avoid race conditions + * when adding new customers and their email addresses at the same time. + * + * @since 3.0 + * + * @param string $email Email address to check. + * @return boolean True if assigned to existing customer, false otherwise. + */ + public function email_exists( $email = '' ) { + + // Bail if not an email address + if ( ! is_email( $email ) ) { + return false; + } + + // Return true if found in users table + if ( email_exists( $email ) ) { + return true; + } + + // Query email addresses table for this address + $exists = edd_get_customer_email_address_by( 'email' , $email ); + + // Return true if found in email addresses table + if ( ! empty( $exists ) ) { + return true; + } + + // Not found + return false; + } + + /** + * Set an email address as the customer's primary email. + * + * This will move the customer's previous primary email to an additional email. + * + * @since 2.6 + * @param string $new_primary_email The email address to remove from the customer. + * @return bool True if the email was set as primary successfully, false otherwise. + */ + public function set_primary_email( $new_primary_email = '' ) { + + // Default return value + $retval = false; + + // Bail if not an email + if ( ! is_email( $new_primary_email ) ) { + return $retval; + } + + do_action( 'edd_customer_pre_set_primary_email', $new_primary_email, $this->id, $this ); + + // Bail if already primary + if ( $new_primary_email === $this->email ) { + return true; + } + + // Get customer emails + $emails = edd_get_customer_email_addresses( array( + 'customer_id' => $this->id + ) ); + + // Pluck addresses, to help with in_array() calls + $plucked = wp_list_pluck( $emails, 'email' ); + + // Maybe fix a missing primary email address in the new table + if ( ! in_array( $this->email, $plucked, true ) ) { + + // Attempt to add the current primary if it's missing + $added = edd_add_customer_email_address( array( + 'customer_id' => $this->id, + 'email' => $this->email, + 'type' => 'primary' + ) ); + + // Maybe re-get all customer emails and re-pluck them + if ( ! empty( $added ) ) { + + // Get customer emails + $emails = edd_get_customer_email_addresses( array( + 'customer_id' => $this->id + ) ); + + // Pluck addresses, and look for the new one + $plucked = wp_list_pluck( $emails, 'email' ); + } + } + + // Bail if not an address for this customer + if ( ! in_array( $new_primary_email, $plucked, true ) ) { + return $retval; + } + + // Loop through addresses and juggle them + foreach ( $emails as $email ) { + + // Make old primary a secondary + if ( ( 'primary' === $email->type ) && ( $new_primary_email !== $email->email ) ) { + edd_update_customer_email_address( $email->id, array( + 'type' => 'secondary' + ) ); + } + + // Make new address primary + if ( ( 'secondary' === $email->type ) && ( $new_primary_email === $email->email ) ) { + edd_update_customer_email_address( $email->id, array( + 'type' => 'primary' + ) ); + } + } + + // Mismatch, so update the customer column + if ( $this->email !== $new_primary_email ) { + + // Update the email column on the customer row + $this->update( array( 'email' => $new_primary_email ) ); + + // Reload the customer emails for this object + $this->email = $new_primary_email; + $this->emails = $this->get_emails(); + $retval = true; + } + + do_action( 'edd_customer_post_set_primary_email', $new_primary_email, $this->id, $this ); + + return (bool) $retval; + } + + /** + * Before 3.0, when the primary email address was changed, it would cascade + * through all previous purchases and update the email address associated + * with it. Since 3.0, that is no longer the case. + * + * This method contains code that is no longer used, and is provided here as + * a convenience function if needed. + * + * @since 3.0 + */ + public function update_order_email_addresses( $email = '' ) { + + // Get the payments + $payment_ids = $this->get_payment_ids(); + + // Bail if no payments + if ( empty( $payment_ids ) ) { + return; + } + + // Update payment emails to primary email + foreach ( $payment_ids as $payment_id ) { + edd_update_payment_meta( $payment_id, 'email', $email ); + } + } + + /** + * Get the payment ids of the customer in an array. + * + * @since 2.6 + * + * @return array An array of payment IDs for the customer, or an empty array if none exist. + */ + public function get_payment_ids() { + + // Bail if no customer + if ( empty( $this->id ) ) { + return array(); + } + + // Get total orders + $count = edd_count_orders( array( + 'customer_id' => $this->id + ) ); + + // Get order IDs + $ids = edd_get_orders( array( + 'customer_id' => $this->id, + 'number' => $count, + 'fields' => 'ids', + 'no_found_rows' => true + ) ); + + // Cast IDs to ints + return array_map( 'absint', $ids ); + } + + /** + * Get an array of EDD_Payment objects from the payment_ids attached to the customer. + * + * @since 2.6 + * + * @param array|string $status A single status as a string or an array of statuses. + * @return array An array of EDD_Payment objects or an empty array. + */ + public function get_payments( $status = array() ) { + + // Get payment IDs + $payment_ids = $this->get_payment_ids(); + $payments = array(); + + // Bail if no IDs + if ( empty( $payment_ids ) ) { + return $payments; + } + + // Get payments one at a time (ugh...) + foreach ( $payment_ids as $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + + if ( empty( $status ) || ( is_array( $status ) && in_array( $payment->status, $status, true ) ) || $status === $payment->status ) { + $payments[] = $payment; + } + } + + return $payments; + } + + /** + * Attach payment to the customer then triggers increasing statistics. + * + * @since 2.3 + * + * @param int $order_id The Order ID to attach to the customer. + * @param bool $update_stats For backwards compatibility, if we should increase the stats or not. + * + * @return bool True if the attachment was successfully, false otherwise. + */ + public function attach_payment( $order_id = 0, $update_stats = true ) { + + // Bail if no payment ID. + if ( empty( $order_id ) ) { + return false; + } + + // Get order. + $order = edd_get_order( $order_id ); + + // Bail if payment does not exist. + if ( empty( $order ) ) { + return false; + } + + do_action( 'edd_customer_pre_attach_payment', $order->id, $this->id, $this ); + + $success = (int) $order->customer_id === (int) $this->id; + + // Update the order if it isn't already attached. + if ( ! $success ) { + // Update the order. + $success = (bool) edd_update_order( + $order_id, + array( + 'customer_id' => $this->id, + 'email' => $this->email, + ) + ); + } + + // Maybe update stats. + if ( ! empty( $success ) && ! empty( $update_stats ) ) { + $this->recalculate_stats(); + } + + do_action( 'edd_customer_post_attach_payment', $success, $order->id, $this->id, $this ); + + return $success; + } + + /** + * Remove a payment from this customer, then triggers reducing stats + * + * @since 2.3 + * + * @param integer $payment_id The Payment ID to remove. + * @param bool $update_stats For backwards compatibility, if we should increase the stats or not. + * + * @return bool $detached True if removed successfully, false otherwise. + */ + public function remove_payment( $payment_id = 0, $update_stats = true ) { + + // Bail if no payment ID + if ( empty( $payment_id ) ) { + return false; + } + + // Get payment + $payment = edd_get_payment( $payment_id ); + + // Bail if payment does not exist + if ( empty( $payment ) ) { + return false; + } + + // Get all previous payment IDs + $payments = $this->get_payment_ids(); + + // Bail if already attached + if ( ! in_array( $payment_id, $payments, true ) ) { + return true; + } + + // Only update stats when published or revoked + if ( ! in_array( $payment->status, array( 'complete', 'revoked' ), true ) ) { + $update_stats = false; + } + + do_action( 'edd_customer_pre_remove_payment', $payment->ID, $this->id, $this ); + + // Update the order + $success = (bool) edd_update_order( $payment_id, array( + 'customer_id' => 0, + 'email' => '' + ) ); + + // Maybe update stats + if ( ! empty( $success ) && ! empty( $update_stats ) ) { + $this->recalculate_stats(); + } + + do_action( 'edd_customer_post_remove_payment', $success, $payment->ID, $this->id, $this ); + + return $success; + } + + /** + * Recalculate stats for this customer. + * + * This replaces the older, less accurate increase/decrease methods. + * + * @since 3.0 + */ + public function recalculate_stats() { + $this->purchase_count = edd_count_orders( + array( + 'customer_id' => $this->id, + 'status' => edd_get_net_order_statuses(), + 'type' => 'sale', + ) + ); + + global $wpdb; + $statuses = edd_get_gross_order_statuses(); + $status_string = implode(', ', array_fill( 0, count( $statuses ), '%s' ) ); + + $this->purchase_value = (float) $wpdb->get_var( $wpdb->prepare( + "SELECT SUM(total / rate) + FROM {$wpdb->edd_orders} + WHERE customer_id = %d + AND status IN({$status_string})", + $this->id, + ...$statuses + ) ); + + // Update the customer purchase count & value + return $this->update( + array( + 'purchase_count' => $this->purchase_count, + 'purchase_value' => $this->purchase_value, + ) + ); + } + + /** Notes *****************************************************************/ + + /** + * Get the parsed notes for a customer as an array. + * + * @since 2.3 + * @since 3.0 Use the new Notes component & API. + * + * @param integer $length The number of notes to get. + * @param integer $paged What note to start at. + * + * @return array The notes requested. + */ + public function get_notes( $length = 20, $paged = 1 ) { + + // Number + $length = is_numeric( $length ) + ? absint( $length ) + : 20; + + // Offset + $offset = is_numeric( $paged ) && ( 1 !== $paged ) + ? ( ( absint( $paged ) - 1 ) * $length ) + : 0; + + // Return the paginated notes for back-compat + return edd_get_notes( array( + 'object_id' => $this->id, + 'object_type' => 'customer', + 'number' => $length, + 'offset' => $offset, + 'order' => 'desc', + ) ); + } + + /** + * Get the total number of notes we have after parsing. + * + * @since 2.3 + * @since 3.0 Use the new Notes component & API. + * + * @return int The number of notes for the customer. + */ + public function get_notes_count() { + return edd_count_notes( array( + 'object_id' => $this->id, + 'object_type' => 'customer', + ) ); + } + + /** + * Add a customer note. + * + * @since 2.3 + * @since 3.0 Use the new Notes component & API + * + * @param string $note The note to add + * @return string|boolean The new note if added successfully, false otherwise + */ + public function add_note( $note = '' ) { + + // Bail if note content is empty + $note = trim( $note ); + if ( empty( $note ) ) { + return false; + } + + /** + * Filter the note of a customer before it's added + * + * @since 2.3 + * @since 3.0 No longer includes the datetime stamp + * + * @param string $note The content of the note to add + * @return string + */ + $note = apply_filters( 'edd_customer_add_note_string', $note ); + + /** + * Allow actions before a note is added + * + * @since 2.3 + */ + do_action( 'edd_customer_pre_add_note', $note, $this->id, $this ); + + // Sanitize note + $note = trim( wp_kses( stripslashes( $note ), edd_get_allowed_tags() ) ); + + // Try to add the note + edd_add_note( array( + 'user_id' => 0, // Authored by System/Bot + 'object_id' => $this->id, + 'object_type' => 'customer', + 'content' => $note, + ) ); + + /** + * Allow actions after a note is added + * + * @since 3.0 Changed to an empty string since notes were moved out + */ + do_action( 'edd_customer_post_add_note', '', $note, $this->id, $this ); + + // Return the formatted note, so we can test, as well as update any displays + return $note; + } + + /** Meta ******************************************************************/ + + /** + * Retrieve customer meta field for a customer. + * + * @since 2.6 + * + * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys. Default empty. + * @param bool $single Optional, default is false. If true, return only the first value of the specified meta_key. + * This parameter has no effect if meta_key is not specified. + * + * @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true. + */ + public function get_meta( $key = '', $single = true ) { + return edd_get_customer_meta( $this->id, $key, $single ); + } + + /** + * Add meta data field to a customer. + * + * @since 2.6 + * + * @param string $meta_key Meta data name. + * @param mixed $meta_value Meta data value. Must be serializable if non-scalar. + * @param bool $unique Optional. Whether the same key should not be added. Default false. + * + * @return int|false Meta ID on success, false on failure. + */ + public function add_meta( $meta_key = '', $meta_value = '', $unique = false ) { + return edd_add_customer_meta( $this->id, $meta_key, $meta_value, $unique ); + } + + /** + * Update customer meta field based on customer ID. + * + * Use the $prev_value parameter to differentiate between meta fields with the + * same key and order ID. + * + * If the meta field for the order does not exist, it will be added. + * + * @since 2.6 + * + * @param string $meta_key Meta data key. + * @param mixed $meta_value Meta data value. Must be serializable if non-scalar. + * @param mixed $prev_value Optional. Previous value to check before removing. Default empty. + * + * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure. + */ + public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) { + return edd_update_customer_meta( $this->id, $meta_key, $meta_value, $prev_value ); + } + + /** + * Remove meta data matching criteria from a customer. + * + * You can match based on the key, or key and value. Removing based on key and value, will keep from removing duplicate + * meta data with the same key. It also allows removing all meta data matching key, if needed. + * + * @since 2.6 + * + * @param string $meta_key Meta data name. + * @param mixed $meta_value Optional. Meta data value. Must be serializable if non-scalar. Default empty. + * + * @return bool True on success, false on failure. + */ + public function delete_meta( $meta_key = '', $meta_value = '' ) { + return edd_delete_customer_meta( $this->id, $meta_key, $meta_value ); + } + + /** Private ***************************************************************/ + + /** + * Sanitize the data for update/create. + * + * @since 2.3 + * + * @param array $data The data to sanitize. + * @return array The sanitized data, based off column defaults. + */ + private function sanitize_columns( $data = array() ) { + $default_values = array(); + + foreach ( $data as $key => $type ) { + + // Only sanitize data that we were provided + if ( ! array_key_exists( $key, $data ) ) { + continue; + } + + switch ( $type ) { + case '%s': + if ( 'email' === $key ) { + $data[ $key ] = sanitize_email( $data[ $key ] ); + } else { + $data[ $key ] = sanitize_text_field( $data[ $key ] ); + } + break; + + case '%d': + if ( ! is_numeric( $data[ $key ] ) || absint( $data[ $key ] ) !== (int) $data[ $key ] ) { + $data[ $key ] = $default_values[ $key ]; + } else { + $data[ $key ] = absint( $data[ $key ] ); + } + break; + + case '%f': + // Convert what was given to a float + $value = floatval( $data[ $key ] ); + + if ( ! is_float( $value ) ) { + $data[ $key ] = $default_values[ $key ]; + } else { + $data[ $key ] = $value; + } + break; + + default: + $data[ $key ] = sanitize_text_field( $data[ $key ] ); + break; + } + } + + return $data; + } + + /** Helpers ***************************************************************/ + + /** + * Retrieve all of the IP addresses used by the customer. + * + * @since 3.0 + * + * @return array Array of objects containing IP address. + */ + public function get_ips() { + return edd_get_orders( array( + 'customer_id' => $this->id, + 'fields' => 'ip', + 'groupby' => 'ip', + ) ); + } + + /** + * Retrieve all the email addresses associated with this customer. + * + * @since 3.0 + * + * @return array + */ + public function get_emails() { + + // Add primary email. + $retval = array( $this->email ); + + // Fetch email addresses from the database. + $emails = edd_get_customer_email_addresses( array( + 'customer_id' => $this->id + ) ); + + // Pluck addresses and merg them + if ( ! empty( $emails ) ) { + + // We only want the email addresses + $emails = wp_list_pluck( $emails, 'email' ); + + // Merge with primary email + $retval = array_merge( $retval, $emails ); + } + + // Return unique results (to avoid duplicates) + return array_unique( $retval ); + } + + /** + * Retrieve an address. + * + * @since 3.0 + * + * @param boolean $is_primary Whether the address is the primary address. Default true. + * + * @return array|\EDD\Customers\Customer_Address|null Object if primary address requested, array otherwise. Null if no result for primary address. + */ + public function get_address( $is_primary = true ) { + $args = array( + 'customer_id' => $this->id, + 'is_primary' => $is_primary, + ); + if ( $is_primary ) { + $args['number'] = 1; + $args['orderby'] = 'date_created'; + $args['order'] = 'desc'; + } + $address = edd_get_customer_addresses( $args ); + if ( ! $is_primary ) { + return $address; + } + if ( is_array( $address ) && ! empty( $address[0] ) ) { + return $address[0]; + } + + return null; + } + + /** + * Retrieve all addresses. + * + * @since 3.0 + * + * @param string $type Address type. Default empty. + * + * @return \EDD\Customers\Customer_Address[] Array of addresses. + */ + public function get_addresses( $type = '' ) { + $addresses = edd_get_customer_addresses( array( + 'customer_id' => $this->id, + ) ); + + if ( ! empty( $type ) ) { + $addresses = wp_filter_object_list( $addresses, array( 'type' => $type ) ); + } + + return $addresses; + } + + /** Deprecated ************************************************************/ + + /** + * Increase the purchase count of a customer. + * + * @since 2.3 + * @deprecated 3.0 Use recalculate_stats() + * + * @param int $count The number to increment purchase count by. Default 1. + * @return int New purchase count. + */ + public function increase_purchase_count( $count = 1 ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Customer::recalculate_stats()' ); + + // Make sure it's numeric and not negative + if ( ! is_numeric( $count ) || absint( $count ) !== $count ) { + return false; + } + + $new_total = (int) $this->purchase_count + (int) $count; + + do_action( 'edd_customer_pre_increase_purchase_count', $count, $this->id, $this ); + + if ( $this->update( array( 'purchase_count' => $new_total ) ) ) { + $this->purchase_count = $new_total; + } + + do_action( 'edd_customer_post_increase_purchase_count', $this->purchase_count, $count, $this->id, $this ); + + return $this->purchase_count; + } + + /** + * Decrease the customer's purchase count. + * + * @since 2.3 + * @deprecated 3.0 Use recalculate_stats() + * + * @param int $count The number to decrement purchase count by. Default 1. + * @return mixed New purchase count if successful, false otherwise. + */ + public function decrease_purchase_count( $count = 1 ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Customer::recalculate_stats()' ); + + // Make sure it's numeric and not negative + if ( ! is_numeric( $count ) || absint( $count ) !== $count ) { + return false; + } + + $new_total = (int) $this->purchase_count - (int) $count; + + if ( $new_total < 0 ) { + $new_total = 0; + } + + do_action( 'edd_customer_pre_decrease_purchase_count', $count, $this->id, $this ); + + if ( $this->update( array( 'purchase_count' => $new_total ) ) ) { + $this->purchase_count = $new_total; + } + + do_action( 'edd_customer_post_decrease_purchase_count', $this->purchase_count, $count, $this->id, $this ); + + return $this->purchase_count; + } + + /** + * Increase the customer's lifetime value. + * + * @since 2.3 + * @deprecated 3.0 Use recalculate_stats() + * + * @param float $value The value to increase by. + * @return mixed New lifetime value if successful, false otherwise. + */ + public function increase_value( $value = 0.00 ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Customer::recalculate_stats()' ); + + $value = floatval( apply_filters( 'edd_customer_increase_value', $value, $this ) ); + $new_value = floatval( $this->purchase_value ) + $value; + + do_action( 'edd_customer_pre_increase_value', $value, $this->id, $this ); + + if ( $this->update( array( 'purchase_value' => $new_value ) ) ) { + $this->purchase_value = $new_value; + } + + do_action( 'edd_customer_post_increase_value', $this->purchase_value, $value, $this->id, $this ); + + return $this->purchase_value; + } + + /** + * Decrease a customer's lifetime value. + * + * @since 2.3 + * @deprecated 3.0 Use recalculate_stats() + * + * @param float $value The value to decrease by. + * @return mixed New lifetime value if successful, false otherwise. + */ + public function decrease_value( $value = 0.00 ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Customer::recalculate_stats()' ); + + $value = floatval( apply_filters( 'edd_customer_decrease_value', $value, $this ) ); + $new_value = floatval( $this->purchase_value ) - $value; + + if ( $new_value < 0 ) { + $new_value = 0.00; + } + + do_action( 'edd_customer_pre_decrease_value', $value, $this->id, $this ); + + if ( $this->update( array( 'purchase_value' => $new_value ) ) ) { + $this->purchase_value = $new_value; + } + + do_action( 'edd_customer_post_decrease_value', $this->purchase_value, $value, $this->id, $this ); + + return $this->purchase_value; + } +} diff --git a/includes/class-edd-db-customer-meta.php b/includes/class-edd-db-customer-meta.php new file mode 100644 index 00000000000..8855ff00c11 --- /dev/null +++ b/includes/class-edd-db-customer-meta.php @@ -0,0 +1,21 @@ +get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $this->primary_key = %s LIMIT 1;", $row_id ) ); + } + + /** + * Retrieve a row by a specific column / value + * + * @since 2.1 + * @return object + */ + public function get_by( $column, $row_id ) { + global $wpdb; + $column = esc_sql( $column ); + return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $column = %s LIMIT 1;", $row_id ) ); + } + + /** + * Retrieve a specific column's value by the primary key + * + * @since 2.1 + * @return string + */ + public function get_column( $column, $row_id ) { + global $wpdb; + $column = esc_sql( $column ); + return $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM $this->table_name WHERE $this->primary_key = %s LIMIT 1;", $row_id ) ); + } + + /** + * Retrieve a specific column's value by the the specified column / value + * + * @since 2.1 + * @return string + */ + public function get_column_by( $column, $column_where, $column_value ) { + global $wpdb; + $column_where = esc_sql( $column_where ); + $column = esc_sql( $column ); + return $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", $column_value ) ); + } + + /** + * Insert a new row + * + * @since 2.1 + * @return int + */ + public function insert( $data, $type = '' ) { + global $wpdb; + + // Set default values + $data = wp_parse_args( $data, $this->get_column_defaults() ); + + do_action( 'edd_pre_insert_' . $type, $data ); + + // Initialise column format array + $column_formats = $this->get_columns(); + + // Force fields to lower case + $data = array_change_key_case( $data ); + + // White list columns + $data = array_intersect_key( $data, $column_formats ); + + // Reorder $column_formats to match the order of columns given in $data + $data_keys = array_keys( $data ); + $column_formats = array_merge( array_flip( $data_keys ), $column_formats ); + + $wpdb->insert( $this->table_name, $data, $column_formats ); + $wpdb_insert_id = $wpdb->insert_id; + + do_action( 'edd_post_insert_' . $type, $wpdb_insert_id, $data ); + + return $wpdb_insert_id; + } + + /** + * Update a row + * + * @since 2.1 + * @return bool + */ + public function update( $row_id, $data = array(), $where = '' ) { + + global $wpdb; + + // Row ID must be positive integer + $row_id = absint( $row_id ); + + if( empty( $row_id ) ) { + return false; + } + + if( empty( $where ) ) { + $where = $this->primary_key; + } + + // Initialise column format array + $column_formats = $this->get_columns(); + + // Force fields to lower case + $data = array_change_key_case( $data ); + + // White list columns + $data = array_intersect_key( $data, $column_formats ); + + // Reorder $column_formats to match the order of columns given in $data + $data_keys = array_keys( $data ); + $column_formats = array_merge( array_flip( $data_keys ), $column_formats ); + + if ( false === $wpdb->update( $this->table_name, $data, array( $where => $row_id ), $column_formats ) ) { + return false; + } + + return true; + } + + /** + * Delete a row identified by the primary key + * + * @since 2.1 + * @return bool + */ + public function delete( $row_id = 0 ) { + + global $wpdb; + + // Row ID must be positive integer + $row_id = absint( $row_id ); + + if( empty( $row_id ) ) { + return false; + } + + if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM $this->table_name WHERE $this->primary_key = %d", $row_id ) ) ) { + return false; + } + + return true; + } + + /** + * Check if the given table exists + * + * @since 2.4 + * @param string $table The table name + * @return bool If the table name exists + */ + public function table_exists( $table ) { + global $wpdb; + $table = sanitize_text_field( $table ); + + return $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE '%s'", $table ) ) === $table; + } + + /** + * Check if the table was ever installed + * + * @since 2.4 + * @return bool Returns if the customers table was installed and upgrade routine run + */ + public function installed() { + return $this->table_exists( $this->table_name ); + } + +} diff --git a/includes/class-edd-discount.php b/includes/class-edd-discount.php new file mode 100644 index 00000000000..d2b17684722 --- /dev/null +++ b/includes/class-edd-discount.php @@ -0,0 +1,1910 @@ +find_by_code( $_id_or_code_or_name ); + + // Name + } elseif ( $by_name ) { + $discount = $this->find_by_name( $_id_or_code_or_name ); + + // Default to ID + } else { + $discount = edd_get_discount( absint( $_id_or_code_or_name ) ); + } + + // Setup or bail + if ( ! empty( $discount ) ) { + $this->setup_discount( $discount ); + } else { + return false; + } + } + + /** + * Magic __get method to dispatch a call to retrieve a protected property. + * + * @since 2.7 + * + * @param mixed $key + * @return mixed + */ + public function __get( $key = '' ) { + $key = sanitize_key( $key ); + + // Back compat for ID + if ( 'discount_id' === $key || 'ID' === $key ) { + return (int) $this->id; + + // Method + } elseif ( method_exists( $this, "get_{$key}" ) ) { + return call_user_func( array( $this, "get_{$key}" ) ); + + // Property + } elseif ( property_exists( $this, $key ) ) { + return $this->{$key}; + + // Other... + } else { + + // Account for old property keys from pre 3.0 + switch ( $key ) { + case 'post_author': + break; + + case 'post_date': + case 'post_date_gmt': + return $this->date_created; + + case 'post_modified': + case 'post_modified_gmt': + return $this->date_modified; + + case 'post_content': + case 'post_title': + return $this->name; + + case 'post_excerpt': + case 'post_status': + return $this->status; + + case 'comment_status': + case 'ping_status': + case 'post_password': + case 'post_name': + case 'to_ping': + case 'pinged': + case 'post_modified': + case 'post_modified_gmt': + case 'post_content_filtered': + case 'post_parent': + case 'guid': + case 'menu_order': + case 'post_mime_type': + case 'comment_count': + case 'filter': + return ''; + + case 'post_type': + return 'edd_discount'; + + case 'expiration': + return $this->get_expiration(); + + case 'start': + return $this->start_date; + + case 'min_price': + return $this->min_charge_amount; + + case 'use_once': + case 'is_single_use': + case 'once_per_customer': + return $this->get_is_single_use(); + + case 'uses': + return $this->use_count; + + case 'not_global': + case 'is_not_global': + return 'global' === $this->scope ? false : true; + } + + return new WP_Error( 'edd-discount-invalid-property', sprintf( __( 'Can\'t get property %s', 'easy-digital-downloads' ), $key ) ); + } + } + + /** + * Magic __set method to dispatch a call to update a protected property. + * + * @since 2.7 + * + * @see set() + * + * @param string $key Property name. + * @param mixed $value Property value. + * + * @return mixed Value of setter being dispatched to. + */ + public function __set( $key, $value ) { + $key = sanitize_key( $key ); + + // Only real properties can be saved. + $keys = array_keys( get_class_vars( get_called_class() ) ); + $old_keys = array( + 'is_single_use', + 'uses', + 'expiration', + 'start', + 'min_price', + 'use_once', + 'is_not_global', + ); + + if ( ! in_array( $key, $keys, true ) && ! in_array( $key, $old_keys, true ) ) { + return false; + } + + // Dispatch to setter method if value needs to be sanitized + if ( method_exists( $this, 'set_' . $key ) ) { + return call_user_func( array( $this, 'set_' . $key ), $key, $value ); + } elseif ( in_array( $key, $old_keys, true ) ) { + switch ( $key ) { + case 'expiration': + $this->end_date = $value; + break; + case 'start': + $this->start_date = $value; + break; + case 'min_price': + $this->min_charge_amount = $value; + break; + case 'use_once': + case 'is_single_use': + $this->once_per_customer = $value; + break; + case 'uses': + $this->use_count = $value; + break; + case 'not_global': + case 'is_not_global': + $this->scope = $value ? 'not_global' : 'global'; + break; + } + } else { + $this->{$key} = $value; + } + } + + /** + * Handle method dispatch dynamically. + * + * @param string $method Method name. + * @param array $args Arguments to be passed to method. + * + * @return mixed + */ + public function __call( $method, $args ) { + $property = strtolower( str_replace( array( 'setup_', 'get_' ), '', $method ) ); + if ( ! method_exists( $this, $method ) && property_exists( $this, $property ) ) { + return $this->{$property}; + } + } + + /** + * Magic __toString method. + * + * @since 3.0 + */ + public function __toString() { + return $this->code; + } + + /** + * Converts the instance of the EDD_Discount object into an array for special cases. + * + * @since 2.7 + * + * @return array EDD_Discount object as an array. + */ + public function array_convert() { + return get_object_vars( $this ); + } + + /** + * Find a discount in the database with the code supplied. + * + * @since 2.7 + * @access private + * + * @param string $code Discount code. + * @return object WP_Post instance of the discount. + */ + private function find_by_code( $code = '' ) { + return edd_get_discount_by( 'code', $code ); + } + + /** + * Find a discount in the database with the name supplied. + * + * @since 2.7 + * @access private + * + * @param string $name Discount name. + * @return object WP_Post instance of the discount. + */ + private function find_by_name( $name = '' ) { + return edd_get_discount_by( 'name', $name ); + } + + /** + * Setup object vars with discount WP_Post object. + * + * @since 2.7 + * @access private + * + * @param object $discount WP_Post instance of the discount. + * @return bool Object initialization successful or not. + */ + private function setup_discount( $discount = null ) { + if ( is_null( $discount ) ) { + return false; + } + + if ( ! is_object( $discount ) ) { + return false; + } + + if ( is_wp_error( $discount ) ) { + return false; + } + + /** + * Fires before the instance of the EDD_Discount object is set up. + * + * @since 2.7 + * + * @param object EDD_Discount EDD_Discount instance of the discount object. + * @param object WP_Post $discount WP_Post instance of the discount object. + */ + do_action( 'edd_pre_setup_discount', $this, $discount ); + + $vars = get_object_vars( $discount ); + + foreach ( $vars as $key => $value ) { + switch ( $key ) { + case 'start_date': + case 'end_date': + if ( '0000-00-00 00:00:00' === $value || is_null( $value ) ) { + $this->{$key} = false; + break; + } + case 'notes': + if ( ! empty( $value ) ) { + $this->{$key} = $value; + } + break; + case 'id': + $this->{$key} = (int) $value; + break; + case 'min_charge_amount': + $this->min_charge_amount = $value; + break; + default: + if ( is_string( $value ) ) { + @json_decode( $value ); + if ( json_last_error() !== JSON_ERROR_NONE ) { + $this->{$key} = json_decode( $value ); + } + } + + $this->{$key} = $value; + break; + } + } + + /** + * Some object vars need to be setup manually as the values need to be + * pulled in from the `edd_adjustmentmeta` table. + */ + $this->excluded_products = (array) edd_get_adjustment_meta( $this->id, 'excluded_product', false ); + $this->product_reqs = (array) edd_get_adjustment_meta( $this->id, 'product_requirement', false ); + $this->product_condition = (string) edd_get_adjustment_meta( $this->id, 'product_condition', true ); + + /** + * Fires after the instance of the EDD_Discount object is set up. Allows extensions to add items to this object via hook. + * + * @since 2.7 + * + * @param object EDD_Discount EDD_Discount instance of the discount object. + * @param object WP_Post $discount WP_Post instance of the discount object. + */ + do_action( 'edd_setup_discount', $this, $discount ); + + if ( ! empty( $this->id ) ) { + return true; + } + + return false; + } + + /** + * Helper method to retrieve meta data associated with the discount. + * + * @since 2.7 + * + * @param string $key Meta key. + * @param bool $single Return single item or array. + * + * @return mixed + */ + public function get_meta( $key = '', $single = true ) { + return edd_get_adjustment_meta( $this->id, $key, $single ); + } + + /** + * Helper method to update meta data associated with the discount. + * + * @since 2.7 + * + * @param string $key Meta key to update. + * @param string $value New meta value to set. + * @param string $prev_value Optional. Previous meta value. + * + * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure. + */ + public function update_meta( $key, $value = '', $prev_value = '' ) { + $filter_key = '_edd_discount_' . $key; + + /** + * Filters the meta value being updated. + * The key is prefixed with `_edd_discount_` for 2.9 backwards compatibility. + * + * @param mixed $value Value being set. + * @param int $id Discount ID. + */ + $value = apply_filters( 'edd_update_discount_meta_' . $filter_key, $value, $this->id ); + + return edd_update_adjustment_meta( $this->id, $key, $value, $prev_value ); + } + + /** + * Retrieve the code used to apply the discount. + * + * @since 2.7 + * + * @return string Discount code. + */ + public function get_code() { + /** + * Filters the discount code. + * + * @since 2.7 + * + * @param string $code Discount code. + * @param int $ID Discount ID. + */ + return apply_filters( 'edd_get_discount_code', $this->code, $this->id ); + } + + /** + * Retrieve the status of the discount + * + * @since 2.7 + * + * @return string Discount code status (active/inactive). + */ + public function get_status() { + /** + * Filters the discount status. + * + * @since 2.7 + * + * @param string $code Discount status (active or inactive). + * @param int $ID Discount ID. + */ + return apply_filters( 'edd_get_discount_status', $this->status, $this->id ); + } + + /** + * Retrieves the status label of the discount. + * + * This method exists as a helper, until legitimate Status classes can be + * registered that will contain an array of status-specific labels. + * + * @since 2.9 + * + * @return string Status label for the current discount. + */ + public function get_status_label() { + + // Default label + $label = ucwords( $this->status ); + + // Specific labels + switch ( $this->status ) { + case '': + $label = __( 'None', 'easy-digital-downloads' ); + break; + case 'draft': + $label = __( 'Draft', 'easy-digital-downloads' ); + break; + case 'expired': + $label = __( 'Expired', 'easy-digital-downloads' ); + break; + case 'inactive': + $label = __( 'Inactive', 'easy-digital-downloads' ); + break; + case 'active': + $label = __( 'Active', 'easy-digital-downloads' ); + break; + case 'inherit': + if ( ! empty( $this->parent ) ) { + $parent = edd_get_discount( $this->parent ); + $label = $parent->get_status_label(); + break; + } + } + + /** + * Filters the discount status. + * + * @since 2.9 + * + * @param string $label Discount status label. + * @param string $status Discount status (active or inactive). + * @param int $id Discount ID. + */ + return apply_filters( 'edd_get_discount_status_label', $label, $this->status, $this->id ); + } + + /** + * Retrieve the type of discount. + * + * @since 2.7 + * + * @return string Discount type (percent or flat amount). + */ + public function get_type() { + /** + * Filters the discount type. + * + * @since 2.7 + * + * @param string $code Discount type (percent or flat amount). + * @param int $ID Discount ID. + */ + return apply_filters( 'edd_get_discount_type', $this->amount_type, $this->id ); + } + + /** + * Retrieve the discount amount. + * + * @since 2.7 + * + * @return mixed float Discount amount. + */ + public function get_amount() { + /** + * Filters the discount amount. + * + * @since 2.7 + * + * @param float $amount Discount amount. + * @param int $ID Discount ID. + */ + return (float) apply_filters( 'edd_get_discount_amount', $this->amount, $this->id ); + } + + /** + * Retrieve the discount requirements for the discount to be satisfied. + * + * @since 2.7 + * + * @return array IDs of required downloads. + */ + public function get_product_reqs() { + + /** + * Filters the download requirements. + * + * @since 2.7 + * + * @param array $product_reqs IDs of required products. + * @param int $ID Discount ID. + */ + return (array) apply_filters( 'edd_get_discount_product_reqs', $this->product_reqs, $this->id ); + } + + /** + * Retrieve the discount scope. + * + * This used to be called "is_not_global". That filter is still here for backwards compatibility. + * + * @since 3.0 + * + * @return string The scope, i.e. "global". + */ + public function get_scope() { + $legacy_value = apply_filters( 'edd_discount_is_not_global', null, $this->id ); + + if ( ! is_null( $legacy_value ) ) { + $this->scope = $legacy_value ? 'global' : 'not_global'; + } + + return apply_filters( 'edd_get_discount_scope', $this->scope, $this->id ); + } + + /** + * Retrieve the product condition. + * + * @since 2.7 + * + * @return string Product condition + */ + public function get_product_condition() { + /** + * Filters the product condition. + * + * @since 2.7 + * + * @param string $product_condition Product condition. + * @param int $ID Discount ID. + */ + return apply_filters( 'edd_discount_product_condition', $this->product_condition, $this->id ); + } + + /** + * Retrieve the downloads that are excluded from having this discount code applied. + * + * @since 2.7 + * + * @return array IDs of excluded downloads. + */ + public function get_excluded_products() { + /** + * Filters the excluded downloads. + * + * @since 2.7 + * + * @param array $excluded_products IDs of excluded products. + * @param int $ID Discount ID. + */ + return (array) apply_filters( 'edd_get_discount_excluded_products', $this->excluded_products, $this->id ); + } + + /** + * Retrieve the start date. + * + * @since 2.7 + * + * @return string Start date. + */ + public function get_start_date() { + /** + * Filters the start date. + * + * @since 2.7 + * + * @param string $start Discount start date. + * @param int $ID Discount ID. + */ + return apply_filters( 'edd_get_discount_start', $this->start_date, $this->id ); + } + + /** + * Retrieve the end date. + * + * @since 2.7 + * + * @return string End date. + */ + public function get_expiration() { + /** + * Filters the end date. + * + * @since 2.7 + * + * @param string $expiration Discount expiration date. + * @param int $ID Discount ID. + */ + return apply_filters( 'edd_get_discount_expiration', $this->end_date, $this->id ); + } + + /** + * Retrieve the uses for the discount code. + * + * @since 2.7 + * + * @return int Uses. + */ + public function get_uses() { + /** + * Filters the maximum uses. + * + * @since 2.7 + * + * @param int $max_uses Maximum uses. + * @param int $ID Discount ID. + */ + return (int) apply_filters( 'edd_get_discount_uses', $this->use_count, $this->id ); + } + + /** + * Retrieve the maximum uses for the discount code. + * + * @since 2.7 + * + * @return int Maximum uses. + */ + public function get_max_uses() { + /** + * Filters the maximum uses. + * + * @since 2.7 + * + * @param int $max_uses Maximum uses. + * @param int $ID Discount ID. + */ + return (int) apply_filters( 'edd_get_discount_max_uses', $this->max_uses, $this->id ); + } + + /** + * Retrieve the minimum spend required for the discount to be satisfied. + * + * @since 2.7 + * + * @return mixed float Minimum spend. + */ + public function get_min_price() { + /** + * Filters the minimum price. + * + * @since 2.7 + * + * @param float $min_price Minimum price. + * @param int $ID Discount ID. + */ + return (float) apply_filters( 'edd_get_discount_min_price', $this->min_charge_amount, $this->id ); + } + + /** + * Retrieve the usage limit per limit (if the discount can only be used once per customer). + * + * @since 2.7 + * + * @return bool Once use per customer? + */ + public function get_is_single_use() { + return $this->get_once_per_customer(); + } + + /** + * Retrieve the usage limit per limit (if the discount can only be used once per customer). + * + * @since 3.0 + * + * @return bool Once use per customer? + */ + public function get_once_per_customer() { + /** + * Filters the single use meta value. + * + * @since 2.7 + * + * @param bool $is_single_use Is the discount only allowed to be used once per customer. + * @param int $ID Discount ID. + */ + return (bool) apply_filters( 'edd_is_discount_single_use', $this->once_per_customer, $this->id ); + } + + /** + * Check if a discount exists. + * + * @since 2.7 + * + * @return bool Discount exists. + */ + public function exists() { + if ( ! $this->id > 0 ) { + return false; + } + + return true; + } + + /** + * Once object variables has been set, an update is needed to persist them to the database. + * + * This is now simply a wrapper to the add() method which handles creating new discounts and updating existing ones. + * + * @since 2.7 + * + * @return bool True if the save was successful, false if it failed or wasn't needed. + */ + public function save() { + $args = get_object_vars( $this ); + $saved = $this->add( $args ); + + return $saved; + } + + /** + * Create a new discount. If the discount already exists in the database, update it. + * + * @since 2.7 + * + * @param array $args Discount details. + * @return mixed bool|int false if data isn't passed and class not instantiated for creation, or post ID for the new discount. + */ + public function add( $args = array() ) { + + // If no code is provided, return early with false + if ( empty( $args['code'] ) ) { + return false; + } + + if ( ! empty( $this->id ) && $this->exists() ) { + return $this->update( $args ); + + } else { + $args = self::convert_legacy_args( $args ); + + if ( ! empty( $args['start_date'] ) ) { + $args['start_date'] = date( 'Y-m-d H:i:s', strtotime( $args['start_date'], current_time( 'timestamp' ) ) ); + } + + if ( ! empty( $args['end_date'] ) ) { + $args['end_date'] = date( 'Y-m-d H:i:s', strtotime( $args['end_date'], current_time( 'timestamp' ) ) ); + + if ( strtotime( $args['end_date'], current_time( 'timestamp' ) ) < current_time( 'timestamp' ) ) { + $args['status'] = 'expired'; + } + } + + if ( ! empty( $args['start_date'] ) && ! empty( $args['end_date'] ) ) { + $start_timestamp = strtotime( $args['start_date'], current_time( 'timestamp' ) ); + $end_timestamp = strtotime( $args['end_date'], current_time( 'timestamp' ) ); + + if ( $start_timestamp > $end_timestamp ) { + // Set the expiration date to the start date if start is later than expiration + $args['end_date'] = $args['start_date']; + } + } + + // Assume discount status is "active" if it has not been set + if ( ! isset( $args['status'] ) ) { + $args['status'] = 'active'; + } + + /** + * Add a new discount to the database. + */ + + /** + * Filters the args before being inserted into the database. + * + * @since 2.7 + * + * @param array $args Discount args. + */ + $args = apply_filters( 'edd_insert_discount', $args ); + + /** + * Filters the args before being inserted into the database (kept for backwards compatibility purposes) + * + * @since 2.7 + * @since 3.0 Updated parameters to pass $args twice for backwards compatibility. + * + * @param array $args Discount args. + */ + $args = apply_filters( 'edd_insert_discount_args', $args, $args ); + + $args = $this->sanitize_columns( $args ); + + /** + * Fires before the discount has been added to the database. + * + * @since 2.7 + * + * @param array $args Discount args. + */ + do_action( 'edd_pre_insert_discount', $args ); + + foreach ( $args as $key => $value ) { + $this->{$key} = $value; + } + + // We have to ensure an ID is not passed to edd_add_discount() + unset( $args['id'] ); + + $id = edd_add_discount( $args ); + + // The DB class 'add' implies an update if the discount being asked to be created already exists + if ( ! empty( $id ) ) { + + // We need to update the ID of the instance of the object in order to add meta + $this->id = $id; + + if ( isset( $args['excluded_products'] ) ) { + if ( is_array( $args['excluded_products'] ) ) { + foreach ( $args['excluded_products'] as $product ) { + edd_add_adjustment_meta( $this->id, 'excluded_product', absint( $product ) ); + } + } + } + + if ( isset( $args['product_reqs'] ) ) { + if ( is_array( $args['product_reqs'] ) ) { + foreach ( $args['product_reqs'] as $product ) { + edd_add_adjustment_meta( $this->id, 'product_requirement', absint( $product ) ); + } + } + } + } + + /** + * Fires after the discount code is inserted. + * + * @since 2.7 + * + * @param array $meta { + * The discount details. + * + * @type string $code The discount code. + * @type string $name The name of the discount. + * @type string $status The discount status. Defaults to active. + * @type int $uses The current number of uses. + * @type int $max_uses The max number of uses. + * @type string $start The start date. + * @type int $min_price The minimum price required to use the discount code. + * @type array $product_reqs The product IDs required to use the discount code. + * @type string $product_condition The conditions in which a product(s) must meet to use the discount code. + * @type array $excluded_products Product IDs excluded from this discount code. + * @type bool $is_not_global If the discount code is not globally applied to all products. Defaults to false. + * @type bool $is_single_use If the code cannot be used more than once per customer. Defaults to false. + * } + * @param int $ID The ID of the discount that was inserted. + */ + do_action( 'edd_post_insert_discount', $args, $this->id ); + + // Discount code created + return $id; + } + } + + /** + * Update an existing discount in the database. + * + * @since 2.7 + * + * @param array $args Discount details. + * @return bool True if update is successful, false otherwise. + */ + public function update( $args = array() ) { + $args = self::convert_legacy_args( $args ); + $ret = false; + + /** + * Filter the data being updated + * + * @since 2.7 + * + * @param array $args Discount args. + * @param int $ID Discount ID. + */ + $args = apply_filters( 'edd_update_discount', $args, $this->id ); + $args = $this->sanitize_columns( $args ); + + // Get current time once to avoid inconsistencies + $current_time = current_time( 'timestamp' ); + + if ( ! empty( $args['start_date'] ) && ! empty( $args['end_date'] ) ) { + $start_timestamp = strtotime( $args['start_date'], $current_time ); + $end_timestamp = strtotime( $args['end_date'], $current_time ); + + // Set the expiration date to the start date if start is later than expiration + if ( $start_timestamp > $end_timestamp ) { + $args['end_date'] = $args['start_date']; + } + } + + // Start date + if ( ! empty( $args['start_date'] ) ) { + $args['start_date'] = date( 'Y-m-d H:i:s', strtotime( $args['start_date'], $current_time ) ); + } + + // End date + if ( ! empty( $args['end_date'] ) ) { + $args['end_date'] = date( 'Y-m-d H:i:s', strtotime( $args['end_date'], $current_time ) ); + } + + if ( isset( $args['excluded_products'] ) ) { + // Reset meta + edd_delete_adjustment_meta( $this->id, 'excluded_product' ); + + if ( is_array( $args['excluded_products'] ) ) { + // Now add each newly excluded product + foreach ( $args['excluded_products'] as $product ) { + edd_add_adjustment_meta( $this->id, 'excluded_product', absint( $product ) ); + } + } + } + + if ( isset( $args['product_reqs'] ) ) { + // Reset meta + edd_delete_adjustment_meta( $this->id, 'product_requirement' ); + + if ( is_array( $args['product_reqs'] ) ) { + // Now add each newly required product + foreach ( $args['product_reqs'] as $product ) { + edd_add_adjustment_meta( $this->id, 'product_requirement', absint( $product ) ); + } + } + } + + // Switch `type` to `amount_type` + if ( ! isset( $args['amount_type'] ) && ! empty( $args['type'] ) && 'discount' !== $args['type'] ) { + $args['amount_type'] = $args['type']; + } + + // Force `type` to `discount` + $args['type'] = 'discount'; + + /** + * Fires before the discount has been updated in the database. + * + * @since 2.7 + * + * @param array $args Discount args. + * @param int $ID Discount ID. + */ + do_action( 'edd_pre_update_discount', $args, $this->id ); + + // If we are using the discounts DB + if ( edd_update_discount( $this->id, $args ) ) { + $discount = edd_get_discount( $this->id ); + $this->setup_discount( $discount ); + $ret = true; + } + + /** + * Fires after the discount has been updated in the database. + * + * @since 2.7 + * + * @param array $args Discount args. + * @param int $ID Discount ID. + */ + do_action( 'edd_post_update_discount', $args, $this->id ); + + return $ret; + } + + /** + * Update the status of the discount. + * + * @since 2.7 + * + * @param string $new_status New status (default: active) + * @return bool If the status been updated or not. + */ + public function update_status( $new_status = 'active' ) { + + /** + * Fires before the status of the discount is updated. + * + * @since 2.7 + * + * @param int $ID Discount ID. + * @param string $new_status New status. + * @param string $post_status Post status. + */ + do_action( 'edd_pre_update_discount_status', $this->id, $new_status, $this->status ); + + $ret = $this->update( array( 'status' => $new_status ) ); + + /** + * Fires after the status of the discount is updated. + * + * @since 2.7 + * + * @param int $ID Discount ID. + * @param string $new_status New status. + * @param string $status Post status. + */ + do_action( 'edd_post_update_discount_status', $this->id, $new_status, $this->status ); + + return (bool) $ret; + } + + /** + * Check if the discount has started. + * + * @since 2.7 + * + * @param bool $set_error Whether an error message be set in session. + * @return bool Is discount started? + */ + public function is_started( $set_error = true ) { + $return = false; + + if ( $this->start_date ) { + $start_date = strtotime( $this->start_date ); + + if ( $start_date < time() ) { + // Discount has pased the start date + $return = true; + } elseif ( $set_error ) { + edd_set_error( 'edd-discount-error', _x( 'This discount is invalid.', 'error shown when attempting to use a discount before its start date', 'easy-digital-downloads' ) ); + } + } else { + // No start date for this discount, so has to be true + $return = true; + } + + /** + * Filters if the discount has started or not. + * + * @since 2.7 + * + * @param bool $return Has the discount started or not. + * @param int $ID Discount ID. + * @param bool $set_error Whether an error message be set in session. + */ + return apply_filters( 'edd_is_discount_started', $return, $this->id, $set_error ); + } + + /** + * Check if the discount has expired. + * + * @since 2.7 + * + * @param bool $update Update the discount to expired if an one is found but has an active status + * @return bool Has the discount expired? + */ + public function is_expired( $update = true ) { + $return = false; + + if ( empty( $this->end_date ) || '0000-00-00 00:00:00' === $this->end_date ) { + return $return; + } + + $end_date = strtotime( $this->end_date ); + + if ( $end_date < time() ) { + if ( $update ) { + $this->update_status( 'expired' ); + } + $return = true; + } + + /** + * Filters if the discount has expired or not. + * + * @since 2.7 + * + * @param bool $return Has the discount expired or not. + * @param int $ID Discount ID. + */ + return apply_filters( 'edd_is_discount_expired', $return, $this->id ); + } + + /** + * Check if the discount has maxed out. + * + * @since 2.7 + * + * @param bool $set_error Whether an error message be set in session. + * @return bool Is discount maxed out? + */ + public function is_maxed_out( $set_error = true ) { + $return = false; + + if ( $this->uses >= $this->max_uses && ! empty( $this->max_uses ) ) { + if ( $set_error ) { + edd_set_error( 'edd-discount-error', __( 'This discount has reached its maximum usage.', 'easy-digital-downloads' ) ); + } + + $return = true; + } + + /** + * Filters if the discount is maxed out or not. + * + * @since 2.7 + * + * @param bool $return Is the discount maxed out or not. + * @param int $ID Discount ID. + * @param bool $set_error Whether an error message be set in session. + */ + return apply_filters( 'edd_is_discount_maxed_out', $return, $this->id, $set_error ); + } + + /** + * Check if the minimum cart amount is satisfied for the discount to hold. + * + * @since 2.7 + * + * @param bool $set_error Whether an error message be set in session. + * @return bool Is the minimum cart amount met? + */ + public function is_min_price_met( $set_error = true ) { + $return = false; + + $cart_amount = edd_get_cart_discountable_subtotal( $this->id ); + + if ( (float) $cart_amount >= (float) $this->min_charge_amount ) { + $return = true; + } elseif ( $set_error ) { + edd_set_error( 'edd-discount-error', sprintf( __( 'Minimum order of %s not met.', 'easy-digital-downloads' ), edd_currency_filter( edd_format_amount( $this->min_charge_amount ) ) ) ); + } + + /** + * Filters if the minimum cart amount has been met to satisfy the discount. + * + * @since 2.7 + * + * @param bool $return Is the minimum cart amount met or not. + * @param int $ID Discount ID. + * @param bool $set_error Whether an error message be set in session. + */ + return apply_filters( 'edd_is_discount_min_met', $return, $this->id, $set_error ); + } + + /** + * Is the discount single use or not? + * + * @since 2.7 + * + * @return bool Is the discount single use or not? + */ + public function is_single_use() { + /** + * Filters if the discount is single use or not. + * + * @since 2.7 + * + * @param bool $single_use Is the discount is single use or not. + * @param int $ID Discount ID. + */ + return (bool) apply_filters( 'edd_is_discount_single_use', $this->once_per_customer, $this->id ); + } + + /** + * Are the product requirements met for the discount to hold. + * + * @since 2.7 + * + * @param bool $set_error Whether an error message be set in session. + * @return bool Are required products in the cart? + */ + public function is_product_requirements_met( $set_error = true ) { + $product_reqs = $this->get_product_reqs(); + $excluded_ps = $this->get_excluded_products(); + $cart_items = edd_get_cart_contents(); + $cart_ids = $cart_items ? wp_list_pluck( $cart_items, 'id' ) : null; + $is_met = true; + + /** + * Normalize our data for product requirements, exclusions and cart data. + */ + + // First absint the items, then sort, and reset the array keys + $product_reqs = array_map( 'absint', $product_reqs ); + asort( $product_reqs ); + $product_reqs = array_filter( array_values( $product_reqs ) ); + + $cart_ids = array_map( 'absint', $cart_ids ); + asort( $cart_ids ); + $cart_ids = array_values( $cart_ids ); + + // Ensure we have requirements before proceeding + if ( ! empty( $product_reqs ) ) { + $matches = array_intersect( $product_reqs, $cart_ids ); + + switch ( $this->get_product_condition() ) { + case 'all': + $is_met = count( $matches ) === count( $product_reqs ); + break; + default: + $is_met = 0 < count( $matches ); + } + + if ( ! $is_met && $set_error ) { + edd_set_error( 'edd-discount-error', __( 'The product requirements for this discount are not met.', 'easy-digital-downloads' ) ); + } + } + + $excluded_ps = array_map( 'absint', $excluded_ps ); + asort( $excluded_ps ); + $excluded_ps = array_filter( array_values( $excluded_ps ) ); + + if ( ! empty( $excluded_ps ) ) { + if ( count( array_intersect( $cart_ids, $excluded_ps ) ) === count( $cart_ids ) ) { + $is_met = false; + + if ( $set_error ) { + edd_set_error( 'edd-discount-error', __( 'This discount is not valid for the cart contents.', 'easy-digital-downloads' ) ); + } + } + } + + /** + * Filters whether the product requirements are met for the discount to hold. + * + * @since 2.7 + * + * @param bool $is_met Are the product requirements met or not. + * @param int $ID Discount ID. + * @param string $product_condition Product condition. + * @param bool $set_error Whether an error message be set in session. + */ + return (bool) apply_filters( 'edd_is_discount_products_req_met', $is_met, $this->id, $this->product_condition, $set_error ); + } + + /** + * Has the discount code been used. + * + * @since 2.7 + * @since 3.0 Refactored to use new query methods. + * + * @param string $user User info. + * @param bool $set_error Whether an error message be set in session. + * + * @return bool Whether the discount has been used or not. + */ + public function is_used( $user = '', $set_error = true ) { + $return = false; + + if ( $this->is_single_use ) { + $payments = array(); + + if ( edd_get_component_interface( 'customer', 'table' )->exists() ) { + $by_user_id = ! is_email( $user ); + + $customer = new EDD_Customer( $user, $by_user_id ); + + $payments = explode( ',', $customer->payment_ids ); + } else { + $user_found = false; + + if ( is_email( $user ) ) { + $user_found = true; // All we need is the email + $key = '_edd_payment_user_email'; + $value = $user; + } else { + $user_data = get_user_by( 'login', $user ); + + if ( $user_data ) { + $user_found = true; + $key = '_edd_payment_user_id'; + $value = $user_data->ID; + } + } + + if ( $user_found ) { + $query_args = array( + 'post_type' => 'edd_payment', + 'meta_query' => array( + array( + 'key' => $key, + 'value' => $value, + 'compare' => '=', + ), + ), + 'fields' => 'ids', + ); + + $payments = get_posts( $query_args ); // Get all payments with matching email + } + } + + if ( $payments ) { + foreach ( $payments as $payment ) { + $payment = new EDD_Payment( $payment ); + + if ( empty( $payment->discounts ) ) { + continue; + } + + if ( in_array( $payment->status, edd_get_incomplete_order_statuses(), true ) ) { + continue; + } + + $discounts = explode( ',', $payment->discounts ); + + if ( is_array( $discounts ) ) { + $discounts = array_map( 'strtoupper', $discounts ); + $key = array_search( strtoupper( $this->code ), $discounts, true ); + + if ( false !== $key ) { + if ( $set_error ) { + edd_set_error( 'edd-discount-error', __( 'This discount has already been redeemed.', 'easy-digital-downloads' ) ); + } + + $return = true; + break; + } + } + } + } + } + + /** + * Filters if the discount is used or not. + * + * @since 2.7 + * + * @param bool $return If the discount is used or not. + * @param int $ID Discount ID. + * @param string $user User info. + * @param bool $set_error Whether an error message be set in session. + */ + return apply_filters( 'edd_is_discount_used', $return, $this->id, $user, $set_error ); + } + + /** + * Checks whether a discount holds at the time of purchase. + * + * @since 2.7 + * + * @param string $user User info. + * @param bool $set_error Whether an error message be set in session. + * @return bool Is the discount valid or not? + */ + public function is_valid( $user = '', $set_error = true ) { + $return = false; + $user = trim( $user ); + + if ( edd_get_cart_contents() && $this->id ) { + if ( + $this->is_active( true, $set_error ) && + $this->is_started( $set_error ) && + ! $this->is_maxed_out( $set_error ) && + ! $this->is_used( $user, $set_error ) && + $this->is_product_requirements_met( $set_error ) && + $this->is_min_price_met( $set_error ) + ) { + $return = true; + } + } elseif ( $set_error ) { + edd_set_error( 'edd-discount-error', _x( 'This discount is invalid.', 'error for when a discount is invalid based on its configuration', 'easy-digital-downloads' ) ); + } + + /** + * Filters whether the discount is valid or not. + * + * @since 2.7 + * + * @param bool $return If the discount is used or not. + * @param int $ID Discount ID. + * @param string $code Discount code. + * @param string $user User info. + * @param bool $set_error Whether an error message be set in session. + */ + return apply_filters( 'edd_is_discount_valid', $return, $this->id, $this->code, $user, $set_error ); + } + + /** + * Checks if a discount code is active. + * + * @since 2.7 + * + * @param bool $update Update the discount to expired if an one is found but has an active status. + * @param bool $set_error Whether an error message be set in session. + * @return bool If the discount is active or not. + */ + public function is_active( $update = true, $set_error = true ) { + $return = false; + + if ( $this->exists() ) { + + if ( $this->is_expired( $update ) ) { + if ( edd_doing_ajax() && $set_error ) { + edd_set_error( 'edd-discount-error', __( 'This discount is expired.', 'easy-digital-downloads' ) ); + } + } elseif ( 'active' === $this->status ) { + $return = true; + } elseif ( edd_doing_ajax() && $set_error ) { + edd_set_error( 'edd-discount-error', __( 'This discount is not active.', 'easy-digital-downloads' ) ); + } + } + + /** + * Filters if the discount is active or not. + * + * @since 2.7 + * + * @param bool $return Is the discount active or not. + * @param int $ID Discount ID. + * @param bool $set_error Whether an error message be set in session. + */ + return apply_filters( 'edd_is_discount_active', $return, $this->id, $set_error ); + } + + /** + * Get Discounted Amount. + * + * @since 2.7 + * + * @param string|int $base_price Price before discount. + * @return float $discounted_price Amount after discount. + */ + public function get_discounted_amount( $base_price ) { + $base_price = floatval( $base_price ); + + if ( 'flat' === $this->amount_type ) { + $amount = $base_price - floatval( $this->amount ); + + if ( $amount < 0 ) { + $amount = 0; + } + } else { + // Percentage discount + $amount = $base_price - ( $base_price * ( floatval( $this->amount ) / 100 ) ); + } + + /** + * Filter the discounted amount calculated. + * + * @since 2.7 + * @access public + * + * @param float $amount Calculated discounted amount. + * @param EDD_Discount $this Discount object. + */ + return apply_filters( 'edd_discounted_amount', $amount, $this ); + } + + /** + * Increment the usage of the discount. + * + * @since 2.7 + * + * @return int New discount usage. + */ + public function increase_usage() { + if ( $this->get_uses() ) { + $this->use_count++; + } else { + $this->use_count = 1; + } + + $args = array( 'use_count' => $this->use_count ); + + $this->max_uses = absint( $this->max_uses ); + + if ( 0 !== $this->max_uses && $this->max_uses <= $this->use_count ) { + $args['status'] = 'inactive'; + } + + $this->update( $args ); + + /** + * Fires after the usage count has been increased. + * + * @since 2.7 + * + * @param int $use_count Discount usage. + * @param int $ID Discount ID. + * @param string $code Discount code. + */ + do_action( 'edd_discount_increase_use_count', $this->use_count, $this->id, $this->code ); + + return (int) $this->use_count; + } + + /** + * Decrement the usage of the discount. + * + * @since 2.7 + * + * @return int New discount usage. + */ + public function decrease_usage() { + if ( $this->get_uses() ) { + $this->use_count--; + } + + if ( $this->use_count < 0 ) { + $this->use_count = 0; + } + + $args = array( 'use_count' => $this->use_count ); + + if ( 0 !== $this->max_uses && $this->max_uses > $this->use_count ) { + $args['status'] = 'active'; + } + + $this->update( $args ); + + /** + * Fires after the usage count has been decreased. + * + * @since 2.7 + * + * @param int $use_count Discount usage. + * @param int $ID Discount ID. + * @param string $code Discount code. + */ + do_action( 'edd_discount_decrease_use_count', $this->use_count, $this->id, $this->code ); + + return (int) $this->use_count; + } + + /** + * Edit Discount Link. + * + * @since 2.7 + * + * @return string Link to the `Edit Discount` page. + */ + public function edit_url() { + return esc_url( + edd_get_admin_url( + array( + 'page' => 'edd-discounts', + 'edd-action' => 'edit_discount', + 'discount' => absint( $this->id ), + ) + ) + ); + } + + /** + * Sanitize the data for update/create + * + * @since 3.0 + * @param array $data The data to sanitize + * @return array The sanitized data, based off column defaults + */ + private function sanitize_columns( $data ) { + $default_values = array(); + + foreach ( $data as $key => $type ) { + + // Only sanitize data that we were provided + if ( ! array_key_exists( $key, $data ) ) { + continue; + } + + switch ( $type ) { + + case '%s': + if ( 'email' === $key ) { + $data[ $key ] = sanitize_email( $data[ $key ] ); + } elseif ( 'notes' === $key ) { + $data[ $key ] = strip_tags( $data[ $key ] ); + } else { + if ( is_array( $data[ $key ] ) ) { + $data[ $key ] = json_encode( $data[ $key ] ); + } else { + $data[ $key ] = sanitize_text_field( $data[ $key ] ); + } + } + break; + + case '%d': + if ( ! is_numeric( $data[ $key ] ) || absint( $data[ $key ] ) !== (int) $data[ $key ] ) { + $data[ $key ] = $default_values[ $key ]; + } else { + $data[ $key ] = absint( $data[ $key ] ); + } + break; + + case '%f': + // Convert what was given to a float + $value = floatval( $data[ $key ] ); + + if ( ! is_float( $value ) ) { + $data[ $key ] = $default_values[ $key ]; + } else { + $data[ $key ] = $value; + } + break; + + default: + $data[ $key ] = ! is_array( $data[ $key ] ) + ? sanitize_text_field( $data[ $key ] ) + : maybe_serialize( array_map( 'sanitize_text_field', $data[ $key ] ) ); + break; + } + } + + return $data; + } + + /** + * Converts pre-3.0 arguments to the 3.0+ version. + * + * @since 3.0 + * @static + * + * @param $args array Arguments to be converted. + * @return array The converted arguments. + */ + public static function convert_legacy_args( $args = array() ) { + + // Loop through arguments provided and adjust old key names for the new schema introduced in 3.0 + $old = array( + 'uses' => 'use_count', + 'max' => 'max_uses', + 'start' => 'start_date', + 'expiration' => 'end_date', + 'min_price' => 'min_charge_amount', + 'products' => 'product_reqs', + 'excluded-products' => 'excluded_products', + 'not_global' => 'scope', + 'is_not_global' => 'scope', + 'use_once' => 'once_per_customer', + 'is_single_use' => 'once_per_customer', + ); + + foreach ( $old as $old_key => $new_key ) { + if ( isset( $args[ $old_key ] ) ) { + if ( in_array( $old_key, array( 'not_global', 'is_not_global' ), true ) && ! array_key_exists( 'scope', $args ) ) { + $args[ $new_key ] = ! empty( $args[ $old_key ] ) + ? 'not_global' + : 'global'; + } else { + $args[ $new_key ] = $args[ $old_key ]; + } + } + unset( $args[ $old_key ] ); + } + + // Default status needs to be active for regression purposes. + // See https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6806 + if ( ! isset( $args['status'] ) ) { + $args['status'] = 'active'; + } + + return $args; + } +} diff --git a/includes/class-edd-download.php b/includes/class-edd-download.php new file mode 100644 index 00000000000..8b45eb15696 --- /dev/null +++ b/includes/class-edd-download.php @@ -0,0 +1,923 @@ +setup_download( $download ); + } + + /** + * Given the download data, let's set the variables + * + * @since 2.3.6 + * @param WP_Post $download The WP_Post object for download. + * @return bool If the setup was successful or not + */ + private function setup_download( $download ) { + + if ( ! is_object( $download ) ) { + return false; + } + + if ( ! $download instanceof WP_Post ) { + return false; + } + + if ( 'download' !== $download->post_type ) { + return false; + } + + foreach ( $download as $key => $value ) { + $this->{$key} = $value; + } + + return true; + } + + /** + * Magic __get function to dispatch a call to retrieve a private property + * + * @since 2.2 + */ + public function __get( $key = '' ) { + if ( method_exists( $this, "get_{$key}" ) ) { + return call_user_func( array( $this, "get_{$key}" ) ); + } else { + return new WP_Error( 'edd-download-invalid-property', sprintf( __( 'Can\'t get property %s', 'easy-digital-downloads' ), $key ) ); + } + } + + /** + * Creates a download + * + * @since 2.3.6 + * @param array $data Array of attributes for a download + * @return mixed false if data isn't passed and class not instantiated for creation, or New Download ID + */ + public function create( $data = array() ) { + + if ( $this->id != 0 ) { + return false; + } + + $defaults = array( + 'post_type' => 'download', + 'post_status' => 'draft', + 'post_title' => __( 'New Download Product', 'easy-digital-downloads' ) + ); + + $args = wp_parse_args( $data, $defaults ); + + /** + * Fired before a download is created + * + * @param array $args The post object arguments used for creation. + */ + do_action( 'edd_download_pre_create', $args ); + + $id = wp_insert_post( $args, true ); + + $download = WP_Post::get_instance( $id ); + + /** + * Fired after a download is created + * + * @param int $id The post ID of the created item. + * @param array $args The post object arguments used for creation. + */ + do_action( 'edd_download_post_create', $id, $args ); + + return $this->setup_download( $download ); + } + + /** + * Retrieve the ID + * + * @since 2.2 + * @return int ID of the download + */ + public function get_ID() { + return $this->ID; + } + + /** + * Retrieve the download name + * + * @since 2.5.8 + * @return string Name of the download + */ + public function get_name() { + return get_the_title( $this->ID ); + } + + /** + * Retrieve the price + * + * @since 2.2 + * @return float Price of the download + */ + public function get_price() { + + if ( ! isset( $this->price ) ) { + $this->price = get_post_meta( $this->ID, 'edd_price', true ); + + if ( $this->price ) { + $this->price = edd_sanitize_amount( $this->price ); + } else { + $this->price = 0; + } + } + + /** + * Override the download price. + * + * @since 2.2 + * + * @param string $price The download price(s). + * @param string|int $id The downloads ID. + */ + return apply_filters( 'edd_get_download_price', $this->price, $this->ID ); + } + + /** + * Retrieve the variable prices + * + * @since 2.2 + * @return array List of the variable prices + */ + public function get_prices() { + + $this->prices = array(); + + if ( true === $this->has_variable_prices() ) { + if ( empty( $this->prices ) ) { + $this->prices = get_post_meta( $this->ID, 'edd_variable_prices', true ); + } + } + + /** + * Override variable prices + * + * @since 2.2 + * + * @param array $prices The array of variables prices. + * @param int|string The ID of the download. + */ + return array_filter( (array) apply_filters( 'edd_get_variable_prices', $this->prices, $this->ID ) ); + } + + /** + * Get the default Price ID for variable priced products. + * + * Since it is possible for the value to not be set on older products, we'll set it to the first price in the array + * if one is not set, as that has been the default behavior since default prices were introduced. + * + * Storing it as the first if found, is just more consistent and intentional. + * + * @since 3.1.2 + * + * @return int|null The default price ID, or null if the product does not have variable prices. + */ + public function get_default_price_id() { + if ( ! $this->has_variable_prices() ) { + return null; + } + + $default_price_id = get_post_meta( $this->ID, '_edd_default_price_id', true ); + + // If no default price ID is set, or the default price ID is not in the prices array, set the first price as the default. + $prices = $this->get_prices(); + if ( is_array( $prices ) && ( ! is_numeric( $default_price_id ) || ! array_key_exists( (int) $default_price_id, $prices ) ) ) { + $default_price_id = key( $prices ); + + // Set the default price ID + update_post_meta( $this->ID, '_edd_default_price_id', $default_price_id ); + } + + return absint( apply_filters( 'edd_variable_default_price_id', $default_price_id, $this->ID ) ); + } + + /** + * Determine if single price mode is enabled or disabled + * + * @since 2.2 + * @return bool True if download is in single price mode, false otherwise + */ + public function is_single_price_mode() { + $ret = $this->has_variable_prices() && get_post_meta( $this->ID, '_edd_price_options_mode', true ); + + /** + * Override the price mode for a download when checking if is in single price mode. + * + * @since 2.3 + * + * @param bool $ret Is download in single price mode? + * @param int|string The ID of the download. + */ + return (bool) apply_filters( 'edd_single_price_option_mode', $ret, $this->ID ); + } + + /** + * Determine if the download has variable prices enabled + * + * @since 2.2 + * @return bool True when the download has variable pricing enabled, false otherwise + */ + public function has_variable_prices() { + $ret = get_post_meta( $this->ID, '_variable_pricing', true ); + + /** + * Override whether the download has variables prices. + * + * @since 2.3 + * + * @param bool $ret Does download have variable prices? + * @param int|string The ID of the download. + */ + return (bool) apply_filters( 'edd_has_variable_prices', $ret, $this->ID ); + } + + /** + * Retrieve the file downloads + * + * @since 2.2 + * @param integer $variable_price_id + * @return array List of download files + */ + public function get_files( $variable_price_id = null ) { + if ( ! isset( $this->files ) ) { + + $this->files = array(); + + // Bundled products are not allowed to have files + if ( $this->is_bundled_download() ) { + return $this->files; + } + + $download_files = get_post_meta( $this->ID, 'edd_download_files', true ); + + if ( ! empty( $download_files ) ) { + if ( ! is_null( $variable_price_id ) && $this->has_variable_prices() ) { + foreach ( $download_files as $key => $file_info ) { + if ( isset( $file_info['condition'] ) ) { + if ( $file_info['condition'] == $variable_price_id || 'all' === $file_info['condition'] ) { + $this->files[ $key ] = $file_info; + } + } + } + + } else { + $this->files = $download_files; + } + } + } + + return apply_filters( 'edd_download_files', $this->files, $this->ID, $variable_price_id ); + } + + /** + * Retrieve the file download limit + * + * @since 2.2 + * @return int Number of download limit + */ + public function get_file_download_limit() { + + if ( ! isset( $this->file_download_limit ) ) { + $limit = get_post_meta( $this->ID, '_edd_download_limit', true ); + $global = edd_get_option( 'file_download_limit', 0 ); + + // Download specific limit + if ( is_numeric( $limit ) ) { + $retval = absint( $limit ); + + // Use global + } elseif ( '' === $limit ) { + $retval = ''; + + // Global limit + } elseif ( ! empty( $global ) ) { + $retval = absint( $global ); + + // Default + } else { + $retval = 0; + } + + $this->file_download_limit = $retval; + } + + return apply_filters( 'edd_file_download_limit', $this->file_download_limit, $this->ID ); + } + + /** + * Retrieve the refund window + * + * @since 3.0 + * @return int Number of days + */ + public function get_refund_window() { + + if ( ! isset( $this->refund_window ) ) { + $window = get_post_meta( $this->ID, '_edd_refund_window', true ); + $global = edd_get_option( 'refund_window', 0 ); // needs to be 0 here + + // Download specific window + if ( is_numeric( $window ) ) { + $retval = absint( $window ); + + // Use global + } elseif ( '' === $window ) { + $retval = ''; + + // Global limit + } elseif ( ! empty( $global ) ) { + $retval = absint( $global ); + + // Default + } else { + $retval = 0; + } + + $this->refund_window = $retval; + } + + return $this->refund_window; // No filter + } + + /** + * Retrieve whether the product is refundable. + * + * @since 3.0 + * + * @return string `refundable` or `nonrefundable` + */ + public function get_refundability() { + + if ( ! isset( $this->refundability ) ) { + $default = 'refundable'; + $refundable = get_post_meta( $this->ID, '_edd_refundability', true ); + $global = edd_get_option( 'refundability', $default ); + + // Download specific window + if ( ! empty( $refundable ) ) { + $retval = $refundable; + + // Use global + } elseif ( ! empty( $global ) ) { + $retval = $global; + + // Default + } else { + $retval = $default; + } + + $this->refundability = $retval; + } + + return $this->refundability; // No filter + } + + /** + * Retrieve the price option that has access to the specified file + * + * @since 2.2 + * @return int|string + */ + public function get_file_price_condition( $file_key = 0 ) { + $files = $this->get_files(); + $condition = isset( $files[ $file_key ]['condition'] ) + ? $files[ $file_key ]['condition'] + : 'all'; + + return apply_filters( 'edd_get_file_price_condition', $condition, $this->ID, $files ); + } + + /** + * Retrieve the download type, default or bundle + * + * @since 2.2 + * @return string Type of download, either 'default' or 'bundle' + */ + public function get_type() { + if ( ! isset( $this->type ) ) { + $this->type = get_post_meta( $this->ID, '_edd_product_type', true ); + + if ( empty( $this->type ) ) { + $this->type = 'default'; + } + } + + return apply_filters( 'edd_get_download_type', $this->type, $this->ID ); + } + + /** + * Determine if this is a bundled download + * + * @since 2.2 + * @return bool True when download is a bundle, false otherwise + */ + public function is_bundled_download() { + return 'bundle' === $this->get_type(); + } + + /** + * Retrieves the Download IDs that are bundled with this Download + * + * @since 2.2 + * @return array List of bundled downloads + */ + public function get_bundled_downloads() { + + if ( ! isset( $this->bundled_downloads ) ) { + $this->bundled_downloads = (array) get_post_meta( $this->ID, '_edd_bundled_products', true ); + } + + return (array) apply_filters( 'edd_get_bundled_products', array_filter( $this->bundled_downloads ), $this->ID ); + } + + /** + * Retrieve the Download IDs that are bundled with this Download based on the variable pricing ID passed + * + * @since 2.7 + * @param int $price_id Variable pricing ID + * @return array List of bundled downloads + */ + public function get_variable_priced_bundled_downloads( $price_id = null ) { + if ( null === $price_id ) { + return $this->get_bundled_downloads(); + } + + $downloads = array(); + $bundled_downloads = $this->get_bundled_downloads(); + $price_assignments = $this->get_bundle_pricing_variations(); + + if ( ! $price_assignments ) { + return $bundled_downloads; + } + + $price_assignments = $price_assignments[0]; + + foreach ( $price_assignments as $key => $value ) { + if ( isset( $bundled_downloads[ $key ] ) && ( $value == $price_id || $value == 'all' ) ) { + $downloads[] = $bundled_downloads[ $key ]; + } + } + + return $downloads; + } + + /** + * Retrieve the download notes + * + * @since 2.2 + * @return string Note related to the download + */ + public function get_notes() { + + if ( ! isset( $this->notes ) ) { + $this->notes = get_post_meta( $this->ID, 'edd_product_notes', true ); + } + + return (string) apply_filters( 'edd_product_notes', $this->notes, $this->ID ); + } + + /** + * Retrieve the download sku + * + * @since 2.2 + * @return string SKU of the download + */ + public function get_sku() { + + if ( ! isset( $this->sku ) ) { + + $this->sku = get_post_meta( $this->ID, 'edd_sku', true ); + + if ( empty( $this->sku ) ) { + $this->sku = '-'; + } + } + + return apply_filters( 'edd_get_download_sku', $this->sku, $this->ID ); + } + + /** + * Retrieve the purchase button behavior + * + * @since 2.2 + * @return string + */ + public function get_button_behavior() { + + if ( ! isset( $this->button_behavior ) ) { + + $this->button_behavior = get_post_meta( $this->ID, '_edd_button_behavior', true ); + + if ( empty( $this->button_behavior ) || ! edd_shop_supports_buy_now() ) { + $this->button_behavior = 'add_to_cart'; + } + } + + return apply_filters( 'edd_get_download_button_behavior', $this->button_behavior, $this->ID ); + } + + /** + * Retrieve the sale count for the download + * + * @since 2.2 + * @return int Number of times this has been purchased + */ + public function get_sales() { + + if ( ! isset( $this->sales ) ) { + + if ( '' == get_post_meta( $this->ID, '_edd_download_sales', true ) ) { + add_post_meta( $this->ID, '_edd_download_sales', 0 ); + } + + $this->sales = get_post_meta( $this->ID, '_edd_download_sales', true ); + + // Never let sales be less than zero + $this->sales = max( $this->sales, 0 ); + } + + return $this->sales; + } + + /** + * Increment the sale count by one + * + * @since 2.2 + * @param int $quantity The quantity to increase the sales by + * @return int New number of total sales + */ + public function increase_sales( $quantity = 1 ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Download::recalculate_net_sales_earnings()' ); + edd_recalculate_download_sales_earnings( $this->ID ); + + $this->get_sales(); + do_action( 'edd_download_increase_sales', $this->ID, $this->sales, $this ); + + return $this->sales; + } + + /** + * Decrement the sale count by one + * + * @since 2.2 + * @param int $quantity The quantity to decrease by + * @return int New number of total sales + */ + public function decrease_sales( $quantity = 1 ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Download::recalculate_net_sales_earnings()' ); + $this->recalculate_net_sales_earnings(); + + $this->get_sales(); + do_action( 'edd_download_decrease_sales', $this->ID, $this->sales, $this ); + + return $this->sales; + } + + /** + * Retrieve the total earnings for the download + * + * @since 2.2 + * @return float Total download earnings + */ + public function get_earnings() { + + if ( ! isset( $this->earnings ) ) { + if ( '' == get_post_meta( $this->ID, '_edd_download_earnings', true ) ) { + add_post_meta( $this->ID, '_edd_download_earnings', 0 ); + } + + $this->earnings = get_post_meta( $this->ID, '_edd_download_earnings', true ); + + // Never let earnings be less than zero + $this->earnings = max( $this->earnings, 0 ); + } + + return $this->earnings; + } + + /** + * Increase the earnings by the given amount + * + * @since 2.2 + * @param int|float $amount Amount to increase the earnings by + * @return float New number of total earnings + */ + public function increase_earnings( $amount = 0 ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'edd_recalculate_download_sales_earnings()' ); + edd_recalculate_download_sales_earnings( $this->ID ); + + $this->get_earnings(); + do_action( 'edd_download_increase_earnings', $this->ID, $this->earnings, $this ); + + return $this->earnings; + } + + /** + * Decrease the earnings by the given amount + * + * @since 2.2 + * @param int|float $amount Number to decrease earning with + * @return float New number of total earnings + */ + public function decrease_earnings( $amount ) { + + _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Download::recalculate_net_sales_earnings()' ); + + $this->recalculate_net_sales_earnings(); + $this->get_earnings(); + + do_action( 'edd_download_decrease_earnings', $this->ID, $this->earnings, $this ); + + return $this->earnings; + } + + /** + * Updates the gross sales and earnings for a download. + * + * @since 3.0 + * @return void + */ + public function recalculate_gross_sales_earnings() { + $download_model = new Download( $this->ID ); + + // This currently uses the post meta functions as we do not yet guarantee that the meta exists. + update_post_meta( $this->ID, '_edd_download_gross_sales', $download_model->get_gross_sales() ); + update_post_meta( $this->ID, '_edd_download_gross_earnings', floatval( $download_model->get_gross_earnings() ) ); + } + + /** + * Recalculates the net sales and earnings for a download. + * + * @since 3.0 + * @return void + */ + public function recalculate_net_sales_earnings() { + $download_model = new Download( $this->ID ); + + $this->update_meta( '_edd_download_sales', intval( $download_model->get_net_sales() ) ); + $this->update_meta( '_edd_download_earnings', floatval( $download_model->get_net_earnings() ) ); + } + + /** + * Determine if the download is free or if the given price ID is free + * + * @since 2.2 + * @param bool $price_id ID of variation if needed + * @return bool True when the download is free, false otherwise + */ + public function is_free( $price_id = false ) { + + $is_free = false; + $variable_pricing = edd_has_variable_prices( $this->ID ); + + if ( $variable_pricing && ! is_null( $price_id ) && $price_id !== false ) { + $price = edd_get_price_option_amount( $this->ID, $price_id ); + + } elseif ( $variable_pricing && $price_id === false ) { + $lowest_price = (float) edd_get_lowest_price_option( $this->ID ); + $highest_price = (float) edd_get_highest_price_option( $this->ID ); + + if ( $lowest_price === 0.00 && $highest_price === 0.00 ) { + $price = 0; + } + + } elseif ( ! $variable_pricing ) { + + $price = get_post_meta( $this->ID, 'edd_price', true ); + } + + if ( isset( $price ) && (float) $price == 0 ) { + $is_free = true; + } + + return (bool) apply_filters( 'edd_is_free_download', $is_free, $this->ID, $price_id ); + } + + /** + * Is quantity input disabled on this product? + * + * @since 2.7 + * @return bool + */ + public function quantities_disabled() { + $ret = (bool) get_post_meta( $this->ID, '_edd_quantities_disabled', true ); + return apply_filters( 'edd_download_quantity_disabled', $ret, $this->ID ); + } + + /** + * Updates a single meta entry for the download + * + * @since 2.3 + * @access private + * @param string $meta_key The meta_key to update + * @param string|array|object $meta_value The value to put into the meta + * @return bool The result of the update query + */ + private function update_meta( $meta_key = '', $meta_value = '' ) { + global $wpdb; + + if ( empty( $meta_key ) || ( ! is_numeric( $meta_value ) && empty( $meta_value ) ) ) { + return false; + } + + // Make sure if it needs to be serialized, we do + $meta_value = maybe_serialize( $meta_value ); + + if ( is_numeric( $meta_value ) ) { + $value_type = is_float( $meta_value ) ? '%f' : '%d'; + } else { + $value_type = "'%s'"; + } + + $sql = $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = $value_type WHERE post_id = $this->ID AND meta_key = '%s'", $meta_value, $meta_key ); + + if ( $wpdb->query( $sql ) ) { + + clean_post_cache( $this->ID ); + return true; + + } + + return false; + } + + /** + * Checks if the download can be purchased + * + * NOTE: Currently only checks on edd_get_cart_contents() and edd_add_to_cart() + * + * @since 2.6.4 + * @return bool If the current user can purchase the download ID + */ + public function can_purchase() { + $can_purchase = true; + + if ( 'publish' !== $this->post_status && ! current_user_can( 'edit_post', $this->ID ) ) { + $can_purchase = false; + } + + return (bool) apply_filters( 'edd_can_purchase_download', $can_purchase, $this ); + } + + /** + * Get pricing variations for bundled items + * + * @since 2.7 + * @return array + */ + public function get_bundle_pricing_variations() { + return get_post_meta( $this->ID, '_edd_bundled_products_conditions' ); + } +} diff --git a/includes/class-edd-fees.php b/includes/class-edd-fees.php new file mode 100755 index 00000000000..b89479f1fe3 --- /dev/null +++ b/includes/class-edd-fees.php @@ -0,0 +1,362 @@ + 1 ) { + + $args = func_get_args(); + $amount = $args[0]; + $label = isset( $args[1] ) ? $args[1] : ''; + $id = isset( $args[2] ) ? $args[2] : ''; + $type = 'fee'; + + $args = array( + 'amount' => $amount, + 'label' => $label, + 'id' => $id, + 'type' => $type, + 'no_tax' => false, + 'download_id' => 0, + 'price_id' => NULL + ); + + } else { + + $defaults = array( + 'amount' => 0, + 'label' => '', + 'id' => '', + 'no_tax' => false, + 'type' => 'fee', + 'download_id' => 0, + 'price_id' => NULL + ); + + $args = wp_parse_args( $args, $defaults ); + + if( $args['type'] != 'fee' && $args['type'] != 'item' ) { + $args['type'] = 'fee'; + } + + } + + // If the fee is for an "item" but we passed in a download id + if( 'item' === $args['type'] && ! empty( $args['download_id'] ) ) { + unset( $args['download_id'] ); + unset( $args['price_id'] ); + } + + if ( ! empty( $args['download_id'] ) ) { + $options = isset( $args['price_id'] ) ? array( 'price_id' => $args['price_id'] ) : array(); + if ( ! edd_item_in_cart( $args['download_id'], $options ) ) { + return false; + } + } + + $fees = $this->get_fees( 'all' ); + + // Determine the key + $key = empty( $args['id'] ) ? sanitize_key( $args['label'] ) : sanitize_key( $args['id'] ); + + // Remove the unneeded id key + unset( $args['id'] ); + + // Sanitize the amount + $args['amount'] = edd_sanitize_amount( $args['amount'] ); + + // Force the amount to have the proper number of decimal places. + $args['amount'] = number_format( (float) $args['amount'], edd_currency_decimal_filter(), '.', '' ); + + // Force no_tax to true if the amount is negative + if( $args['amount'] < 0 ) { + $args['no_tax'] = true; + } + + // Set the fee + $fees[ $key ] = apply_filters( 'edd_fees_add_fee', $args, $this ); + + // Allow 3rd parties to process the fees before storing them in the session + $fees = apply_filters( 'edd_fees_set_fees', $fees, $this ); + + // Update fees + EDD()->session->set( 'edd_cart_fees', $fees ); + + do_action( 'edd_post_add_fee', $fees, $key, $args ); + + return $fees; + } + + /** + * Remove an Existing Fee + * + * @since 1.5 + * @param string $id Fee ID + * @uses EDD_Fees::get_fees() + * @uses EDD_Session::set() + * @return array Remaining fees + */ + public function remove_fee( $id = '' ) { + + $fees = $this->get_fees( 'all' ); + + if ( isset( $fees[ $id ] ) ) { + unset( $fees[ $id ] ); + EDD()->session->set( 'edd_cart_fees', $fees ); + + do_action( 'edd_post_remove_fee', $fees, $id ); + } + + return $fees; + } + + /** + * Check if any fees are present + * + * @since 1.5 + * @param string $type Fee type, "fee" or "item" + * @uses EDD_Fees::get_fees() + * @return bool True if there are fees, false otherwise + */ + public function has_fees( $type = 'fee' ) { + + if( 'all' == $type || 'fee' == $type ) { + if( ! edd_get_cart_contents() ) { + $type = 'item'; + } + + } + + $fees = $this->get_fees( $type ); + return ! empty( $fees ) && is_array( $fees ); + } + + /** + * Retrieve all active fees + * + * @since 1.5 + * @param string $type Fee type, "fee" or "item" + * @param int $download_id The download ID whose fees to retrieve + * @param null|int $price_id The variable price ID whose fees to retrieve + * @uses EDD_Session::get() + * @return array|bool List of fees when available, false when there are no fees + */ + public function get_fees( $type = 'fee', $download_id = 0, $price_id = null ) { + $fees = EDD()->session->get( 'edd_cart_fees' ); + + if ( EDD()->cart->is_empty() ) { + // We can only get item type fees when the cart is empty + $type = 'item'; + } + + if ( ! empty( $fees ) && ! empty( $type ) && 'all' !== $type ) { + foreach ( $fees as $key => $fee ) { + if ( ! empty( $fee['type'] ) && $type != $fee['type'] ) { + unset( $fees[ $key ] ); + } + } + } + + if ( ! empty( $fees ) && ! empty( $download_id ) ) { + // Remove fees that don't belong to the specified Download + $applied_fees = array(); + foreach ( $fees as $key => $fee ) { + + if ( empty( $fee['download_id'] ) || (int) $download_id !== (int) $fee['download_id'] ) { + unset( $fees[ $key ] ); + } + + $string_to_hash = "{$key}_{$download_id}"; + if ( ! is_null( $price_id ) && isset( $fee['price_id'] ) ) { + $string_to_hash .= "_{$fee['price_id']}"; + } + $fee_hash = md5( $string_to_hash ); + + if ( in_array( $fee_hash, $applied_fees, true ) ) { + unset( $fees[ $key ] ); + } + + $applied_fees[] = $fee_hash; + } + } + + // Now that we've removed any fees that are for other Downloads, lets also remove any fees that don't match this price id + if ( ! empty( $fees ) && ! empty( $download_id ) && ! is_null( $price_id ) ) { + // Remove fees that don't belong to the specified Download AND Price ID + foreach ( $fees as $key => $fee ) { + if ( is_null( $fee['price_id'] ) ) { + continue; + } + + if ( (int) $price_id !== (int) $fee['price_id'] ){ + unset( $fees[ $key ] ); + } + } + } + + if ( ! empty( $fees ) ) { + // Remove fees that belong to a specific download but are not in the cart + foreach ( $fees as $key => $fee ) { + if ( empty( $fee['download_id'] ) ) { + continue; + } + + if ( ! edd_item_in_cart( $fee['download_id'] ) ) { + unset( $fees[ $key ] ); + } + } + } + + // Allow 3rd parties to process the fees before returning them + return apply_filters( 'edd_fees_get_fees', ! empty( $fees ) ? $fees : array(), $this ); + } + + /** + * Retrieve a specific fee + * + * @since 1.5 + * + * @param string $id ID of the fee to get + * @return array|bool The fee array when available, false otherwise + */ + public function get_fee( $id = '' ) { + $fees = $this->get_fees( 'all' ); + + if ( ! isset( $fees[ $id ] ) ) + return false; + + return $fees[ $id ]; + } + + /** + * Calculate the total fee amount for a specific fee type + * + * Can be negative + * + * @since 2.0 + * @param string $type Fee type, "fee" or "item" + * @uses EDD_Fees::get_fees() + * @uses EDD_Fees::has_fees() + * @return float Total fee amount + */ + public function type_total( $type = 'fee' ) { + $fees = $this->get_fees( $type ); + $total = (float) 0.00; + + if ( $this->has_fees( $type ) ) { + foreach ( $fees as $fee ) { + $total += edd_sanitize_amount( $fee['amount'] ); + } + } + + return edd_sanitize_amount( $total ); + } + + /** + * Calculate the total fee amount + * + * Can be negative + * + * @since 1.5 + * @uses EDD_Fees::get_fees() + * @uses EDD_Fees::has_fees() + * @param int $download_id The download ID whose fees to retrieve + * @return float Total fee amount + */ + public function total( $download_id = 0 ) { + $fees = $this->get_fees( 'all', $download_id ); + $total = (float) 0.00; + + if ( $this->has_fees( 'all' ) ) { + foreach ( $fees as $fee ) { + $total += edd_sanitize_amount( $fee['amount'] ); + } + } + + return edd_sanitize_amount( $total ); + } + + /** + * Stores the fees in the payment meta + * + * @since 1.5 + * @uses EDD_Session::set() + * @param array $payment_meta The meta data to store with the payment + * @param array $payment_data The info sent from process-purchase.php + * @return array Return the payment meta with the fees added + */ + public function record_fees( $payment_meta, $payment_data ) { + if ( $this->has_fees( 'all' ) ) { + + $payment_meta['fees'] = $this->get_fees( 'all' ); + + // Only clear fees from session when status is not pending + if( ! empty( $payment_data['status'] ) && 'pending' !== strtolower( $payment_data['status'] ) ) { + + EDD()->session->set( 'edd_cart_fees', null ); + + } + } + + return $payment_meta; + } + + /** + * Gets the tax to be added to a fee. + * + * @since 3.0 + * @param array $fee + * @param float $tax_rate + * @return float + */ + public function get_calculated_tax( $fee, $tax_rate ) { + $tax = 0.00; + if ( ! ( $tax_rate || empty( $fee['no_tax'] ) ) || $fee['amount'] < 0 ) { + return $tax; + } + + return ( floatval( $fee['amount'] ) * $tax_rate ) / 100; + } +} diff --git a/includes/class-edd-html-elements.php b/includes/class-edd-html-elements.php new file mode 100755 index 00000000000..1f5d9a4512f --- /dev/null +++ b/includes/class-edd-html-elements.php @@ -0,0 +1,1023 @@ + 'products', + 'id' => 'products', + 'class' => '', + 'multiple' => false, + 'selected' => 0, + 'chosen' => false, + 'number' => 30, + 'bundles' => true, + 'variations' => false, + 'show_variations_only' => false, + 'placeholder' => sprintf( __( 'Choose a %s', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'data' => array( + 'search-type' => 'download', + 'search-placeholder' => sprintf( __( 'Search %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + ), + 'required' => false, + 'products' => array(), + ); + + $args = wp_parse_args( $args, $defaults ); + + $products = $args['products']; + if ( empty( $args['products'] ) ) { + $products = $this->get_products( $args ); + } + $existing_ids = wp_list_pluck( $products, 'ID' ); + if ( ! empty( $args['selected'] ) ) { + + $selected_items = $args['selected']; + if ( ! is_array( $selected_items ) ) { + $selected_items = array( $selected_items ); + } + + foreach ( $selected_items as $selected_item ) { + if ( ! in_array( $selected_item, $existing_ids, true ) ) { + + // If the selected item has a variation, we just need the product ID. + $has_variation = strpos( $selected_item, '_' ); + if ( false !== $has_variation ) { + $selected_item = substr( $selected_item, 0, $has_variation ); + } + + $post = get_post( $selected_item ); + if ( ! is_null( $post ) ) { + $products[] = $post; + } + } + } + } + + $options = array( + '' => '', + ); + if ( $products ) { + foreach ( $products as $product ) { + $has_variations = edd_has_variable_prices( $product->ID ); + + // If a product has no variations, just add it to the list and continue. + if ( ! $has_variations ) { + $title = esc_html( $product->post_title ); + $options[ absint( $product->ID ) ] = $title; + + continue; + } + + // The product does have variations. Add the top level product to the list + // if not showing variations, or not showing variations only. + if ( false === $args['variations'] || ! $args['show_variations_only'] ) { + $title = esc_html( $product->post_title ); + if ( ! $args['show_variations_only'] ) { + $title .= ' (' . __( 'All Price Options', 'easy-digital-downloads' ) . ')'; + } + $options[ absint( $product->ID ) ] = $title; + } + + // If showing variations, add them to the list. + if ( $args['variations'] ) { + $prices = edd_get_variable_prices( $product->ID ); + if ( ! empty( $prices ) ) { + foreach ( $prices as $key => $value ) { + $name = ! empty( $value['name'] ) ? $value['name'] : ''; + if ( $name ) { + $options[ absint( $product->ID ) . '_' . $key ] = esc_html( $product->post_title . ': ' . $name ); + } + } + } + } + } + } + + // This ensures that any selected products are included in the drop down + if ( is_array( $args['selected'] ) ) { + foreach ( $args['selected'] as $item ) { + if ( ! array_key_exists( $item, $options ) ) { + + $parsed_item = edd_parse_product_dropdown_value( $item ); + + if ( $parsed_item['price_id'] !== false ) { + $prices = edd_get_variable_prices( (int) $parsed_item['download_id'] ); + foreach ( $prices as $key => $value ) { + $name = ( isset( $value['name'] ) && ! empty( $value['name'] ) ) ? $value['name'] : ''; + + if ( $name && (int) $parsed_item['price_id'] === (int) $key ) { + $options[ absint( $product->ID ) . '_' . $key ] = esc_html( get_the_title( (int) $parsed_item['download_id'] ) . ': ' . $name ); + } + } + } else { + $options[ $parsed_item['download_id'] ] = get_the_title( $parsed_item['download_id'] ); + } + } + } + } elseif ( false !== $args['selected'] && $args['selected'] !== 0 ) { + if ( ! array_key_exists( $args['selected'], $options ) ) { + $parsed_item = edd_parse_product_dropdown_value( $args['selected'] ); + + if ( $parsed_item['price_id'] !== false ) { + $prices = edd_get_variable_prices( (int) $parsed_item['download_id'] ); + + foreach ( $prices as $key => $value ) { + $name = ( isset( $value['name'] ) && ! empty( $value['name'] ) ) ? $value['name'] : ''; + + if ( $name && (int) $parsed_item['price_id'] === (int) $key ) { + $options[ absint( $product->ID ) . '_' . $key ] = esc_html( get_the_title( (int) $parsed_item['download_id'] ) . ': ' . $name ); + } + } + } else { + $options[ $parsed_item['download_id'] ] = get_the_title( $parsed_item['download_id'] ); + } + } + } + + if ( ! $args['bundles'] ) { + $args['class'] .= ' no-bundles'; + } + + if ( $args['variations'] ) { + $args['class'] .= ' variations'; + } + + if ( $args['show_variations_only'] ) { + $args['class'] .= ' variations-only'; + } + + // 'all' gets created as an option if passed via the `selected` argument. + if ( isset( $options['all'] ) ) { + unset( $options['all'] ); + } + + $output = $this->select( array( + 'name' => $args['name'], + 'selected' => $args['selected'], + 'id' => $args['id'], + 'class' => $args['class'], + 'options' => $options, + 'chosen' => $args['chosen'], + 'multiple' => $args['multiple'], + 'placeholder' => $args['placeholder'], + 'show_option_all' => isset( $args['show_option_all'] ) ? $args['show_option_all'] : false, + 'show_option_none' => false, + 'data' => $args['data'], + 'required' => $args['required'], + ) ); + + return $output; + } + + /** + * Get EDD products for the product dropdown. + * + * @param array $args Parameters for the get_posts function. + * @return array WP_Post[] Array of download objects. + */ + public function get_products( $args = array() ) { + $defaults = array( + 'number' => 30, + 'bundles' => true, + ); + + $args = wp_parse_args( $args, $defaults ); + + $product_args = array( + 'post_type' => 'download', + 'orderby' => 'title', + 'order' => 'ASC', + 'posts_per_page' => $args['number'], + ); + + if ( ! current_user_can( 'edit_products' ) ) { + $product_args['post_status'] = apply_filters( 'edd_product_dropdown_status_nopriv', array( 'publish' ) ); + } else { + $product_args['post_status'] = apply_filters( + 'edd_product_dropdown_status', + array( + 'publish', + 'draft', + 'private', + 'future', + ) + ); + } + + if ( is_array( $product_args['post_status'] ) ) { + + // Given the array, sanitize them. + $product_args['post_status'] = array_map( 'sanitize_text_field', $product_args['post_status'] ); + } else { + + // If we didn't get an array, fallback to 'publish'. + $product_args['post_status'] = array( 'publish' ); + } + + // Maybe disable bundles. + if ( ! $args['bundles'] ) { + $product_args['meta_query'] = array( + 'relation' => 'OR', + array( + 'key' => '_edd_product_type', + 'value' => 'bundle', + 'compare' => '!=', + ), + array( + 'key' => '_edd_product_type', + 'value' => 'bundle', + 'compare' => 'NOT EXISTS', + ), + ); + } + + $product_args = apply_filters( 'edd_product_dropdown_args', $product_args ); + + return get_posts( $product_args ); + } + + /** + * Renders an HTML Dropdown of all customers + * + * @since 2.2 + * + * @param array $args + * + * @return string $output Customer dropdown + */ + public function customer_dropdown( $args = array() ) { + $defaults = array( + 'name' => 'customers', + 'id' => 'customers', + 'class' => '', + 'multiple' => false, + 'selected' => 0, + 'chosen' => true, + 'placeholder' => __( 'Choose a Customer', 'easy-digital-downloads' ), + 'number' => 30, + 'data' => array( + 'search-type' => 'customer', + 'search-placeholder' => __( 'Search Customers', 'easy-digital-downloads' ), + ), + 'none_selected' => __( 'No customer attached', 'easy-digital-downloads' ), + 'required' => false, + ); + + $args = wp_parse_args( $args, $defaults ); + + $customers = edd_get_customers( array( + 'number' => $args['number'], + ) ); + + $options = array(); + + if ( $customers ) { + $options[0] = $args['none_selected']; + foreach ( $customers as $customer ) { + $options[ absint( $customer->id ) ] = esc_html( $customer->name . ' (' . $customer->email . ')' ); + } + } else { + $options[0] = __( 'No customers found', 'easy-digital-downloads' ); + } + + if ( ! empty( $args['selected'] ) ) { + + // If a selected customer has been specified, we need to ensure it's in the initial list of customers displayed + if ( ! array_key_exists( $args['selected'], $options ) ) { + $customer = new EDD_Customer( $args['selected'] ); + + if ( $customer ) { + $options[ absint( $args['selected'] ) ] = esc_html( $customer->name . ' (' . $customer->email . ')' ); + } + } + } + + $output = $this->select( array( + 'name' => $args['name'], + 'selected' => $args['selected'], + 'id' => $args['id'], + 'class' => $args['class'] . ' edd-customer-select', + 'options' => $options, + 'multiple' => $args['multiple'], + 'placeholder' => $args['placeholder'], + 'chosen' => $args['chosen'], + 'show_option_all' => false, + 'show_option_none' => false, + 'data' => $args['data'], + 'required' => $args['required'], + ) ); + + return $output; + } + + /** + * Renders an HTML Dropdown of all the Users + * + * @since 2.6.9 + * + * @param array $args + * + * @return string $output User dropdown + */ + public function user_dropdown( $args = array() ) { + $defaults = array( + 'name' => 'users', + 'id' => 'users', + 'class' => '', + 'multiple' => false, + 'selected' => 0, + 'chosen' => true, + 'placeholder' => __( 'Select a User', 'easy-digital-downloads' ), + 'number' => 30, + 'data' => array( + 'search-type' => 'user', + 'search-placeholder' => __( 'Search Users', 'easy-digital-downloads' ), + ), + 'required' => false, + ); + + $args = wp_parse_args( $args, $defaults ); + + $user_args = array( + 'number' => $args['number'], + ); + $users = get_users( $user_args ); + $options = array(); + + if ( $users ) { + foreach ( $users as $user ) { + $options[ $user->ID ] = esc_html( $user->display_name ); + } + } else { + $options[0] = __( 'No users found', 'easy-digital-downloads' ); + } + + $selected = $args['selected']; + if ( ! is_array( $selected ) ) { + $selected = array( $selected ); + } + // If a selected user has been specified, we need to ensure it's in the initial list of user displayed + if ( ! empty( $selected ) ) { + foreach ( $selected as $selected_user ) { + if ( ! array_key_exists( $selected_user, $options ) ) { + $user = get_userdata( $selected_user ); + + if ( $user ) { + $options[ absint( $user->ID ) ] = esc_html( $user->display_name ); + } + } + } + } + + $output = $this->select( array( + 'name' => $args['name'], + 'selected' => $args['selected'], + 'id' => $args['id'], + 'class' => $args['class'] . ' edd-user-select', + 'options' => $options, + 'multiple' => $args['multiple'], + 'placeholder' => $args['placeholder'], + 'chosen' => $args['chosen'], + 'show_option_all' => false, + 'show_option_none' => false, + 'data' => $args['data'], + 'required' => $args['required'], + ) ); + + return $output; + } + + /** + * Renders an HTML Dropdown of all the Discounts + * + * @since 1.5.2 + * @since 3.0 Allow $args to be passed. + * + * @param string $name Name attribute of the dropdown. + * @param int $selected Discount to select automatically. + * @param string $status Discount post_status to retrieve. + * + * @return string $output Discount dropdown + */ + public function discount_dropdown( $name = 'edd_discounts', $selected = 0, $status = '' ) { + $defaults = array( + 'name' => 'discounts', + 'id' => 'discounts', + 'class' => '', + 'multiple' => false, + 'selected' => 0, + 'chosen' => true, + 'placeholder' => __( 'Choose a Discount', 'easy-digital-downloads' ), + 'show_option_all' => __( 'All Discounts', 'easy-digital-downloads' ), + 'number' => 30, + 'data' => array( + 'search-type' => 'discount', + 'search-placeholder' => __( 'Search Discounts', 'easy-digital-downloads' ), + ), + 'required' => false, + ); + + $args = func_get_args(); + + if ( 1 === func_num_args() && is_array( $args[0] ) ) { + $args = wp_parse_args( $args[0], $defaults ); + } else { + $args = wp_parse_args( array( + 'name' => $name, + 'selected' => $selected, + 'nopaging' => true, + ), $defaults ); + } + + $discount_args = array( + 'number' => $args['number'], + ); + + if ( ! empty( $status ) ) { + $discount_args['status'] = $status; + } + + $discounts = edd_get_discounts( $discount_args ); + $options = array(); + + if ( $discounts ) { + foreach ( $discounts as $discount ) { + $options[ absint( $discount->id ) ] = esc_html( $discount->name ); + } + } else { + $options[0] = __( 'No discounts found', 'easy-digital-downloads' ); + } + + $output = $this->select( array( + 'name' => $args['name'], + 'selected' => $args['selected'], + 'id' => $args['id'], + 'class' => $args['class'] . ' edd-user-select', + 'options' => $options, + 'multiple' => $args['multiple'], + 'placeholder' => $args['placeholder'], + 'chosen' => $args['chosen'], + 'show_option_all' => $args['show_option_all'], + 'show_option_none' => false, + 'required' => $args['required'], + ) ); + + return $output; + } + + /** + * Renders an HTML Dropdown of all the Categories + * + * @since 1.5.2 + * + * @param string $name Name attribute of the dropdown + * @param int $selected Category to select automatically + * + * @return string $output Category dropdown + */ + public function category_dropdown( $name = 'edd_categories', $selected = 0 ) { + $categories = get_terms( 'download_category', apply_filters( 'edd_category_dropdown', array() ) ); + $options = array(); + + foreach ( $categories as $category ) { + $options[ absint( $category->term_id ) ] = esc_html( $category->name ); + } + + $category_labels = edd_get_taxonomy_labels( 'download_category' ); + $output = $this->select( array( + 'name' => $name, + 'selected' => $selected, + 'options' => $options, + 'show_option_all' => sprintf( _x( 'All %s', 'plural: Example: "All Categories"', 'easy-digital-downloads' ), $category_labels['name'] ), + 'show_option_none' => false, + ) ); + + return $output; + } + + /** + * Renders an HTML Dropdown of years + * + * @since 1.5.2 + * + * @param string $name Name attribute of the dropdown. + * @param int $selected Year to select automatically. + * @param int $years_before Number of years before the current year the dropdown should start with. + * @param int $years_after Number of years after the current year the dropdown should finish at. + * @param string $id A unique identifier for the field. + * @return string $output Year dropdown + */ + public function year_dropdown( $name = 'year', $selected = 0, $years_before = 5, $years_after = 0, $id = 'edd_year_select' ) { + $current = date( 'Y' ); + $start_year = $current - absint( $years_before ); + $end_year = $current + absint( $years_after ); + $selected = empty( $selected ) ? date( 'Y' ) : $selected; + $options = array(); + + while ( $start_year <= $end_year ) { + $options[ absint( $start_year ) ] = $start_year; + $start_year ++; + } + + $output = $this->select( + array( + 'name' => $name, + 'id' => $id . '_' . $name, + 'selected' => $selected, + 'options' => $options, + 'show_option_all' => false, + 'show_option_none' => false, + ) + ); + + return $output; + } + + /** + * Renders an HTML Dropdown of months + * + * @since 1.5.2 + * + * @param string $name Name attribute of the dropdown. + * @param int $selected Month to select automatically. + * @param string $id A unique identifier for the field. + * @param boolean $return_long_name Whether to use the long name for the month. + * + * @return string $output Month dropdown + */ + public function month_dropdown( $name = 'month', $selected = 0, $id = 'edd_month_select', $return_long_name = false ) { + $month = 1; + $options = array(); + $selected = empty( $selected ) ? date( 'n' ) : $selected; + + while ( $month <= 12 ) { + $options[ absint( $month ) ] = edd_month_num_to_name( $month, $return_long_name ); + $month ++; + } + + $output = $this->select( + array( + 'name' => $name, + 'id' => $id . '_' . $name, + 'selected' => $selected, + 'options' => $options, + 'show_option_all' => false, + 'show_option_none' => false, + ) + ); + + return $output; + } + + /** + * Gets the countries dropdown. + * + * @since 3.0 + * @param array $args The array of parameters passed to the method + * @param string $country The selected country + * @return string + */ + public function country_select( $args = array(), $country = '' ) { + $args = wp_parse_args( + $args, + array( + 'name' => 'edd_countries', + 'class' => 'edd_countries_filter', + 'options' => edd_get_country_list(), + 'chosen' => true, + 'selected' => $country, + 'show_option_none' => false, + 'placeholder' => __( 'Choose a Country', 'easy-digital-downloads' ), + 'show_option_all' => __( 'All Countries', 'easy-digital-downloads' ), + 'data' => array( + 'nonce' => wp_create_nonce( 'edd-country-field-nonce' ), + ), + 'required' => false, + ) + ); + + if ( false === strpos( $args['class'], 'edd_countries_filter' ) ) { + $args['class'] .= ' edd_countries_filter'; + } + + return $this->select( $args ); + } + + /** + * Gets the regions dropdown. + * + * @since 3.0 + * @param array $args The array of parameters passed to the method + * @param string $country The country from which to populate the regions + * @param string $region The selected region + * @return string + */ + public function region_select( $args = array(), $country = '', $region = '' ) { + if ( ! $country ) { + $country = edd_get_shop_country(); + } + $args = wp_parse_args( + $args, + array( + 'name' => 'edd_regions', + 'class' => 'edd_regions_filter', + 'options' => edd_get_shop_states( $country ), + 'chosen' => true, + 'selected' => $region, + 'show_option_none' => false, + 'placeholder' => __( 'Choose a Region', 'easy-digital-downloads' ), + 'show_option_all' => __( 'All Regions', 'easy-digital-downloads' ), + 'required' => false, + ) + ); + + if ( false === strpos( $args['class'], 'edd_regions_filter' ) ) { + $args['class'] .= ' edd_regions_filter'; + } + + return $this->select( $args ); + } + + /** + * Renders an HTML Dropdown + * + * @since 1.6 + * + * @param array $args + * + * @return string + */ + public function select( $args = array() ) { + + $args = wp_parse_args( $args, array( + 'options' => array(), + 'name' => null, + 'class' => '', + 'id' => '', + 'selected' => 0, + 'chosen' => false, + 'placeholder' => null, + 'multiple' => false, + 'show_option_all' => _x( 'All', 'all dropdown items', 'easy-digital-downloads' ), + 'show_option_none' => _x( 'None', 'no dropdown items', 'easy-digital-downloads' ), + 'data' => array(), + 'readonly' => false, + 'disabled' => false, + 'required' => false, + ) ); + + $data_elements = ''; + foreach ( $args['data'] as $key => $value ) { + $data_elements .= ' data-' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"'; + } + + if ( $args['multiple'] ) { + $multiple = ' MULTIPLE'; + } else { + $multiple = ''; + } + + if ( $args['chosen'] ) { + $args['class'] .= ' edd-select-chosen'; + if ( is_rtl() ) { + $args['class'] .= ' chosen-rtl'; + } + } + + if ( $args['placeholder'] ) { + $placeholder = $args['placeholder']; + } else { + $placeholder = ''; + } + + if ( isset( $args['readonly'] ) && $args['readonly'] ) { + $readonly = ' readonly="readonly"'; + } else { + $readonly = ''; + } + + if ( isset( $args['disabled'] ) && $args['disabled'] ) { + $disabled = ' disabled="disabled"'; + } else { + $disabled = ''; + } + + $required = ''; + if ( ! empty( $args['required'] ) ) { + $required = ' required'; + } + + $class = implode( ' ', array_map( 'esc_attr', explode( ' ', $args['class'] ) ) ); + $output = ''; + + if ( ! isset( $args['selected'] ) || ( is_array( $args['selected'] ) && empty( $args['selected'] ) ) || ! $args['selected'] ) { + $selected = ""; + } + + if ( ! empty( $args['show_option_all'] ) ) { + if ( $args['multiple'] && ! empty( $args['selected'] ) ) { + $selected = selected( true, in_array( 0, (array) $args['selected'] ), false ); + } elseif ( isset( $args['selected'] ) && ! is_array( $args['selected'] ) ) { + $selected = selected( $args['selected'], 0, false ); + } + $output .= ''; + } + + if ( ! empty( $args['options'] ) ) { + if ( $args['show_option_none'] ) { + if ( $args['multiple'] ) { + $selected = selected( true, in_array( - 1, $args['selected'] ), false ); + } elseif ( isset( $args['selected'] ) && ! is_array( $args['selected'] ) && ! empty( $args['selected'] ) ) { + $selected = selected( $args['selected'], - 1, false ); + } + $output .= ''; + } + + foreach ( $args['options'] as $key => $option ) { + if ( $args['multiple'] && is_array( $args['selected'] ) ) { + $selected = selected( true, in_array( (string) $key, $args['selected'] ), false ); + } elseif ( isset( $args['selected'] ) && ! is_array( $args['selected'] ) ) { + $selected = selected( $args['selected'], $key, false ); + } + + $output .= ''; + } + } + + $output .= ''; + + return $output; + } + + /** + * Renders an HTML Checkbox + * + * @since 1.9 + * @since 3.0 Added `label` argument. + * + * @param array $args + * + * @return string Checkbox HTML code + */ + public function checkbox( $args = array() ) { + $defaults = array( + 'name' => null, + 'current' => null, + 'class' => 'edd-checkbox', + 'options' => array( + 'disabled' => false, + 'readonly' => false, + ), + 'label' => '', + 'value' => null, + ); + + $args = wp_parse_args( $args, $defaults ); + + $classes = explode( ' ', $args['class'] ); + $classes[] = $args['name']; + $class = implode( ' ', array_map( 'sanitize_html_class', array_unique( array_filter( $classes ) ) ) ); + + $options = ''; + if ( ! empty( $args['options']['disabled'] ) ) { + $options .= ' disabled="disabled"'; + } elseif ( ! empty( $args['options']['readonly'] ) ) { + $options .= ' readonly'; + } + + $value = ''; + if ( ! empty( $args['value'] ) ) { + $value .= ' value="' . esc_attr( $args['value'] ) . '"'; + } + + // Checked could mean 'on' or 1 or true, so sanitize it for checked() + $to_check = ! empty( $args['current'] ); + $checked = checked( true, $to_check, false ); + + // Get the HTML to output + $output = ''; + + if ( ! empty( $args['label'] ) ) { + $output .= ''; + } + + return $output; + } + + /** + * Renders an HTML Text field + * + * @since 1.5.2 + * + * @param array $args Arguments for the text field + * + * @return string Text field + */ + public function text( $args = array() ) { + // Backwards compatibility + if ( func_num_args() > 1 ) { + $args = func_get_args(); + + $name = $args[0]; + $value = isset( $args[1] ) ? $args[1] : ''; + $label = isset( $args[2] ) ? $args[2] : ''; + $desc = isset( $args[3] ) ? $args[3] : ''; + } + + $defaults = array( + 'id' => '', + 'name' => isset( $name ) ? $name : 'text', + 'value' => isset( $value ) ? $value : null, + 'label' => isset( $label ) ? $label : null, + 'desc' => isset( $desc ) ? $desc : null, + 'placeholder' => '', + 'class' => 'regular-text', + 'disabled' => false, + 'autocomplete' => '', + 'data' => false, + 'required' => false, + ); + + $args = wp_parse_args( $args, $defaults ); + + $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['class'] ) ) ); + $disabled = ''; + if ( $args['disabled'] ) { + $disabled = ' disabled="disabled"'; + } + + $required = ''; + if ( ! empty( $args['required'] ) ) { + $required = ' required'; + } + + $data = ''; + if ( ! empty( $args['data'] ) ) { + foreach ( $args['data'] as $key => $value ) { + $data .= 'data-' . edd_sanitize_key( $key ) . '="' . esc_attr( $value ) . '" '; + } + } + + $output = ''; + if ( ! empty( $args['label'] ) ) { + $output .= ''; + } + + if ( ! empty( $args['desc'] ) ) { + $output .= '' . esc_html( $args['desc'] ) . ''; + } + + $output .= ''; + + $output .= ''; + + return $output; + } + + /** + * Renders a date picker + * + * @since 2.4 + * + * @param array $args Arguments for the text field + * + * @return string Datepicker field + */ + public function date_field( $args = array() ) { + + if ( empty( $args['class'] ) ) { + $args['class'] = 'edd_datepicker'; + $args['data']['format'] = edd_get_date_picker_format(); + + } elseif ( ! strpos( $args['class'], 'edd_datepicker' ) ) { + $args['class'] .= ' edd_datepicker'; + $args['data']['format'] = edd_get_date_picker_format(); + } + + return $this->text( $args ); + } + + /** + * Renders an HTML textarea + * + * @since 1.9 + * + * @param array $args Arguments for the textarea + * + * @return string textarea + */ + public function textarea( $args = array() ) { + $defaults = array( + 'name' => 'textarea', + 'value' => null, + 'label' => null, + 'desc' => null, + 'class' => 'large-text', + 'disabled' => false, + ); + + $args = wp_parse_args( $args, $defaults ); + + $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['class'] ) ) ); + $disabled = ''; + if ( $args['disabled'] ) { + $disabled = ' disabled="disabled"'; + } + + $output = ''; + + if ( ! empty( $args['label'] ) ) { + $output .= ''; + } + + $output .= ''; + + if ( ! empty( $args['desc'] ) ) { + $output .= '' . esc_html( $args['desc'] ) . ''; + } + + $output .= ''; + + return $output; + } + + /** + * Renders an ajax user search field + * + * @since 2.0 + * + * @param array $args + * + * @return string text field with ajax search + */ + public function ajax_user_search( $args = array() ) { + + // Parse args + $args = wp_parse_args( $args, array( + 'id' => 'user_id', + 'name' => 'user_id', + 'value' => null, + 'placeholder' => __( 'Enter Username', 'easy-digital-downloads' ), + 'label' => null, + 'desc' => null, + 'class' => 'edd-user-dropdown', + 'disabled' => false, + 'autocomplete' => 'off', + 'data' => false, + ) ); + + // Setup the AJAX class + $args['class'] = 'edd-ajax-user-search ' . sanitize_html_class( $args['class'] ); + + // Concatenate output + $output = ''; + $output .= $this->text( $args ); + $output .= ''; + $output .= ''; + $output .= ''; + + return $output; + } + + /** + * Show a required indicator on a field. + * + * @return string + */ + public function show_required() { + + $output = ''; + $output .= sprintf( '%s', __( 'Required', 'easy-digital-downloads' ) ); + + return $output; + } +} diff --git a/includes/class-edd-license-handler.php b/includes/class-edd-license-handler.php new file mode 100644 index 00000000000..450b8e408ed --- /dev/null +++ b/includes/class-edd-license-handler.php @@ -0,0 +1,523 @@ +file = $_file; + $this->item_name = $_item_name; + + if ( is_numeric( $_item_id ) ) { + $this->item_id = absint( $_item_id ); + } + + $this->item_shortname = 'edd_' . preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->item_name ) ) ); + $this->version = $_version; + $this->edd_license = new License( $this->item_name, $_optname ); + if ( empty( $_api_url ) && ( empty( $this->edd_license->key ) || empty( $this->edd_license->license ) ) ) { + $pro_license = new License( 'pro' ); + if ( ! empty( $pro_license->key ) ) { + $this->is_pro_license = true; + $this->edd_license = $pro_license; + } + } + $this->license = $this->edd_license->key; + $this->author = $_author; + $this->api_handler = new API( $_api_url ); + $this->api_url = $_api_url; + $this->pass_manager = new \EDD\Admin\Pass_Manager(); + + // Setup hooks + $this->hooks(); + + /** + * Maintain an array of active, licensed plugins that have a license key entered. + * This is to help us more easily determine if the site has a license key entered + * at all. Initializing it this way helps us limit the data to activated plugins only. + * If we relied on the options table (`edd_%_license_active`) then we could accidentally + * be picking up plugins that have since been deactivated. + * + * @see \EDD\Admin\Promos\Notices\License_Upgrade_Notice::__construct() + */ + if ( is_null( $this->api_url ) ) { + global $edd_licensed_products; + if ( ! is_array( $edd_licensed_products ) ) { + $edd_licensed_products = array(); + } + $edd_licensed_products[ $this->item_shortname ] = (int) (bool) ( $this->license && empty( $this->edd_license->error ) ); + } + } + + /** + * Include the updater class + * + * @access private + * @return void + */ + private function includes() {} + + /** + * Setup hooks + * + * @access private + * @return void + */ + private function hooks() { + + // Register settings + add_filter( 'edd_settings_licenses', array( $this, 'settings' ) ); + + // Check that license is valid once per week + add_action( 'edd_weekly_scheduled_events', array( $this, 'weekly_license_check' ) ); + + // For testing license notices, uncomment this line to force checks on every page load + //add_action( 'admin_init', array( $this, 'weekly_license_check' ) ); + + // Updater + add_action( 'init', array( $this, 'auto_updater' ) ); + + // Display notices to admins + add_action( 'admin_notices', array( $this, 'notices' ) ); + + add_action( 'in_plugin_update_message-' . plugin_basename( $this->file ), array( $this, 'plugin_row_license_missing' ), 10, 2 ); + + // Register plugins for beta support + add_filter( 'edd_beta_enabled_extensions', array( $this, 'register_beta_support' ) ); + + // Add the EDD version to the API parameters. + add_filter( 'edd_sl_plugin_updater_api_params', array( $this, 'filter_sl_api_params' ), 10, 3 ); + + // Fix missing Stripe keys due to option name change. + add_action( 'admin_init', array( $this, 'fix_stripe_key' ) ); + } + + /** + * Auto updater + * + * @access private + * @return void + */ + public function auto_updater() { + + $doing_cron = defined( 'DOING_CRON' ) && DOING_CRON; + if ( ! current_user_can( 'manage_options' ) && ! $doing_cron ) { + return; + } + + $license = $this->license; + // Fall back to the highest license key if one is not saved for this extension or there isn't a pro license. + if ( empty( $license ) && empty( $this->api_url ) ) { + if ( $this->pass_manager->highest_license_key ) { + $license = $this->pass_manager->highest_license_key; + } + } + + // Don't check for updates if there isn't a license key. + if ( empty( $license ) ) { + return; + } + + $args = array( + 'version' => $this->version, + 'license' => $license, + 'author' => $this->author, + 'beta' => function_exists( 'edd_extension_has_beta_support' ) && edd_extension_has_beta_support( $this->item_shortname ), + ); + + if ( ! empty( $this->item_id ) ) { + $args['item_id'] = $this->item_id; + } else { + $args['item_name'] = $this->item_name; + } + + if ( ! class_exists( 'EDD_SL_Plugin_Updater' ) ) { + require_once 'EDD_SL_Plugin_Updater.php'; + } + + // Setup the updater + new EDD_SL_Plugin_Updater( + is_null( $this->api_url ) ? $this->api_handler->get_url() : $this->api_url, + $this->file, + $args + ); + } + + /** + * Add license field to settings + * + * @param array $settings + * @return array + */ + public function settings( $settings ) { + return array_merge( $settings, array( + array( + 'id' => $this->item_shortname . '_license_key', + 'name' => $this->item_name, + 'desc' => '', + 'type' => 'license_key', + 'options' => array( + 'is_valid_license_option' => $this->item_shortname . '_license_active', + 'item_id' => $this->item_id, + 'api_url' => $this->api_url, + 'file' => $this->file, + ), + 'size' => 'regular', + ) + ) ); + } + + /** + * Check if license key is valid once per week + * + * @since 2.5 + * @return void + */ + public function weekly_license_check() { + + // If a pro license is active, that license check is handled separately. + if ( $this->is_pro_license && empty( $this->api_url ) ) { + return; + } + + // Don't fire when saving settings. + if ( ! empty( $_POST['edd_settings'] ) ) { + return; + } + + if ( empty( $this->license ) ) { + return; + } + + // data to send in our API request + $api_params = array( + 'edd_action' => 'check_license', + 'license' => $this->license, + 'item_name' => urlencode( $this->item_name ), + ); + + if ( ! empty( $this->item_id ) ) { + $api_params['item_id'] = $this->item_id; + } + + $license_data = $this->api_handler->make_request( $api_params ); + if ( ! $license_data ) { + return false; + } + + if ( empty( $this->api_url ) ) { + $this->pass_manager->maybe_set_pass_flag( $this->license, $license_data ); + } + $this->edd_license->save( $license_data ); + } + + /** + * Admin notices for errors + * + * @return void + */ + public function notices() { + if ( empty( $this->license ) ) { + return; + } + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + if ( ! empty( $_GET['tab'] ) && 'licenses' === $_GET['tab'] ) { + return; + } + + if ( ( empty( $this->edd_license->license ) || 'valid' !== $this->edd_license->license ) ) { + + EDD()->notices->add_notice( + array( + 'id' => 'edd-missing-license', + 'class' => "error {$this->item_shortname}-license-error", + 'message' => sprintf( + /* translators: 1. opening anchor tag; 2. closing anchor tag */ + __( 'You have invalid or expired license keys for Easy Digital Downloads. %1$sActivate License(s)%2$s', 'easy-digital-downloads' ), + '', + '' + ), + 'is_dismissible' => false, + ) + ); + } + } + + /** + * Displays message inline on plugin row that the license key is missing + * + * @since 2.5 + * @return void + */ + public function plugin_row_license_missing( $plugin_data, $version_info ) { + static $showed_imissing_key_message = array(); + + $license = $this->edd_license; + + if ( ( empty( $license->license ) || 'valid' !== $license->license ) && empty( $showed_imissing_key_message[ $this->item_shortname ] ) ) { + echo ' ' . __( 'Enter valid license key for automatic updates.', 'easy-digital-downloads' ) . ''; + $showed_imissing_key_message[ $this->item_shortname ] = true; + } + + } + + /** + * Adds this plugin to the beta page + * + * @param array $products + * @since 2.6.11 + * @return void + */ + public function register_beta_support( $products ) { + $products[ $this->item_shortname ] = $this->item_name; + + return $products; + } + + /** + * Adds the EDD version to the API parameters. + * + * @since 2.11 + * @param array $api_params The array of parameters sent in the API request. + * @param array $api_data The array of API data defined when instantiating the class. + * @param string $plugin_file The path to the plugin file. + * @return array + */ + public function filter_sl_api_params( $api_params, $api_data, $plugin_file ) { + + if ( $this->file === $plugin_file ) { + $api_params['easy-digital-downloads_version'] = defined( 'EDD_VERSION' ) ? EDD_VERSION : ''; + } + + return $api_params; + } + + /** + * If the original Stripe gateway key is set and the new one is not, + * update the license key to fix automatic updates. + * + * @since 3.0.4 + * @return void + */ + public function fix_stripe_key() { + $license_key = edd_get_option( 'edd_stripe_pro_payment_gateway_license_key' ); + if ( $license_key ) { + return; + } + $old_key = edd_get_option( 'edd_stripe_payment_gateway_license_key' ); + if ( $old_key ) { + edd_update_option( 'edd_stripe_pro_payment_gateway_license_key', sanitize_text_field( $old_key ) ); + edd_delete_option( 'edd_stripe_payment_gateway_license_key' ); + } + + $old_license_status = get_option( 'edd_stripe_payment_gateway_license_key_active' ); + if ( $old_license_status ) { + update_option( 'edd_stripe_pro_payment_gateway_license_key_active', santize_text_field( $old_license_status ) ); + delete_option( 'edd_stripe_payment_gateway_license_key_active' ); + } + } + + /** + * Activate the license key. + * + * @deprecated 3.1.1 + * @return void + */ + public function activate_license() { + + if ( ! isset( $_POST['edd_settings'] ) ) { + return; + } + + if ( ! isset( $_REQUEST[ $this->item_shortname . '_license_key-nonce'] ) || ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) { + return; + } + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + if ( empty( $_POST['edd_settings'][ $this->item_shortname . '_license_key'] ) ) { + delete_option( $this->item_shortname . '_license_active' ); + return; + } + + foreach ( $_POST as $key => $value ) { + if ( false !== strpos( $key, 'license_key_deactivate' ) ) { + // Don't activate a key when deactivating a different key + return; + } + } + + if ( 'valid' === $this->edd_license->license ) { + return; + } + + $license = sanitize_text_field( $_POST['edd_settings'][ $this->item_shortname . '_license_key' ] ); + + if ( empty( $license ) ) { + return; + } + + // Data to send to the API + $api_params = array( + 'edd_action' => 'activate_license', + 'license' => $license, + 'item_name' => urlencode( $this->item_name ), + 'url' => home_url() + ); + + if ( ! empty( $this->item_id ) ) { + $api_params['item_id'] = $this->item_id; + } + + // Call the API + $license_data = $this->api_handler->make_request( $api_params ); + + // Make sure there are no errors + if ( ! $license_data ) { + return; + } + + // Tell WordPress to look for updates + set_site_transient( 'update_plugins', null ); + + $this->pass_manager->maybe_set_pass_flag( $license, $license_data ); + + // Clear the option for licensed extensions to force regeneration. + if ( ! empty( $license_data->license ) && 'valid' === $license_data->license ) { + delete_option( 'edd_licensed_extensions' ); + } + + $this->edd_license->save( $license_data ); + } + + /** + * Deactivate the license key + * + * @deprecated 3.1.1 + * @return void + */ + public function deactivate_license() { + + if ( ! isset( $_POST['edd_settings'] ) ) { + return; + } + + if ( ! isset( $_POST['edd_settings'][ $this->item_shortname . '_license_key'] ) ) { + return; + } + + if ( ! wp_verify_nonce( $_REQUEST[ $this->item_shortname . '_license_key-nonce'], $this->item_shortname . '_license_key-nonce' ) ) { + wp_die( __( 'Nonce verification failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + // Run on deactivate button press + if ( isset( $_POST[ $this->item_shortname . '_license_key_deactivate'] ) ) { + + // Data to send to the API + $api_params = array( + 'edd_action' => 'deactivate_license', + 'license' => $this->license, + 'item_name' => urlencode( $this->item_name ), + 'url' => home_url() + ); + + if ( ! empty( $this->item_id ) ) { + $api_params['item_id'] = $this->item_id; + } + + // Call the API + $response = $this->api_handler->make_request( $api_params ); + + // Make sure there are no errors + if ( ! $response ) { + return; + } + + $this->pass_manager->maybe_remove_pass_flag( $this->license ); + + delete_option( $this->item_shortname . '_license_active' ); + } + } + + /** + * Display help text at the top of the Licenses tag + * + * @since 2.5 + * @deprecated 3.1.1.4 + * @param string $active_tab + * @return void + */ + public function license_help_text( $active_tab = '' ) {} +} + +endif; // end class_exists check diff --git a/includes/class-edd-logging.php b/includes/class-edd-logging.php new file mode 100755 index 00000000000..522a5009f72 --- /dev/null +++ b/includes/class-edd-logging.php @@ -0,0 +1,857 @@ +log_types() ); + } + + /** + * Create new log entry + * + * This is just a simple and fast way to log something. Use $this->insert_log() + * if you need to store custom meta data + * + * @since 1.3.1 + * + * @param string $title Log entry title. + * @param string $message Log entry message. + * @param int $parent Download ID. + * @param string $type Log type (default: null). + * + * @return int ID of the newly created log item. + */ + public function add( $title = '', $message = '', $parent = 0, $type = null ) { + return $this->insert_log( array( + 'post_title' => $title, + 'post_content' => $message, + 'post_parent' => $parent, + 'log_type' => $type + ) ); + } + + /** + * Easily retrieves log items for a particular object ID. + * + * @since 1.3.1 + * + * @param int $object_id Object ID (default: 0). + * @param string $type Log type (default: null). + * @param int $paged Page number (default: null). + * + * @return array Array of the connected logs. + */ + public function get_logs( $object_id = 0, $type = null, $paged = null ) { + return $this->get_connected_logs( array( + 'post_parent' => $object_id, + 'paged' => $paged, + 'log_type' => $type + ) ); + } + + /** + * Stores a log entry. + * + * @since 1.3.1 + * @since 3.0 Updated to use the new database classes as part of the migration to custom tables. + * + * @param array $log_data Log entry data. + * @param array $log_meta Log entry meta. + * @return int The ID of the newly created log item. + */ + public function insert_log( $log_data = array(), $log_meta = array() ) { + + // Parse args + $args = wp_parse_args( $log_data, array( + 'post_type' => 'edd_log', + 'post_status' => 'publish', + 'post_parent' => 0, + 'post_content' => '', + 'log_type' => false, + ) ); + + /** + * Triggers just before a log is inserted. + * + * @param array $args Log entry data. + * @param array $log_meta Log meta data. + */ + do_action( 'edd_pre_insert_log', $args, $log_meta ); + + // Used to dynamically dispatch the method call to insert() to the correct class. + $insert_method = 'edd_add_log'; + + // Set up variables to hold data to go into the logs table by default. + $data = array( + 'content' => $args['post_content'], + 'object_id' => isset( $args['post_parent'] ) + ? $args['post_parent'] + : 0, + 'object_type' => isset( $args['log_type'] ) + ? $args['log_type'] + : null, + /* + * Fallback user ID is the current user, due to it previously being set to that by WordPress + * core when setting post_author on the CPT. + */ + 'user_id' => ! empty( $log_meta['user'] ) ? $log_meta['user'] : get_current_user_id() + ); + + $type = $args['log_type']; + if ( ! empty( $type ) ) { + $data['type'] = $type; + } + + if ( array_key_exists( 'post_title', $args ) ) { + $data['title'] = $args['post_title']; + } + + $meta_to_unset = array( 'user' ); + + // Override $data and $insert_method based on the log type. + if ( 'api_request' === $args['log_type'] ) { + $insert_method = 'edd_add_api_request_log'; + + $data = array( + 'user_id' => ! empty( $log_meta['user'] ) ? $log_meta['user'] : 0, + 'api_key' => ! empty( $log_meta['key'] ) ? $log_meta['key'] : 'public', + 'token' => ! empty( $log_meta['token'] ) ? $log_meta['token'] : 'public', + 'version' => ! empty( $log_meta['version'] ) ? $log_meta['version'] : '', + 'request' => ! empty( $args['post_excerpt'] ) ? $args['post_excerpt'] : '', + 'error' => ! empty( $args['post_content'] ) ? $args['post_content'] : '', + 'ip' => ! empty( $log_meta['request_ip'] ) ? $log_meta['request_ip'] : '', + 'time' => ! empty( $log_meta['time'] ) ? $log_meta['time'] : '', + ); + + $meta_to_unset = array( 'user', 'key', 'token', 'version', 'request_ip', 'time' ); + } elseif ( 'file_download' === $args['log_type'] ) { + $insert_method = 'edd_add_file_download_log'; + + if ( ! class_exists( 'Browser' ) ) { + require_once EDD_PLUGIN_DIR . 'includes/libraries/browser.php'; + } + + $browser = new Browser(); + + $user_agent = $browser->getBrowser() . ' ' . $browser->getVersion() . '/' . $browser->getPlatform(); + + $data = array( + 'product_id' => $args['post_parent'], + 'file_id' => ! empty( $log_meta['file_id'] ) ? $log_meta['file_id'] : 0, + 'order_id' => ! empty( $log_meta['payment_id'] ) ? $log_meta['payment_id'] : 0, + 'price_id' => ! empty( $log_meta['price_id'] ) ? $log_meta['price_id'] : 0, + 'customer_id' => ! empty( $log_meta['customer_id'] ) ? $log_meta['customer_id'] : 0, + 'ip' => ! empty( $log_meta['ip'] ) ? $log_meta['ip'] : '', + 'user_agent' => $user_agent, + ); + + $meta_to_unset = array( 'file_id', 'payment_id', 'price_id', 'customer_id', 'ip', 'user_id' ); + } + + // Now unset the meta we've used up in the main data array. + foreach ( $meta_to_unset as $meta_key ) { + unset( $log_meta[ $meta_key ] ); + } + + // Get the log ID if method is callable + $log_id = is_callable( $insert_method ) + ? call_user_func( $insert_method, $data ) + : false; + + // Set log meta, if any + if ( $log_id && ! empty( $log_meta ) ) { + + // Use the right log fetching function based on the type of log this is. + if ( 'edd_add_api_request_log' === $insert_method ) { + $add_meta_function = 'edd_add_api_request_log_meta'; + } elseif ( 'edd_add_file_download_log' === $insert_method ) { + $add_meta_function = 'edd_add_file_download_log_meta'; + } else { + $add_meta_function = 'edd_add_log_meta'; + } + + if ( is_callable( $add_meta_function ) ) { + foreach ( (array) $log_meta as $key => $meta ) { + $add_meta_function( $log_id, sanitize_key( $key ), $meta ); + } + } + } + + /** + * Triggers after a log has been inserted. + * + * @param int $log_id ID of the new log. + * @param array $args Log data. + * @param array $log_meta Log meta data. + */ + do_action( 'edd_post_insert_log', $log_id, $args, $log_meta ); + + return $log_id; + } + + /** + * Update and existing log item + * + * @since 1.3.1 + * @since 3.0 - Added $log_id parameter and boolean return type. + * + * @param array $log_data Log entry data. + * @param array $log_meta Log entry meta. + * @param int $log_id Log ID. + * @return bool True on success, false otherwise. + */ + public function update_log( $log_data = array(), $log_meta = array(), $log_id = 0 ) { + // $log_id is at the end because it was introduced in 3.0 + do_action( 'edd_pre_update_log', $log_data, $log_meta, $log_id ); + + $defaults = array( + 'post_content' => '', + 'post_title' => '', + 'object_id' => 0, + 'object_type' => '', + ); + + $args = wp_parse_args( $log_data, $defaults ); + + if ( isset( $args['ID'] ) && empty( $log_id ) ) { + $log_id = $args['ID']; + } + + // Bail if the log ID is still empty. + if ( empty( $log_id ) ) { + return false; + } + + // Used to dynamically dispatch the method call to insert() to the correct class. + $update_method = 'edd_update_log'; + $update_meta_function = 'edd_update_log_meta'; + + $type = $args['log_type']; + if ( ! empty( $type ) ) { + $data['type'] = $args['log_type']; + } + + $data = array( + 'object_id' => $args['object_id'], + 'object_type' => $args['object_type'], + 'title' => $args['title'], + 'message' => $args['message'], + ); + + if ( 'api_request' === $data['type'] ) { + $update_meta_function = 'edd_update_api_request_log_meta'; + $legacy = array( + 'user' => 'user_id', + 'key' => 'api_key', + 'token' => 'token', + 'version' => 'version', + 'post_excerpt' => 'request', + 'post_content' => 'error', + 'request_ip' => 'ip', + 'time' => 'time', + ); + + foreach ( $legacy as $old_key => $new_key ) { + if ( isset( $log_meta[ $old_key ] ) ) { + $data[ $new_key ] = $log_meta[ $old_key ]; + + unset( $log_meta[ $old_key ] ); + } + } + } elseif ( 'file_download' === $data['type'] ) { + $update_meta_function = 'edd_update_file_download_log_meta'; + $legacy = array( + 'file_id' => 'file_id', + 'payment_id' => 'payment_id', + 'price_id' => 'price_id', + 'user_id' => 'user_id', + 'ip' => 'ip', + ); + + foreach ( $legacy as $old_key => $new_key ) { + if ( isset( $log_meta[ $old_key ] ) ) { + $data[ $new_key ] = $log_meta[ $old_key ]; + + unset( $log_meta[ $old_key ] ); + } + } + + if ( isset( $args['post_parent'] ) ) { + $data['download_id'] = $args['post_parent']; + } + } + + unset( $data['type'] ); + + // Bail if not callable + if ( ! is_callable( $update_method ) ) { + return false; + } + + call_user_func( $update_method, $data ); + + // Set log meta, if any + if ( is_callable( $update_meta_function ) ) { + if ( 'edd_update_log' === $update_method && ! empty( $log_meta ) ) { + foreach ( (array) $log_meta as $key => $meta ) { + $update_meta_function( $log_id, sanitize_key( $key ), $meta ); + } + } + } + + do_action( 'edd_post_update_log', $log_id, $log_data, $log_meta ); + } + + /** + * Retrieve all connected logs. + * + * Used for retrieving logs related to particular items, such as a specific purchase. + * + * @access public + * @since 1.3.1 + * + * @param array $args Query arguments. + * @return mixed array Logs fetched, false otherwise. + */ + public function get_connected_logs( $args = array() ) { + + $log_type = isset( $args['log_type'] ) + ? $args['log_type'] + : false; + + // Parse arguments + $r = $this->parse_args( $args ); + + // Used to dynamically dispatch the call to the correct class. + $log_type = $this->get_log_table( $log_type ); + $func = "edd_get_{$log_type}"; + $logs = is_callable( $func ) + ? call_user_func( $func, $r ) + : false; + + // Return the logs (or false) + return $logs; + } + + /** + * Retrieves number of log entries connected to particular object ID. + * + * @since 1.3.1 + * @since 1.9 - Added date query support. + * + * @param int $object_id Object ID (default: 0). + * @param string $type Log type (default: null). + * @param array $meta_query Log meta query (default: null). + * @param array $date_query Log date query (default: null) [since 1.9]. + * + * @return int Log count. + */ + public function get_log_count( $object_id = 0, $type = null, $meta_query = null, $date_query = null ) { + $r = array( + $this->get_object_id_column_name_for_type( $type ) => $object_id, + ); + + if ( ! empty( $type ) && $this->valid_type( $type ) ) { + $r['type'] = $type; + } + + if ( ! empty( $meta_query ) ) { + $r['meta_query'] = $meta_query; + } + + if ( ! empty( $date_query ) ) { + $r['date_query'] = $date_query; + } + + // Used to dynamically dispatch the call to the correct class. + $log_type = $this->get_log_table( $type ); + + // Call the func, or not + $func = "edd_count_{$log_type}"; + $count = is_callable( $func ) + ? call_user_func( $func, $r ) + : 0; + + return $count; + } + + /** + * Delete logs based on parameters passed. + * + * @since 1.3.1 + * + * @param int $object_id Object ID (default: 0). + * @param string $type Log type (default: null). + * @param array $meta_query Log meta query (default: null). + */ + public function delete_logs( $object_id = 0, $type = null, $meta_query = null ) { + $r = array( + $this->get_object_id_column_name_for_type( $type ) => $object_id, + ); + + if ( ! empty( $type ) && $this->valid_type( $type ) ) { + $r['type'] = $type; + } + + if ( ! empty( $meta_query ) ) { + $r['meta_query'] = $meta_query; + } + + // Used to dynamically dispatch the call to the correct class. + $log_type = $this->get_log_table( $type ); + + // Call the func, or not. + $func = "edd_get_{$log_type}"; + $logs = is_callable( $func ) + ? call_user_func( $func, $r ) + : array(); + + // Bail if no logs. + if ( empty( $logs ) ) { + return; + } + + // Maybe bail if delete function does not exist. + $func = rtrim( "edd_delete_{$log_type}", 's' ); + if ( ! is_callable( $func ) ) { + return; + } + + // Loop through and delete logs. + foreach ( $logs as $log ) { + call_user_func( $func, $log->id ); + } + } + + /** + * Get the new log type from the old type. + * + * @since 3.0 + * + * @param string $type + * + * @return string + */ + private function get_log_table( $type = '' ) { + $retval = 'logs'; + + if ( 'api_request' === $type ) { + $retval = 'api_request_logs'; + } elseif ( 'file_download' === $type ) { + $retval = 'file_download_logs'; + } + + return $retval; + } + + /** + * Parse arguments. Contains back-compat argument aliasing. + * + * @since 3.0 + * + * @param array $args + * @return array + */ + private function parse_args( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'log_type' => false, + 'post_type' => 'edd_log', + 'post_status' => 'publish', + 'post_parent' => 0, + 'posts_per_page' => 20, + 'paged' => get_query_var( 'paged' ), + 'orderby' => 'id', + ) ); + + // Back-compat for ID ordering + if ( 'ID' === $r['orderby'] ) { + $r['orderby'] = 'id'; + } + + // Back-compat for log_type + if ( ! empty( $r['log_type'] ) ) { + $r['type'] = $r['log_type']; + } + + // Back-compat for post_parent. + if ( ! empty( $r['post_parent'] ) ) { + $type = ! empty( $r['log_type'] ) ? $r['log_type'] : ''; + $r[ $this->get_object_id_column_name_for_type( $type ) ] = $r['post_parent']; + } + + // Back compat for posts_per_page + $r['number'] = $r['posts_per_page']; + + // Unset old keys + unset( + $r['posts_per_page'], + $r['post_parent'], + $r['post_status'], + $r['post_type'], + $r['log_type'] + ); + + if ( ! isset( $r['offset'] ) ) { + $r['offset'] = $r['paged'] > 1 + ? ( ( $r['paged'] - 1 ) * $r['number'] ) + : 0; + unset( $r['paged'] ); + } + + // Return parsed args + return $r; + } + + /** + * Gets the object ID column name based on the log type. + * + * @since 3.1 + * @param string $type The log type. + * @return string The column name to query for the object ID. + */ + private function get_object_id_column_name_for_type( $type = '' ) { + + switch ( $type ) { + case 'file_download': + $object_id = 'product_id'; + break; + + case 'api_request': + $object_id = 'user_id'; + break; + + default: + $object_id = 'object_id'; + break; + } + + return $object_id; + } + + /** File System ***********************************************************/ + + /** + * Sets up the log file if it is writable + * + * @since 2.8.7 + * @return void + */ + public function setup_log_file() { + $this->init_fs(); + + $upload_dir = wp_upload_dir(); + $this->filename = wp_hash( home_url( '/' ) ) . '-edd-debug.log'; + $this->file = trailingslashit( $upload_dir['basedir'] ) . $this->filename; + + if ( ! $this->get_fs()->is_writable( $upload_dir['basedir'] ) ) { + $this->is_writable = false; + } + } + + /** + * Initialize the WordPress file system + * + * @since 3.0 + * + * @global WP_Filesystem_Base $wp_filesystem + */ + private function init_fs() { + global $wp_filesystem; + + if ( ! empty( $wp_filesystem ) ) { + return; + } + + // Include the file-system + require_once ABSPATH . 'wp-admin/includes/file.php'; + + // Initialize the file system + WP_Filesystem(); + } + + /** + * Get the WordPress file-system + * + * @since 3.0 + * + * @return WP_Filesystem_Base + */ + private function get_fs() { + return ! empty( $GLOBALS['wp_filesystem'] ) + ? $GLOBALS['wp_filesystem'] + : false; + } + + /** + * Log message to file. + * + * @access public + * @since 2.8.7 + * + * @param string $message Message to insert in the log. + */ + public function log_to_file( $message = '' ) { + $message = date( 'Y-n-d H:i:s' ) . ' - ' . $message . "\r\n"; + $this->write_to_log( $message ); + } + + /** + * Return the location of the log file that EDD_Logging will use. + * + * @since 2.9.1 + * + * @return string + */ + public function get_log_file_path() { + return $this->file; + } + + /** + * Retrieve the log data. + * + * @access public + * @since 2.8.7 + * + * @return string Log data. + */ + public function get_file_contents() { + return $this->get_file(); + } + + /** + * Retrieve the file data is written to + * + * @access protected + * @since 2.8.7 + * + * @return string File data. + */ + protected function get_file() { + $file = ''; + + if ( $this->get_fs()->exists( $this->file ) ) { + if ( ! $this->get_fs()->is_writable( $this->file ) ) { + $this->is_writable = false; + } + + $file = $this->get_fs()->get_contents( $this->file ); + } else { + $this->get_fs()->put_contents( $this->file, '' ); + $this->get_fs()->chmod( $this->file, 0664 ); + } + + return $file; + } + + /** + * Write the log message. + * + * @access protected + * @since 2.8.7 + */ + protected function write_to_log( $message = '' ) { + file_put_contents( $this->file, $message, FILE_APPEND ); + } + + /** + * Delete the log file or removes all contents in the log file if we cannot delete it. + * + * @access public + * @since 2.8.7 + * + * @return bool True if the log was cleared, false otherwise. + */ + public function clear_log_file() { + $this->get_fs()->delete( $this->file ); + + if ( $this->get_fs()->exists( $this->file ) ) { + + // It's still there, so maybe server doesn't have delete rights + $this->get_fs()->chmod( $this->file, 0664 ); + $this->get_fs()->delete( $this->file ); + + // See if it's still there... + if ( $this->get_fs()->exists( $this->file ) ) { + $this->get_fs()->put_contents( $this->file, '' ); + } + } + + $this->file = ''; + return true; + } + + /** Deprecated ************************************************************/ + + /** + * Registers the edd_log post type. + * + * @since 1.3.1 + * @deprecated 3.0 Due to migration to custom tables. + */ + public function register_post_type() { + _edd_deprecated_function( __FUNCTION__, '3.0.0' ); + } + + /** + * Register the log type taxonomy. + * + * @since 1.3.1 + * @deprecated 3.0 Due to migration to custom tables. + */ + public function register_taxonomy() { + _edd_deprecated_function( __FUNCTION__, '3.0.0' ); + } +} + +/** + * Helper method to insert a new log into the database. + * + * @since 1.3.3 + * + * @see EDD_Logging::add() + * + * @param string $title Log title. + * @param string $message Log message. + * @param int $parent Download ID. + * @param null $type Log type. + * + * @return int ID of the new log. + */ +function edd_record_log( $title = '', $message = '', $parent = 0, $type = null ) { + $edd_logs = EDD()->debug_log; + + return $edd_logs->add( $title, $message, $parent, $type ); +} + +/** + * Logs a message to the debug log file. + * + * @since 2.8.7 + * @since 2.9.4 Added the 'force' option. + * + * @param string $message Log message. + * @param bool $force Whether to force a log entry to be added. Default false. + */ +function edd_debug_log( $message = '', $force = false ) { + $edd_logs = EDD()->debug_log; + + if ( edd_is_debug_mode() || $force ) { + + if ( function_exists( 'mb_convert_encoding' ) ) { + + $message = mb_convert_encoding( $message, 'UTF-8' ); + + } + + $edd_logs->log_to_file( $message ); + } +} + +/** + * Logs an exception to the debug log file. + * + * @since 3.0 + * + * @param \Exception $exception Exception object. + */ +function edd_debug_log_exception( $exception ) { + + $label = get_class( $exception ); + + if ( $exception->getCode() ) { + + $message = sprintf( '%1$s: %2$s - %3$s', + $label, + $exception->getCode(), + $exception->getMessage() + ); + + } else { + + $message = sprintf( '%1$s: %2$s', + $label, + $exception->getMessage() + ); + + } + + edd_debug_log( $message ); +} diff --git a/includes/class-edd-register-meta.php b/includes/class-edd-register-meta.php new file mode 100644 index 00000000000..1e473357619 --- /dev/null +++ b/includes/class-edd-register-meta.php @@ -0,0 +1,472 @@ +hooks(); + } + + /** + * Get the one true instance of EDD_Register_Meta. + * + * @since 2.5 + * @return $instance + */ + static public function instance() { + + if ( !self::$instance ) { + self::$instance = new EDD_Register_Meta(); + } + + return self::$instance; + + } + + /** + * Register the hooks to kick off meta registration. + * + * @since 2.5 + * @return void + */ + private function hooks() { + add_action( 'init', array( $this, 'register_download_meta' ) ); + add_action( 'init', array( $this, 'register_payment_meta' ) ); + } + + /** + * Register the meta for the download post type. + * + * @since 2.5 + * @return void + */ + public function register_download_meta() { + register_meta( + 'post', + '_edd_download_earnings', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => 'edd_sanitize_amount', + 'type' => 'float', + 'description' => __( 'The total earnings for the specified product', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_download_sales', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => array( $this, 'intval_wrapper' ), + 'type' => 'float', + 'description' => __( 'The number of sales for the specified product.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + 'edd_price', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => array( $this, 'sanitize_price' ), + 'type' => 'float', + 'description' => __( 'The price of the product.', 'easy-digital-downloads' ), + 'show_in_rest' => true, + ) + ); + + /** + * Even though this is an array, we're using 'object' as the type here. Since the variable pricing can be either + * 1 or 0 based for the array keys, we use the additional properties to avoid WP Core resetting the variable price IDs + */ + register_meta( + 'post', + 'edd_variable_prices', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => array( $this, 'sanitize_variable_prices' ), + 'single' => true, + 'type' => 'object', + 'description' => __( 'An array of variable prices for the product.', 'easy-digital-downloads' ), + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array(), + 'additionalProperties' => array( + 'type' => 'object', + 'properties' => array( + 'index' => array( + 'type' => 'integer', + ), + 'name' => array( + 'type' => 'string', + ), + 'amount' => array( + 'type' => 'number', + ), + ), + 'additionalProperties' => true, + ), + ), + ), + ) + ); + + register_meta( + 'post', + 'edd_download_files', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => array( $this, 'sanitize_files' ), + 'type' => 'array', + 'description' => __( 'The files associated with the product, available for download.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_bundled_products', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => array( $this, 'sanitize_array' ), + 'single' => true, + 'type' => 'array', + 'description' => __( 'An array of product IDs to associate with a bundle.', 'easy-digital-downloads' ), + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ) + ) + ), + ) + ); + + register_meta( + 'post', + '_edd_button_behavior', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => 'sanitize_text_field', + 'type' => 'string', + 'description' => __( "Defines how this product's 'Purchase' button should behave, either add to cart or buy now", 'easy-digital-downloads' ), + 'show_in_rest' => true, + ) + ); + + register_meta( + 'post', + '_edd_default_price_id', + array( + 'object_subtype' => 'download', + 'sanitize_callback' => array( $this, 'intval_wrapper' ), + 'type' => 'int', + 'description' => __( 'When variable pricing is enabled, this value defines which option should be chosen by default.', 'easy-digital-downloads' ), + 'show_in_rest' => true, + ) + ); + } + + /** + * Register the meta for the edd_payment post type. + * + * @since 2.5 + * @return void + */ + public function register_payment_meta() { + + register_meta( + 'post', + '_edd_payment_user_email', + array( + 'object_subtype' => 'edd_payment', + 'sanitize_callback' => 'sanitize_email', + 'type' => 'string', + 'description' => __( 'The email address associated with the purchase.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_customer_id', + array( + 'object_subtype' => 'edd_payment', + 'sanitize_callback' => array( $this, 'intval_wrapper' ), + 'type' => 'int', + 'description' => __( 'The Customer ID associated with the payment.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_user_id', + array( + 'object_subtype' => 'edd_payment', + 'sanitize_callback' => array( $this, 'intval_wrapper' ), + 'type' => 'int', + 'description' => __( 'The User ID associated with the payment.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_user_ip', + array( + 'sanitize_callback' => 'sanitize_text_field', + 'type' => 'string', + 'description' => __( 'The IP address the payment was made from.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_purchase_key', + array( + 'sanitize_callback' => 'sanitize_text_field', + 'type' => 'string', + 'description' => __( 'The unique purchase key for this payment.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_total', + array( + 'sanitize_callback' => 'edd_sanitize_amount', + 'type' => 'float', + 'description' => __( 'The purchase total for this payment.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_mode', + array( + 'sanitize_callback' => 'sanitize_text_field', + 'type' => 'string', + 'description' => __( 'Identifies if the purchase was made in Test or Live mode.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_gateway', + array( + 'sanitize_callback' => 'sanitize_text_field', + 'type' => 'string', + 'description' => __( 'The registered gateway that was used to process this payment.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_meta', + array( + 'sanitize_callback' => array( $this, 'sanitize_array' ), + 'type' => 'array', + 'description' => __( 'Array of payment meta that contains cart details, downloads, amounts, taxes, discounts, and subtotals, etc.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_payment_tax', + array( + 'sanitize_callback' => 'edd_sanitize_amount', + 'type' => 'float', + 'description' => __( 'The total amount of tax paid for this payment.', 'easy-digital-downloads' ), + ) + ); + + register_meta( + 'post', + '_edd_completed_date', + array( + 'sanitize_callback' => 'sanitize_text_field', + 'type' => 'string', + 'description' => __( 'The date this payment was changed to the `completed` status.', 'easy-digital-downloads' ), + ) + ); + } + + /** + * Wrapper for intval + * Setting intval as the callback was stating an improper number of arguments, this avoids that. + * + * @since 2.5 + * @param int $value The value to sanitize. + * @return int The value sanitiezed to be an int. + */ + public function intval_wrapper( $value ) { + return intval( $value ); + } + + /** + * Sanitize values that come in as arrays + * + * @since 2.5 + * @param array $value The value passed into the meta. + * @return array The sanitized value. + */ + public function sanitize_array( $value = array() ) { + + if ( ! is_array( $value ) ) { + + if ( is_object( $value ) ) { + $value = (array) $value; + } + + if ( is_serialized( $value ) ) { + + preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $value, $matches ); + if ( ! empty( $matches ) ) { + return false; + } + + $value = (array) maybe_unserialize( $value ); + + } + + } + + return $value; + } + + /** + * Perform some sanitization on the amount field including not allowing negative values by default + * + * @since 2.6.5 + * @param float $price The price to sanitize + * @return float A sanitized price + */ + public function sanitize_price( $price ) { + + $allow_negative_prices = apply_filters( 'edd_allow_negative_prices', false ); + + if ( ! $allow_negative_prices && $price < 0 ) { + $price = 0; + } + + return edd_sanitize_amount( $price ); + } + + /** + * Sanitize the variable prices + * + * Ensures prices are correctly mapped to an array starting with an index of 0 + * + * @since 2.5 + * @param array $prices Variable prices + * @return array $prices Array of the remapped variable prices + */ + public function sanitize_variable_prices( $prices = array() ) { + $prices = $this->remove_blank_rows( $prices ); + + if ( ! is_array( $prices ) ) { + return array(); + } + + foreach ( $prices as $id => $price ) { + + if ( empty( $price['amount'] ) && empty( $price['name'] ) ) { + + unset( $prices[ $id ] ); + continue; + + } elseif ( empty( $price['amount'] ) ) { + + $price['amount'] = 0; + + } + + $prices[ $id ]['amount'] = $this->sanitize_price( $price['amount'] ); + + } + + return $prices; + } + + /** + * Sanitize the file downloads + * + * Ensures files are correctly mapped to an array starting with an index of 0 + * + * @since 2.5 + * @param array $files Array of all the file downloads + * @return array $files Array of the remapped file downloads + */ + function sanitize_files( $files = array() ) { + $files = $this->remove_blank_rows( $files ); + + // Files should always be in array format, even when there are none. + if ( ! is_array( $files ) ) { + $files = array(); + } + + // Clean up filenames to ensure whitespaces are stripped + foreach( $files as $id => $file ) { + + if( ! empty( $files[ $id ]['file'] ) ) { + $files[ $id ]['file'] = trim( $file['file'] ); + } + + if( ! empty( $files[ $id ]['name'] ) ) { + $files[ $id ]['name'] = sanitize_text_field( $file['name'] ); + } + } + + // Make sure all files are rekeyed starting at 0 + return $files; + } + + /** + * Don't save blank rows. + * + * When saving, check the price and file table for blank rows. + * If the name of the price or file is empty, that row should not + * be saved. + * + * @since 2.5 + * @param array $new Array of all the meta values + * @return array $new New meta value with empty keys removed + */ + private function remove_blank_rows( $new ) { + + if ( is_array( $new ) ) { + foreach ( $new as $key => $value ) { + if ( empty( $value['name'] ) && empty( $value['amount'] ) && empty( $value['file'] ) ) { + unset( $new[ $key ] ); + } + } + } + + return $new; + } + +} +EDD_Register_Meta::instance(); diff --git a/includes/class-edd-requirements-check.php b/includes/class-edd-requirements-check.php new file mode 100644 index 00000000000..f238b4a8e3f --- /dev/null +++ b/includes/class-edd-requirements-check.php @@ -0,0 +1,508 @@ + array( + 'minimum' => '7.1', + 'name' => 'PHP', + 'exists' => true, + 'current' => false, + 'checked' => false, + 'met' => false, + ), + + // WordPress + 'wp' => array( + 'minimum' => '5.4', + 'name' => 'WordPress', + 'exists' => true, + 'current' => false, + 'checked' => false, + 'met' => false, + ), + ); + + /** + * Setup plugin requirements + * + * @since 3.0 + */ + public function __construct() { + + // Setup file & base + $this->file = EDD_PLUGIN_FILE; + $this->base = EDD_PLUGIN_BASE; + + // Load or quit + $this->met() + ? $this->load() + : $this->quit(); + } + + /** + * Quit without loading + * + * @since 3.0 + */ + private function quit() { + add_action( 'admin_head', array( $this, 'admin_head' ) ); + add_filter( "plugin_action_links_{$this->base}", array( $this, 'plugin_row_links' ) ); + add_action( "after_plugin_row_{$this->base}", array( $this, 'plugin_row_notice' ) ); + } + + /** Specific Methods ******************************************************/ + + /** + * Load normally + * + * @since 3.0 + */ + private function load() { + + require_once dirname( $this->file ) . '/vendor/autoload.php'; + + // Maybe include the bundled bootstrapper + if ( ! class_exists( 'Easy_Digital_Downloads' ) ) { + require_once dirname( $this->file ) . '/includes/class-easy-digital-downloads.php'; + } + + // Maybe hook-in the bootstrapper + if ( class_exists( 'Easy_Digital_Downloads' ) ) { + + // Bootstrap to plugins_loaded before priority 10 to make sure + // add-ons are loaded after us. + add_action( 'plugins_loaded', array( $this, 'bootstrap' ), 4 ); + + // Register the activation hook + register_activation_hook( $this->file, array( $this, 'install' ) ); + } + } + + /** + * Install, usually on an activation hook. + * + * @since 3.0 + */ + public function install() { + + // Bootstrap to include all of the necessary files. + $this->bootstrap(); + + // Network wide? + $network_wide = ! empty( $_GET['networkwide'] ) + ? (bool) $_GET['networkwide'] + : false; + + // Call the installer directly during the activation hook. + edd_install( $network_wide ); + } + + /** + * Bootstrap everything. + * + * @since 3.0 + */ + public function bootstrap() { + \Easy_Digital_Downloads::instance( $this->file ); + } + + /** + * Plugin specific URL for an external requirements page. + * + * @since 3.0 + * @return string + */ + private function unmet_requirements_url() { + return 'https://easydigitaldownloads.com/recommended-wordpress-hosting/'; + } + + /** + * Plugin specific text to quickly explain what's wrong. + * + * @since 3.0 + * @return string + */ + private function unmet_requirements_text() { + esc_html_e( 'This plugin is not fully active.', 'easy-digital-downloads' ); + } + + /** + * Plugin specific text to describe a single unmet requirement. + * + * @since 3.0 + * @return string + */ + private function unmet_requirements_description_text() { + return esc_html__( 'Requires %1$s (%2$s), but (%3$s) is installed.', 'easy-digital-downloads' ); + } + + /** + * Plugin specific text to describe a single missing requirement. + * + * @since 3.0 + * @return string + */ + private function unmet_requirements_missing_text() { + return esc_html__( 'Requires %1$s (%2$s), but it appears to be missing.', 'easy-digital-downloads' ); + } + + /** + * Plugin specific text used to link to an external requirements page. + * + * @since 3.0 + * @return string + */ + private function unmet_requirements_link() { + return esc_html__( 'Requirements', 'easy-digital-downloads' ); + } + + /** + * Plugin specific aria label text to describe the requirements link. + * + * @since 3.0 + * @return string + */ + private function unmet_requirements_label() { + return esc_html__( 'Easy Digital Download Requirements', 'easy-digital-downloads' ); + } + + /** + * Plugin specific text used in CSS to identify attribute IDs and classes. + * + * @since 3.0 + * @return string + */ + private function unmet_requirements_name() { + return 'edd-requirements'; + } + + /** Agnostic Methods ******************************************************/ + + /** + * Plugin agnostic method to output the additional plugin row + * + * @since 3.0 + */ + public function plugin_row_notice() { + // wp_is_auto_update_enabled_for_type was introduced in WordPress 5.5. + $colspan = function_exists( 'wp_is_auto_update_enabled_for_type' ) && wp_is_auto_update_enabled_for_type( 'plugin' ) ? 2 : 1; + ?> + + + + + + unmet_requirements_text(); ?> + + + unmet_requirements_description(); ?> + + + requirements as $properties ) { + if ( empty( $properties['met'] ) ) { + $this->unmet_requirement_description( $properties ); + } + } + } + + /** + * Plugin agnostic method to output specific unmet requirement information + * + * @since 3.0 + * @param array $requirement + */ + private function unmet_requirement_description( $requirement = array() ) { + + // Requirement exists, but is out of date + if ( ! empty( $requirement['exists'] ) ) { + $text = sprintf( + $this->unmet_requirements_description_text(), + '' . esc_html( $requirement['name'] ) . '', + '' . esc_html( $requirement['minimum'] ) . '', + '' . esc_html( $requirement['current'] ) . '' + ); + + // Requirement could not be found + } else { + $text = sprintf( + $this->unmet_requirements_missing_text(), + '' . esc_html( $requirement['name'] ) . '', + '' . esc_html( $requirement['minimum'] ) . '' + ); + } + + // Output the description + echo '

    ' . $text . '

    '; + } + + /** + * Plugin agnostic method to output unmet requirements styling + * + * @since 3.0 + */ + public function admin_head() { + + // Get the requirements row name + $name = $this->unmet_requirements_name(); + ?> + + + unmet_requirements_url() ) . '" aria-label="' . esc_attr( $this->unmet_requirements_label() ) . '">' + . esc_html( $this->unmet_requirements_link() ) + . ''; + + // Return links with Requirements link + return $links; + } + + /** Checkers **************************************************************/ + + /** + * Plugin specific requirements checker + * + * @since 3.0 + */ + private function check() { + + // Loop through requirements + foreach ( $this->requirements as $dependency => $properties ) { + + // Which dependency are we checking? + switch ( $dependency ) { + + // PHP + case 'php': + $version = phpversion(); + break; + + // WP + case 'wp': + $version = get_bloginfo( 'version' ); + break; + + // Unknown + default: + $version = false; + break; + } + + // Merge to original array + if ( ! empty( $version ) ) { + $this->requirements[ $dependency ] = array_merge( + $this->requirements[ $dependency ], + array( + 'current' => $version, + 'checked' => true, + 'met' => version_compare( $version, $properties['minimum'], '>=' ), + ) + ); + } + } + } + + /** + * Have all requirements been met? + * + * @since 3.0 + * + * @return boolean + */ + public function met() { + + // Run the check + $this->check(); + + $to_meet = wp_list_pluck( $this->requirements, 'met' ); + + // Look for unmet dependencies, and exit if so + foreach ( $to_meet as $met ) { + if ( empty( $met ) ) { + return false; + } + } + + return true; + } + + /** Translations **********************************************************/ + + /** + * Plugin specific text-domain loader. + * + * @deprecated 3.1.1.3. Since EDD no longer bundles any language files, + * and WordPress Core automatically loads the custom wp-content/languages/easy-digital-downloads/.mo file if it's found, + * this is no longer needed. + * @since 1.4 + * @return void + */ + public function load_textdomain() { + + /* + * Due to the introduction of language packs through translate.wordpress.org, + * loading our textdomain is complex. + * + * In v2.4.6, our textdomain changed from "edd" to "easy-digital-downloads". + * + * To support existing translation files from before the change, we must + * look for translation files in several places and under several names. + * + * - wp-content/languages/plugins/easy-digital-downloads (introduced with language packs) + * - wp-content/languages/edd/ (custom folder we have supported since 1.4) + * - wp-content/plugins/easy-digital-downloads/languages/ + * + * In wp-content/languages/edd/ we must look for: + * - "easy-digital-downloads-{lang}_{country}.mo" + * + * In wp-content/languages/edd/ we must look for: + * - "edd-{lang}_{country}.mo" as that was the old file naming convention + * + * In wp-content/languages/plugins/easy-digital-downloads/ we only need to look for: + * - "easy-digital-downloads-{lang}_{country}.mo" as that is the new structure + * + * In wp-content/plugins/easy-digital-downloads/languages/, we must look for: + * - both naming conventions. This is done by filtering "load_textdomain_mofile" + */ + add_filter( 'load_textdomain_mofile', array( $this, 'load_old_textdomain' ), 10, 2 ); + + // Set filter for plugin's languages directory. + $edd_lang_dir = dirname( $this->base ) . '/languages/'; + $edd_lang_dir = apply_filters( 'edd_languages_directory', $edd_lang_dir ); + + unload_textdomain( 'easy-digital-downloads' ); + + /** + * Defines the plugin language locale used in Easy Digital Downloads. + * + * @var $get_locale The locale to use. + */ + $locale = apply_filters( 'plugin_locale', get_user_locale(), 'easy-digital-downloads' ); + $mofile = sprintf( '%1$s-%2$s.mo', 'easy-digital-downloads', $locale ); + + // Look for wp-content/languages/edd/easy-digital-downloads-{lang}_{country}.mo + $mofile_global1 = WP_LANG_DIR . "/edd/easy-digital-downloads-{$locale}.mo"; + + // Look for wp-content/languages/edd/edd-{lang}_{country}.mo + $mofile_global2 = WP_LANG_DIR . "/edd/edd-{$locale}.mo"; + + // Look in wp-content/languages/plugins/easy-digital-downloads + $mofile_global3 = WP_LANG_DIR . "/plugins/easy-digital-downloads/{$mofile}"; + + // Try to load from first global location + if ( file_exists( $mofile_global1 ) ) { + load_textdomain( 'easy-digital-downloads', $mofile_global1 ); + + // Try to load from next global location + } elseif ( file_exists( $mofile_global2 ) ) { + load_textdomain( 'easy-digital-downloads', $mofile_global2 ); + + // Try to load from next global location + } elseif ( file_exists( $mofile_global3 ) ) { + load_textdomain( 'easy-digital-downloads', $mofile_global3 ); + + // Load the default language files + } else { + load_plugin_textdomain( 'easy-digital-downloads', false, $edd_lang_dir ); + } + } + + /** + * Load a .mo file for the old textdomain if one exists. + * + * @deprecated 3.1.1.3 + * @see https://github.com/10up/grunt-wp-plugin/issues/21#issuecomment-62003284 + */ + public function load_old_textdomain( $mofile, $textdomain ) { + + // Fallback for old text domain + if ( ( 'easy-digital-downloads' === $textdomain ) && ! file_exists( $mofile ) ) { + $mofile = dirname( $mofile ) . DIRECTORY_SEPARATOR . str_replace( $textdomain, 'edd', basename( $mofile ) ); + } + + // Return (possibly overridden) mofile + return $mofile; + } +} diff --git a/includes/class-edd-roles.php b/includes/class-edd-roles.php new file mode 100755 index 00000000000..7970c4d048a --- /dev/null +++ b/includes/class-edd-roles.php @@ -0,0 +1,297 @@ + true, + 'edit_posts' => true, + 'delete_posts' => true, + 'unfiltered_html' => true, + 'upload_files' => true, + 'export' => true, + 'import' => true, + 'delete_others_pages' => true, + 'delete_others_posts' => true, + 'delete_pages' => true, + 'delete_private_pages' => true, + 'delete_private_posts' => true, + 'delete_published_pages' => true, + 'delete_published_posts' => true, + 'edit_others_pages' => true, + 'edit_others_posts' => true, + 'edit_pages' => true, + 'edit_private_pages' => true, + 'edit_private_posts' => true, + 'edit_published_pages' => true, + 'edit_published_posts' => true, + 'manage_categories' => true, + 'manage_links' => true, + 'moderate_comments' => true, + 'publish_pages' => true, + 'publish_posts' => true, + 'read_private_pages' => true, + 'read_private_posts' => true, + ) ); + + add_role( 'shop_accountant', __( 'Shop Accountant', 'easy-digital-downloads' ), array( + 'read' => true, + 'edit_posts' => false, + 'delete_posts' => false, + ) ); + + add_role( 'shop_worker', __( 'Shop Worker', 'easy-digital-downloads' ), array( + 'read' => true, + 'edit_posts' => false, + 'upload_files' => true, + 'delete_posts' => false, + ) ); + + add_role( 'shop_vendor', __( 'Shop Vendor', 'easy-digital-downloads' ), array( + 'read' => true, + 'edit_posts' => false, + 'upload_files' => true, + 'delete_posts' => false, + ) ); + } + + /** + * Add new shop-specific capabilities. + * + * @since 1.4.4 + */ + public function add_caps() { + global $wp_roles; + + if ( class_exists( 'WP_Roles' ) ) { + if ( ! isset( $wp_roles ) ) { + $wp_roles = new WP_Roles(); // WPCS: override ok. + } + } + + if ( is_object( $wp_roles ) ) { + $wp_roles->add_cap( 'shop_manager', 'view_shop_reports' ); + $wp_roles->add_cap( 'shop_manager', 'view_shop_sensitive_data' ); + $wp_roles->add_cap( 'shop_manager', 'export_shop_reports' ); + $wp_roles->add_cap( 'shop_manager', 'manage_shop_settings' ); + $wp_roles->add_cap( 'shop_manager', 'manage_shop_discounts' ); + + $wp_roles->add_cap( 'administrator', 'view_shop_reports' ); + $wp_roles->add_cap( 'administrator', 'view_shop_sensitive_data' ); + $wp_roles->add_cap( 'administrator', 'export_shop_reports' ); + $wp_roles->add_cap( 'administrator', 'manage_shop_discounts' ); + $wp_roles->add_cap( 'administrator', 'manage_shop_settings' ); + + // Add the main post type capabilities. + $capabilities = $this->get_core_caps(); + foreach ( $capabilities as $cap_group ) { + foreach ( $cap_group as $cap ) { + $wp_roles->add_cap( 'shop_manager', $cap ); + $wp_roles->add_cap( 'administrator', $cap ); + $wp_roles->add_cap( 'shop_worker', $cap ); + } + } + + $wp_roles->add_cap( 'shop_accountant', 'edit_products' ); + $wp_roles->add_cap( 'shop_accountant', 'read_private_products' ); + $wp_roles->add_cap( 'shop_accountant', 'view_shop_reports' ); + $wp_roles->add_cap( 'shop_accountant', 'export_shop_reports' ); + $wp_roles->add_cap( 'shop_accountant', 'edit_shop_payments' ); + + $wp_roles->add_cap( 'shop_vendor', 'edit_product' ); + $wp_roles->add_cap( 'shop_vendor', 'edit_products' ); + $wp_roles->add_cap( 'shop_vendor', 'delete_product' ); + $wp_roles->add_cap( 'shop_vendor', 'delete_products' ); + $wp_roles->add_cap( 'shop_vendor', 'publish_products' ); + $wp_roles->add_cap( 'shop_vendor', 'edit_published_products' ); + $wp_roles->add_cap( 'shop_vendor', 'upload_files' ); + $wp_roles->add_cap( 'shop_vendor', 'assign_product_terms' ); + } + } + + /** + * Gets the core post type capabilities. + * + * @since 1.4.4 + * + * @return array $capabilities Core post type capabilities. + */ + public function get_core_caps() { + $capabilities = array(); + + $capability_types = array( 'product', 'shop_payment', 'shop_discount' ); + + foreach ( $capability_types as $capability_type ) { + $capabilities[ $capability_type ] = array( + // Post type + "edit_{$capability_type}", + "read_{$capability_type}", + "delete_{$capability_type}", + "edit_{$capability_type}s", + "edit_others_{$capability_type}s", + "publish_{$capability_type}s", + "read_private_{$capability_type}s", + "delete_{$capability_type}s", + "delete_private_{$capability_type}s", + "delete_published_{$capability_type}s", + "delete_others_{$capability_type}s", + "edit_private_{$capability_type}s", + "edit_published_{$capability_type}s", + + // Terms + "manage_{$capability_type}_terms", + "edit_{$capability_type}_terms", + "delete_{$capability_type}_terms", + "assign_{$capability_type}_terms", + + // Custom + "view_{$capability_type}_stats", + "import_{$capability_type}s", + ); + } + + return $capabilities; + } + + /** + * Map meta caps to primitive caps. + * + * @since 2.0 + * + * @param array $caps Capabilities for meta capability. + * @param string $cap Capability name. + * @param int $user_id User ID. + * @param mixed $args Arguments. + * + * @return array $caps + */ + public function meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) { + + // Ensure user ID is a valid integer. + $user_id = absint( $user_id ); + + switch ( $cap ) { + case 'view_product_stats': + if ( empty( $args[0] ) ) { + break; + } + + $download = get_post( $args[0] ); + + // Bail if download was not found. + if ( empty( $download ) ) { + break; + } + + // No stats for auto-drafts. + if ( 'auto-draft' === $download->post_status ) { + $caps = array( 'do_not_allow' ); + break; + } + + if ( user_can( $user_id, 'view_shop_reports' ) || absint( $download->post_author ) === $user_id ) { + $caps = array(); + } + + break; + } + + return $caps; + + } + + /** + * Remove core post type capabilities (called on uninstall). + * + * @since 1.5.2 + */ + public function remove_caps() { + global $wp_roles; + + if ( class_exists( 'WP_Roles' ) ) { + if ( ! isset( $wp_roles ) ) { + $wp_roles = new WP_Roles(); // WPCS: override ok. + } + } + + if ( is_object( $wp_roles ) ) { + + /** Shop Manager Capabilities */ + $wp_roles->remove_cap( 'shop_manager', 'view_shop_reports' ); + $wp_roles->remove_cap( 'shop_manager', 'view_shop_sensitive_data' ); + $wp_roles->remove_cap( 'shop_manager', 'export_shop_reports' ); + $wp_roles->remove_cap( 'shop_manager', 'manage_shop_discounts' ); + $wp_roles->remove_cap( 'shop_manager', 'manage_shop_settings' ); + + /** Site Administrator Capabilities */ + $wp_roles->remove_cap( 'administrator', 'view_shop_reports' ); + $wp_roles->remove_cap( 'administrator', 'view_shop_sensitive_data' ); + $wp_roles->remove_cap( 'administrator', 'export_shop_reports' ); + $wp_roles->remove_cap( 'administrator', 'manage_shop_discounts' ); + $wp_roles->remove_cap( 'administrator', 'manage_shop_settings' ); + + /** Remove the Main Post Type Capabilities */ + $capabilities = $this->get_core_caps(); + + foreach ( $capabilities as $cap_group ) { + foreach ( $cap_group as $cap ) { + $wp_roles->remove_cap( 'shop_manager', $cap ); + $wp_roles->remove_cap( 'administrator', $cap ); + $wp_roles->remove_cap( 'shop_worker', $cap ); + } + } + + /** Shop Accountant Capabilities */ + $wp_roles->remove_cap( 'shop_accountant', 'edit_products' ); + $wp_roles->remove_cap( 'shop_accountant', 'read_private_products' ); + $wp_roles->remove_cap( 'shop_accountant', 'view_shop_reports' ); + $wp_roles->remove_cap( 'shop_accountant', 'export_shop_reports' ); + + /** Shop Vendor Capabilities */ + $wp_roles->remove_cap( 'shop_vendor', 'edit_product' ); + $wp_roles->remove_cap( 'shop_vendor', 'edit_products' ); + $wp_roles->remove_cap( 'shop_vendor', 'delete_product' ); + $wp_roles->remove_cap( 'shop_vendor', 'delete_products' ); + $wp_roles->remove_cap( 'shop_vendor', 'publish_products' ); + $wp_roles->remove_cap( 'shop_vendor', 'edit_published_products' ); + $wp_roles->remove_cap( 'shop_vendor', 'upload_files' ); + } + } +} diff --git a/includes/class-edd-session.php b/includes/class-edd-session.php new file mode 100755 index 00000000000..8b2fb5cdae6 --- /dev/null +++ b/includes/class-edd-session.php @@ -0,0 +1,426 @@ +use_php_sessions = $this->use_php_sessions(); + + if ( $this->use_php_sessions ) { + if ( is_multisite() ) { + $this->prefix = '_' . get_current_blog_id(); + } + + // Use PHP SESSION (must be enabled via the EDD_USE_PHP_SESSIONS constant) + add_action( 'init', array( $this, 'maybe_start_session' ), -2 ); + } else { + if ( ! $this->should_start_session() ) { + return; + } + + // Use WP_Session (default) + if ( ! defined( 'WP_SESSION_COOKIE' ) ) { + define( 'WP_SESSION_COOKIE', 'edd_wp_session' ); + } + + if ( ! class_exists( 'Recursive_ArrayAccess' ) ) { + require_once EDD_PLUGIN_DIR . 'includes/libraries/class-recursive-arrayaccess.php'; + } + + if ( ! class_exists( 'WP_Session' ) ) { + require_once EDD_PLUGIN_DIR . 'includes/libraries/class-wp-session.php'; + require_once EDD_PLUGIN_DIR . 'includes/libraries/wp-session.php'; + } + + add_filter( 'wp_session_expiration_variant', array( $this, 'set_expiration_variant_time' ), 99999 ); + add_filter( 'wp_session_expiration', array( $this, 'set_expiration_time' ), 99999 ); + } + + // Based off our session handling, we need to use different hooks and priorities. + if ( empty( $this->session ) && ! $this->use_php_sessions ) { + $hook = 'plugins_loaded'; + $priority = 10; + } else { + $hook = 'init'; + $priority = -1; + } + + add_action( $hook, array( $this, 'init' ), $priority ); + } + + /** + * Setup the WP_Session instance. + * + * @since 1.5 + */ + public function init() { + if ( $this->use_php_sessions ) { + $key = 'edd' . $this->prefix; + $this->session = isset( $_SESSION[ $key ] ) && is_array( $_SESSION[ $key ] ) + ? $_SESSION[ $key ] + : array(); + } else { + $this->session = WP_Session::get_instance(); + } + + $use_cookie = $this->use_cart_cookie(); + $cart = $this->get( 'edd_cart' ); + $purchase = $this->get( 'edd_purchase' ); + + if ( $use_cookie ) { + if ( ! empty( $cart ) || ! empty( $purchase ) ) { + $this->set_cart_cookie(); + } else { + $this->set_cart_cookie( false ); + } + } + + return $this->session; + } + + /** + * Retrieve session ID. + * + * @since 1.6 + * + * @return string Session ID + */ + public function get_id() { + return $this->session->session_id; + } + + /** + * Retrieve a session variable. + * + * @since 1.5 + * + * @param string $key Session key. + * @return mixed Session variable. + */ + public function get( $key ) { + $key = sanitize_key( $key ); + $return = false; + + if ( isset( $this->session[ $key ] ) && ! empty( $this->session[ $key ] ) ) { + preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $this->session[ $key ], $matches ); + + if ( ! empty( $matches ) ) { + $this->set( $key, null ); + return false; + } + + if ( is_numeric( $this->session[ $key ] ) ) { + $return = $this->session[ $key ]; + } else { + $maybe_json = json_decode( $this->session[ $key ] ); + + // Since json_last_error is PHP 5.3+, we have to rely on a `null` value for failing to parse JSON. + if ( is_null( $maybe_json ) ) { + $is_serialized = is_serialized( $this->session[ $key ] ); + if ( $is_serialized ) { + $value = @unserialize( $this->session[ $key ] ); + $this->set( $key, (array) $value ); + $return = $value; + } else { + $return = $this->session[ $key ]; + } + } else { + $return = json_decode( $this->session[ $key ], true ); + } + } + } + + return $return; + } + + /** + * Set a session variable. + * + * @since 1.5 + * + * @param string $key Session key. + * @param int|string|array $value Session variable. + * + * @return mixed Session variable + */ + public function set( $key, $value ) { + $key = sanitize_key( $key ); + + if ( is_array( $value ) ) { + $this->session[ $key ] = wp_json_encode( $value ); + } else { + $this->session[ $key ] = esc_attr( $value ); + } + + if ( $this->use_php_sessions ) { + $_SESSION[ 'edd' . $this->prefix ] = $this->session; + } + + return $this->session[ $key ]; + } + + /** + * Set a cookie to identify whether the cart is empty or not. + * + * This is for hosts and caching plugins to identify if caching should be disabled. + * + * @since 1.8 + * + * @param bool $set Whether to set or destroy. Default true. + */ + public function set_cart_cookie( $set = true ) { + + // Bail if headers already sent. + if ( headers_sent() ) { + return; + } + + if ( $set ) { + @setcookie( 'edd_items_in_cart', '1', time() + 30 * 60, COOKIEPATH, COOKIE_DOMAIN, is_ssl() ); + } elseif ( isset( $_COOKIE['edd_items_in_cart'] ) ) { + @setcookie( 'edd_items_in_cart', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, is_ssl() ); + } + } + + /** + * Force the cookie expiration variant time to 23 hours. + * + * @since 2.0 + * @since 3.0 Set default value of $exp parameter to 1 as it is unused. + * + * @param int $exp Default expiration (1 hour). + * @return int Cookie expiration variant time. + */ + public function set_expiration_variant_time( $exp = 1 ) { + return HOUR_IN_SECONDS * 23; + } + + /** + * Force the cookie expiration time to 24 hours. + * + * @since 1.9 + * @since 3.0 Set default value of $exp parameter to 1 as it is unused. + * + * @param int $exp Default expiration (1 hour). + * @return int Cookie expiration time. + */ + public function set_expiration_time( $exp = 1 ) { + return HOUR_IN_SECONDS * 24; + } + + /** + * Starts a new session if one hasn't started yet. + * + * Checks to see if the server supports PHP sessions + * or if the EDD_USE_PHP_SESSIONS constant is defined + * + * @since 2.1 + * @return bool $ret True if we are using PHP sessions, false otherwise. + */ + public function use_php_sessions() { + + // Set default return value to false. + $ret = false; + + // If the database variable is already set, no need to run autodetection. + $edd_use_php_sessions = (bool) get_option( 'edd_use_php_sessions' ); + + if ( ! $edd_use_php_sessions ) { + + // Attempt to detect if the server supports PHP sessions + if ( function_exists( 'session_start' ) ) { + $this->set( 'edd_use_php_sessions', 1 ); + + if ( $this->get( 'edd_use_php_sessions' ) ) { + $ret = true; + + // Set the database option + update_option( 'edd_use_php_sessions', true ); + } + } + } else { + $ret = $edd_use_php_sessions; + } + + // Enable or disable PHP Sessions based on the EDD_USE_PHP_SESSIONS constant. + if ( defined( 'EDD_USE_PHP_SESSIONS' ) && EDD_USE_PHP_SESSIONS ) { + $ret = true; + } else if ( defined( 'EDD_USE_PHP_SESSIONS' ) && ! EDD_USE_PHP_SESSIONS ) { + $ret = false; + } + + // Filter & return. + return (bool) apply_filters( 'edd_use_php_sessions', $ret ); + } + + /** + * Determines if a user has set the EDD_USE_CART_COOKIE. + * + * @since 2.5 + * + * @return bool If the store should use the edd_items_in_cart cookie to help avoid caching + */ + public function use_cart_cookie() { + + // Set default return value to true. + $ret = true; + + if ( defined( 'EDD_USE_CART_COOKIE' ) && ! EDD_USE_CART_COOKIE ) { + $ret = false; + } + + // Filter & return. + return (bool) apply_filters( 'edd_use_cart_cookie', $ret ); + } + + /** + * Determines if we should start sessions. + * + * @since 2.5.11 + * + * @return bool True if sessions should start, false otherwise. + */ + public function should_start_session() { + + // Set default return value to true. + $start_session = true; + + if ( ! empty( $_SERVER['REQUEST_URI'] ) ) { + $blacklist = $this->get_blacklist(); + $uri = ltrim( $_SERVER['REQUEST_URI'], '/' ); + $uri = untrailingslashit( $uri ); + + if ( in_array( $uri, $blacklist, true ) ) { + $start_session = false; + } + + if ( false !== strpos( $uri, 'feed=' ) ) { + $start_session = false; + } + + // We do not want to start sessions in the admin unless we're processing an ajax request. + if ( is_admin() && false === strpos( $uri, 'wp-admin/admin-ajax.php' ) ) { + $start_session = false; + } + + // Starting sessions while saving the file editor can break the save process, so don't start. + if ( false !== strpos( $uri, 'wp_scrape_key' ) ) { + $start_session = false; + } + } + + // Filter & return. + return (bool) apply_filters( 'edd_start_session', $start_session ); + } + + /** + * Retrieve the URI blacklist. + * + * These are the URIs where we never start sessions. + * + * @since 2.5.11 + * + * @return array URI blacklist. + */ + public function get_blacklist() { + $blacklist = apply_filters( 'edd_session_start_uri_blacklist', array( + 'feed', + 'feed/rss', + 'feed/rss2', + 'feed/rdf', + 'feed/atom', + 'comments/feed' + ) ); + + // Look to see if WordPress is in a sub folder or this is a network site that uses sub folders + $folder = str_replace( network_home_url(), '', get_site_url() ); + + if ( ! empty( $folder ) ) { + foreach ( $blacklist as $path ) { + $blacklist[] = $folder . '/' . $path; + } + } + + return $blacklist; + } + + /** + * Starts a new session if one hasn't started yet. + * + * @since 2.1.3 + */ + public function maybe_start_session() { + + // Bail if should not start session. + if ( ! $this->should_start_session() ) { + return; + } + + // Bail if headers already sent. + if ( headers_sent() ) { + return; + } + + // Start if old version of PHP & no session ID exists. + if ( version_compare( PHP_VERSION, '5.4', '<' ) && ! session_id() ) { + session_start(); + + // Start if modern PHP and session-status is not active. + } elseif ( defined( 'PHP_SESSION_ACTIVE' ) && ( session_status() !== PHP_SESSION_ACTIVE ) ) { + session_start(); + } + } +} diff --git a/includes/class-edd-stats.php b/includes/class-edd-stats.php new file mode 100755 index 00000000000..c1ce208afec --- /dev/null +++ b/includes/class-edd-stats.php @@ -0,0 +1,522 @@ + __( 'Today', 'easy-digital-downloads' ), + 'yesterday' => __( 'Yesterday', 'easy-digital-downloads' ), + 'this_week' => __( 'This Week', 'easy-digital-downloads' ), + 'last_week' => __( 'Last Week', 'easy-digital-downloads' ), + 'this_month' => __( 'This Month', 'easy-digital-downloads' ), + 'last_month' => __( 'Last Month', 'easy-digital-downloads' ), + 'this_quarter' => __( 'This Quarter', 'easy-digital-downloads' ), + 'last_quarter' => __( 'Last Quarter', 'easy-digital-downloads' ), + 'this_year' => __( 'This Year', 'easy-digital-downloads' ), + 'last_year' => __( 'Last Year', 'easy-digital-downloads' ) + ) ); + } + + /** + * Setup the dates passed to our constructor. + * + * This calls the convert_date() member function to ensure the dates are formatted correctly + * + * @since 1.8 + * @return void + */ + public function setup_dates( $_start_date = 'this_month', $_end_date = false ) { + + if ( empty( $_start_date ) ) { + $_start_date = 'this_month'; + } + + if ( empty( $_end_date ) ) { + $_end_date = $_start_date; + } + + $this->start_date = $this->convert_date( $_start_date ); + $this->end_date = $this->convert_date( $_end_date, true ); + + } + + /** + * Converts a date to a timestamp + * + * @since 1.8 + * @return array|WP_Error If the date is invalid, a WP_Error object will be returned + */ + public function convert_date( $date, $end_date = false ) { + + $this->timestamp = false; + $second = $end_date ? 59 : 0; + $minute = $end_date ? 59 : 0; + $hour = $end_date ? 23 : 0; + $day = 1; + $month = date( 'n', current_time( 'timestamp' ) ); + $year = date( 'Y', current_time( 'timestamp' ) ); + + if ( ( is_string( $date ) || is_int( $date ) ) && array_key_exists( $date, $this->get_predefined_dates() ) ) { + + // This is a predefined date rate, such as last_week + switch( $date ) { + + case 'this_month' : + + if ( $end_date ) { + + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + + } + + break; + + case 'last_month' : + + if ( $month == 1 ) { + + $month = 12; + $year--; + + } else { + + $month--; + + } + + if ( $end_date ) { + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + } + + break; + + case 'today' : + + $day = date( 'd', current_time( 'timestamp' ) ); + + if ( $end_date ) { + $hour = 23; + $minute = 59; + $second = 59; + } + + break; + + case 'yesterday' : + + $day = date( 'd', current_time( 'timestamp' ) ) - 1; + + // Check if Today is the first day of the month (meaning subtracting one will get us 0) + if ( $day < 1 ) { + + // If current month is 1 + if ( 1 == $month ) { + + $year -= 1; // Today is January 1, so skip back to last day of December + $month = 12; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + + } else { + + // Go back one month and get the last day of the month + $month -= 1; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + + } + } + + break; + + case 'this_week' : + + $days_to_week_start = ( date( 'w', current_time( 'timestamp' ) ) - 1 ) *60*60*24; + $today = date( 'd', current_time( 'timestamp' ) ) *60*60*24; + + if ( $today < $days_to_week_start ) { + + if ( $month > 1 ) { + $month -= 1; + } else { + $month = 12; + } + + } + + if ( ! $end_date ) { + + // Getting the start day + + $day = date( 'd', current_time( 'timestamp' ) - $days_to_week_start ) - 1; + $day += get_option( 'start_of_week' ); + + } else { + + // Getting the end day + + $day = date( 'd', current_time( 'timestamp' ) - $days_to_week_start ) - 1; + $day += get_option( 'start_of_week' ) + 6; + + } + + break; + + case 'last_week' : + + $days_to_week_start = ( date( 'w', current_time( 'timestamp' ) ) - 1 ) *60*60*24; + $today = date( 'd', current_time( 'timestamp' ) ) *60*60*24; + + if ( $today < $days_to_week_start ) { + + if ( $month > 1 ) { + $month -= 1; + } else { + $month = 12; + } + + } + + if ( ! $end_date ) { + + // Getting the start day + + $day = date( 'd', current_time( 'timestamp' ) - $days_to_week_start ) - 8; + $day += get_option( 'start_of_week' ); + + } else { + + // Getting the end day + + $day = date( 'd', current_time( 'timestamp' ) - $days_to_week_start ) - 8; + $day += get_option( 'start_of_week' ) + 6; + + } + + break; + + case 'this_quarter' : + + $month_now = date( 'n', current_time( 'timestamp' ) ); + + if ( $month_now <= 3 ) { + + if ( ! $end_date ) { + $month = 1; + } else { + $month = 3; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + } else if ( $month_now <= 6 ) { + + if ( ! $end_date ) { + $month = 4; + } else { + $month = 6; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + } else if ( $month_now <= 9 ) { + + if ( ! $end_date ) { + $month = 7; + } else { + $month = 9; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + } else { + + if ( ! $end_date ) { + $month = 10; + } else { + $month = 12; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + } + + break; + + case 'last_quarter' : + + $month_now = date( 'n', current_time( 'timestamp' ) ); + + if ( $month_now <= 3 ) { + + if ( ! $end_date ) { + $month = 10; + } else { + $year -= 1; + $month = 12; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + } else if ( $month_now <= 6 ) { + + if ( ! $end_date ) { + $month = 1; + } else { + $month = 3; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + } else if ( $month_now <= 9 ) { + + if ( ! $end_date ) { + $month = 4; + } else { + $month = 6; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + } else { + + if ( ! $end_date ) { + $month = 7; + } else { + $month = 9; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + } + + break; + + case 'this_year' : + + if ( ! $end_date ) { + $month = 1; + } else { + $month = 12; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + break; + + case 'last_year' : + + $year -= 1; + if ( ! $end_date ) { + $month = 1; + $day = 1; + } else { + $month = 12; + $day = cal_days_in_month( CAL_GREGORIAN, $month, $year ); + $hour = 23; + $minute = 59; + $second = 59; + } + + break; + + } + + + } else if ( is_numeric( $date ) ) { + + // return $date unchanged since it is a timestamp + $this->timestamp = true; + + } else if ( false !== strtotime( $date ) ) { + + $date = strtotime( $date, current_time( 'timestamp' ) ); + $year = date( 'Y', $date ); + $month = date( 'm', $date ); + $day = date( 'd', $date ); + + } else { + return new WP_Error( 'invalid_date', __( 'Improper date provided.', 'easy-digital-downloads' ) ); + } + + // Create an exact timestamp + if ( false === $this->timestamp ) { + $date = mktime( $hour, $minute, $second, $month, $day, $year ); + } + + return apply_filters( 'edd_stats_date', $date, $end_date, $this ); + } + + /** + * Modifies the WHERE flag for payment counts + * + * Only get payments in our date range + * + * @since 1.8 + * @return string + */ + public function count_where( $where = '' ) { + + $start_where = $end_where = ''; + + if ( $this->start_date ) { + + if ( $this->timestamp ) { + $format = 'Y-m-d H:i:s'; + } else { + $format = 'Y-m-d 00:00:00'; + } + + $start_date = date( $format, $this->start_date ); + $start_where = " AND date_created >= '{$start_date}'"; + } + + if ( $this->end_date ) { + + if ( $this->timestamp ) { + $format = 'Y-m-d H:i:s'; + } else { + $format = 'Y-m-d 23:59:59'; + } + + $end_date = date( $format, $this->end_date ); + + $end_where = " AND date_created <= '{$end_date}'"; + } + + $where .= "{$start_where}{$end_where}"; + + return $where; + } + + /** + * Modifies the WHERE flag for payment queries + * + * @since 1.8 + * @return string + */ + public function payments_where( $where = '' ) { + global $wpdb; + + $start_where = ''; + $end_where = ''; + + if ( ! is_wp_error( $this->start_date ) ) { + + if ( $this->timestamp ) { + $format = 'Y-m-d H:i:s'; + } else { + $format = 'Y-m-d 00:00:00'; + } + + $start_date = date( $format, $this->start_date ); + $start_where = " AND {$wpdb->posts}.post_date >= '{$start_date}'"; + } + + if ( ! is_wp_error( $this->end_date ) ) { + + if ( $this->timestamp ) { + $format = 'Y-m-d H:i:s'; + } else { + $format = 'Y-m-d 23:59:59'; + } + + $end_date = date( $format, $this->end_date ); + + $end_where = " AND {$wpdb->posts}.post_date <= '{$end_date}'"; + } + + $where .= "{$start_where}{$end_where}"; + + return $where; + } +} diff --git a/includes/class-stats.php b/includes/class-stats.php new file mode 100644 index 00000000000..f0bd0373d21 --- /dev/null +++ b/includes/class-stats.php @@ -0,0 +1,3237 @@ +set_date_ranges(); + + // Maybe parse query. + if ( ! empty( $query ) ) { + $this->parse_query( $query ); + + $this->query_var_originals = $this->query_vars; + + // Set defaults. + } else { + $this->query_var_originals = $this->query_vars = array( + 'start' => '', + 'end' => '', + 'range' => '', + 'exclude_taxes' => false, + 'currency' => false, + 'currency_sql' => '', + 'status' => array(), + 'status_sql' => '', + 'type' => array(), + 'type_sql' => '', + 'where_sql' => '', + 'date_query_sql' => '', + 'date_query_column' => '', + 'column' => '', + 'table' => '', + 'function' => 'SUM', + 'output' => 'raw', + 'relative' => false, + 'relative_start' => '', + 'relative_end' => '', + 'grouped' => false, + 'product_id' => '', + 'price_id' => null, + 'revenue_type' => 'gross', + ); + } + + } + + /** + * Builds a fully qualified amount column and function, given the currency settings, + * tax settings, and accepted functions. + * + * @param array $args { + * Optional arguments. + * + * @type string $column_prefix Column prefix (table alias or name). + * @type array $accepted_function Accepted functions for this query. + * } + * + * @return string Example: `SUM( total / rate )` + * @throws \InvalidArgumentException + */ + private function get_amount_column_and_function( $args = array() ) { + $args = wp_parse_args( $args, array( + 'column_prefix' => '', + 'accepted_functions' => array(), + 'requested_function' => false, + 'rate' => true, + ) ); + + $column = $this->query_vars['column']; + $column_prefix = ''; + + if ( ! empty( $args['column_prefix'] ) ) { + $column_prefix = $args['column_prefix'] . '.'; + } + + if ( empty( $column ) ) { + $column = true === $this->query_vars['exclude_taxes'] ? "{$column_prefix}total - {$column_prefix}tax" : $column_prefix . 'total'; + } else { + $column = $column_prefix . $column; + } + + $default_function = is_array( $args['accepted_functions'] ) && isset( $args['accepted_functions'][0] ) ? $args['accepted_functions'][0] : false; + $function = ! empty( $this->query_vars['function'] ) ? $this->query_vars['function'] : $default_function; + + if ( ! empty( $args['requested_function'] ) ) { + $function = $args['requested_function']; + } + + if ( empty( $function ) ) { + throw new \InvalidArgumentException( 'Missing select function.' ); + } + + if ( ! empty( $args['accepted_functions'] ) && ! in_array( strtoupper( $function ), $args['accepted_functions'], true ) ) { + if ( ! empty( $default_function ) ) { + $function = $default_function; + } else { + throw new \InvalidArgumentException( sprintf( 'Invalid function "%s". Must be one of: %s', $this->query_vars['function'], json_encode( $args['accepted_functions'] ) ) ); + } + } + + $function = strtoupper( $function ); + + // Multiply by rate if currency conversion is enabled. + if ( + ! empty( $args['rate'] ) && + in_array( $function, array( 'SUM', 'AVG' ), true ) && + ( empty( $this->query_vars['currency'] ) || 'convert' === $this->query_vars['currency'] ) && + ( false !== strpos( $column, 'total' ) || false !== strpos( $column, 'tax' ) ) + ) { + $column = sprintf( '(%s) / %s', $column, $column_prefix . 'rate' ); + } + + return sprintf( '%s(%s)', $function, $column ); + } + + /** Calculation Methods ***************************************************/ + + /** Orders ***************************************************************/ + + /** + * Calculate order earnings. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function SQL function. Accepts `SUM` and `AVG`. Default `SUM`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Formatted order earnings. + */ + public function get_order_earnings( $query = array() ) { + $this->parse_query( $query ); + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['date_query_column'] = 'date_created'; + + if ( empty( $this->query_vars['function'] ) ) { + $this->query_vars['function'] = 'SUM'; + } + + /* + * By default we're checking sales only and excluding refunds. This gives us gross order earnings. + * This may be overridden in $query parameters that get passed through. + */ + $this->query_vars['type'] = $this->get_revenue_type_order_types(); + $this->query_vars['status'] = edd_get_gross_order_statuses(); + + /** + * Filters Order statuses that should be included when calculating stats. + * + * @since 2.7 + * + * @param array $statuses Order statuses to include when generating stats. + */ + $this->query_vars['status'] = array_unique( apply_filters( 'edd_payment_stats_post_statuses', $this->query_vars['status'] ) ); + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'SUM', 'AVG' ) + ) ); + + $initial_query = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 + {$this->query_vars['status_sql']} + {$this->query_vars['type_sql']} + {$this->query_vars['currency_sql']} + {$this->query_vars['where_sql']} + {$this->query_vars['date_query_sql']}"; + + $initial_result = $this->get_db()->get_row( $initial_query ); + + if ( true === $this->query_vars['relative'] ) { + + $relative_date_query_sql = $this->generate_relative_date_query_sql(); + + $relative_query = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 + {$this->query_vars['status_sql']} + {$this->query_vars['type_sql']} + {$this->query_vars['currency_sql']} + {$this->query_vars['where_sql']} + {$relative_date_query_sql}"; + + + $relative_result = $this->get_db()->get_row( $relative_query ); + } + + $total = null === $initial_result->total + ? 0.00 + : (float) $initial_result->total; + + if ( 'array' === $this->query_vars['output'] ) { + $output = array( + 'value' => $total, + 'relative_data' => ( true === $this->query_vars['relative'] ) ? $this->generate_relative_data( floatval( $total ), floatval( $relative_result->total ) ) : array(), + ); + } else { + if ( true === $this->query_vars['relative'] ) { + $output = $this->generate_relative_markup( floatval( $total ), floatval( $relative_result->total ) ); + } else { + $output = $this->maybe_format( $total ); + } + } + + // Reset query vars. + $this->post_query(); + + return $output; + } + + /** + * Calculate the number of orders. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT` and `AVG`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Number of orders. + */ + public function get_order_count( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + /* + * By default we're checking sales only and excluding refunds. This gives us gross order counts. + * This may be overridden in $query parameters that get passed through. + */ + $this->query_vars['type'] = 'sale'; + $this->query_vars['status'] = $this->get_revenue_type_statuses(); + + /** + * Filters Order statuses that should be included when calculating stats. + * + * @since 2.7 + * + * @param array $statuses Order statuses to include when generating stats. + */ + $this->query_vars['status'] = apply_filters( 'edd_payment_stats_post_statuses', $this->query_vars['status'] ); + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'COUNT', 'AVG' ) + ) ); + + // First get the 'current' date filter's results. + $initial_query = "SELECT COUNT(id) AS total + FROM {$this->query_vars['table']} + WHERE 1=1 + {$this->query_vars['status_sql']} + {$this->query_vars['type_sql']} + {$this->query_vars['currency_sql']} + {$this->query_vars['where_sql']} + {$this->query_vars['date_query_sql']}"; + + $initial_result = $this->get_db()->get_row( $initial_query ); + + if ( true === $this->query_vars['relative'] ) { + + // Now get the relative data. + $relative_date_query_sql = $this->generate_relative_date_query_sql(); + + $relative_query = "SELECT COUNT(id) AS total + FROM {$this->query_vars['table']} + WHERE 1=1 + {$this->query_vars['status_sql']} + {$this->query_vars['type_sql']} + {$this->query_vars['currency_sql']} + {$this->query_vars['where_sql']} + {$relative_date_query_sql}"; + + $relative_result = $this->get_db()->get_row( $relative_query ); + } + + $total = null === $initial_result + ? 0 + : absint( $initial_result->total ); + + if ( true === $this->query_vars['relative'] ) { + $total = $this->generate_relative_markup( absint( $total ), absint( $relative_result->total ) ); + } + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Calculate the busiest day of the week for stores. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Busiest day of the week. + */ + public function get_busiest_day( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $sql = "SELECT DAYOFWEEK(date_created) AS day, COUNT({$this->query_vars['column']}) as total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY day + ORDER BY total DESC + LIMIT 1"; + + $result = $this->get_db()->get_row( $sql ); + + $days = array( + __( 'Sunday', 'easy-digital-downloads' ), + __( 'Monday', 'easy-digital-downloads' ), + __( 'Tuesday', 'easy-digital-downloads' ), + __( 'Wednesday', 'easy-digital-downloads' ), + __( 'Thursday', 'easy-digital-downloads' ), + __( 'Friday', 'easy-digital-downloads' ), + __( 'Saturday', 'easy-digital-downloads' ), + ); + + $day = null === $result + ? '' + : $days[ $result->day - 1 ]; + + // Reset query vars. + $this->post_query(); + + return $day; + } + + /** + * Calculate number of refunded orders. + * + * @since 3.0 + * + * @see \EDD\Stats::get_order_count() + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT` and `AVG`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Number of refunded orders. + */ + public function get_order_refund_count( $query = array() ) { + $query['status'] = isset( $query['status'] ) + ? $query['status'] + : array( 'complete' ); + + if ( ! array( $query['status'] ) ) { + $query['status'] = array( $query['status'] ); + } + + $query['type'] = array( 'refund' ); + + return $this->get_order_count( $query ); + } + + /** + * Calculate number of refunded order items. + * + * @since 3.0 + * + * @see \EDD\Stats::get_order_item_count() + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT` and `AVG`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Number of refunded orders. + */ + public function get_order_item_refund_count( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_items; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + if ( empty( $this->query_vars['function'] ) ) { + $this->query_vars['function'] = 'COUNT'; + } + + // Base value for status. + $query['status'] = isset( $query['status'] ) + ? $query['status'] + : array( 'refunded' ); + + /* + * The type should be `sale` because we're querying for fully refunded order items only. + * That means we look in `type` = `sale` and `status` = `refunded`. + */ + $this->query_vars['where_sql'] .= " AND {$this->get_db()->edd_orders}.type = 'sale' "; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'column_prefix' => $this->query_vars['table'], + 'accepted_functions' => array( 'COUNT', 'AVG' ) + ) ); + + $product_id = ! empty( $this->query_vars['product_id'] ) + ? $this->get_db()->prepare( 'AND product_id = %d', absint( $this->query_vars['product_id'] ) ) + : ''; + + $price_id = $this->generate_price_id_query_sql(); + + $currency_sql = str_replace( $this->get_db()->edd_order_items, $this->get_db()->edd_orders, $this->query_vars['currency_sql'] ); + + // Calculating an average requires a subquery. + if ( 'AVG' === $this->query_vars['function'] ) { + $sql = "SELECT AVG(id) AS total + FROM ( + SELECT COUNT({$this->query_vars['table']}.id) AS id + FROM {$this->query_vars['table']} + INNER JOIN {$this->get_db()->edd_orders} ON( {$this->get_db()->edd_orders}.id = {$this->query_vars['table']}.order_id ) + WHERE 1=1 {$product_id} {$price_id} {$this->query_vars['status_sql']} {$currency_sql} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY order_id + ) AS counts"; + } elseif ( true === $this->query_vars['grouped'] ) { + $sql = "SELECT {$this->query_vars['table']}.product_id, {$this->query_vars['table']}.price_id, {$function} AS total + FROM {$this->query_vars['table']} + INNER JOIN {$this->get_db()->edd_orders} ON( {$this->get_db()->edd_orders}.id = {$this->query_vars['table']}.order_id ) + WHERE 1=1 {$product_id} {$price_id} {$this->query_vars['status_sql']} {$currency_sql} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY product_id, price_id + ORDER BY total DESC"; + } else { + $sql = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + INNER JOIN {$this->get_db()->edd_orders} ON( {$this->get_db()->edd_orders}.id = {$this->query_vars['table']}.order_id ) + WHERE 1=1 {$product_id} {$price_id} {$this->query_vars['status_sql']} {$currency_sql} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } + + $result = $this->get_db()->get_results( $sql ); + + if ( true === $this->query_vars['grouped'] ) { + array_walk( $result, function ( &$value ) { + + // Format resultant object. + $value->product_id = absint( $value->product_id ); + $value->price_id = is_numeric( $value->price_id ) ? absint( $value->price_id ) : null; + $value->total = absint( $value->total ); + + // Add instance of EDD_Download to resultant object. + $value->object = edd_get_download( $value->product_id ); + } ); + } else { + $result = null === $result[0]->total + ? 0.00 + : absint( $result[0]->total ); + } + + // Reset query vars. + $this->post_query(); + + return $result; + + return $this->get_order_item_count( $query ); + } + + /** + * Calculate total amount for refunded orders. + * + * @since 3.0 + * + * @see \EDD\Stats::get_order_earnings() + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function SQL function. Default `SUM`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Formatted amount from refunded orders. + */ + public function get_order_refund_amount( $query = array() ) { + $this->parse_query( $query ); + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['date_query_column'] = 'date_created'; + + if ( empty( $this->query_vars['function'] ) ) { + $this->query_vars['function'] = 'SUM'; + } + + /* + * By default we're checking refunds only and excluding any other types. This gives us gross refund amounts. + * This may be overridden in $query parameters that get passed through. + */ + $this->query_vars['type'] = 'refund'; + $this->query_vars['status'] = array( 'complete' ); + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'SUM', 'AVG' ) + ) ); + + $initial_query = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 + {$this->query_vars['status_sql']} + {$this->query_vars['type_sql']} + {$this->query_vars['currency_sql']} + {$this->query_vars['where_sql']} + {$this->query_vars['date_query_sql']}"; + + $initial_result = $this->get_db()->get_row( $initial_query ); + + if ( true === $this->query_vars['relative'] ) { + + $relative_date_query_sql = $this->generate_relative_date_query_sql(); + + $relative_query = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 + {$this->query_vars['status_sql']} + {$this->query_vars['type_sql']} + {$this->query_vars['currency_sql']} + {$this->query_vars['where_sql']} + {$relative_date_query_sql}"; + + $relative_result = $this->get_db()->get_row( $relative_query ); + } + + $total = null === $initial_result->total + ? 0.00 + : (float) $initial_result->total; + + if ( true === $this->query_vars['relative'] ) { + $total = -( floatval( $initial_result->total ) ); + $relative = -( floatval( $relative_result->total ) ); + $total = $this->generate_relative_markup( $total, $relative, true ); + } else { + $total = $this->maybe_format( -( $total ) ); + } + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Calculate average time for an order to be refunded. + * + * @since 3.0 + * + * @see \EDD\Stats::get_order_earnings() + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `AVG` only. Default `AVG`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Average time for an order to be refunded in human readable format. + */ + public function get_average_refund_time( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'date_completed'; + $this->query_vars['date_query_column'] = 'date_created'; + + $type_sql = $this->get_db()->prepare( 'AND o2.type = %s', esc_sql( 'refund' ) ); + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $sql = "SELECT AVG( TIMESTAMPDIFF( SECOND, {$this->query_vars['table']}.{$this->query_vars['column']}, o2.date_created ) ) AS time_to_refund + FROM {$this->query_vars['table']} + INNER JOIN {$this->query_vars['table']} o2 ON {$this->query_vars['table']}.id = o2.parent + WHERE 1=1 {$type_sql} {$this->query_vars['currency_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_var( $sql ); + + $time_to_refund = null === $result + ? '' + : $result; + + // Beginning of time. + $base = strtotime( '1970-01-01 00:00:00' ); + + if ( ! empty( $time_to_refund ) ) { + $time_to_refund = absint( $time_to_refund ); + + $intervals = array( 'year', 'month', 'day', 'hour', 'minute', 'second' ); + $diffs = array(); + + foreach ( $intervals as $interval ) { + $time = strtotime( '+1 ' . $interval, $base ); + + $add = 1; + $looped = 0; + + while ( $time_to_refund >= $time ) { + $add++; + $time = strtotime( '+' . $add . ' ' . $interval, $base ); + $looped++; + } + + $base = strtotime( '+' . $looped . ' ' . $interval, $base ); + $diffs[ $interval ] = $looped; + } + + $count = 0; + $times = array(); + + foreach ( $diffs as $interval => $value ) { + + // Keep precision to 2. + if ( $count >= 2 ) { + break; + } + + // Add value and interval if value is bigger than 0. + if ( $value > 0 ) { + $interval = substr( $interval, 0, 1 ); + + // Add value and interval to times array. + $times[] = $value . $interval; + $count ++; + } + } + } + + // Reset query vars. + $this->post_query(); + + return empty( $time_to_refund ) + ? '' + : implode( ' ', $times ); + } + + /** + * Calculate refund rate. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return float|int Rate of refunded orders. + */ + public function get_refund_rate( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $status_sql = $this->get_db()->prepare( "AND status = %s AND type = '%s'", esc_sql( 'complete' ), esc_sql( 'refund' ) ); + + $ignore_free = $this->get_db()->prepare( "AND {$this->query_vars['table']}.total > %d", 0 ); + + $sql = "SELECT COUNT(id ) / o.number_orders * 100 AS `refund_rate` + FROM {$this->query_vars['table']} + CROSS JOIN ( + SELECT COUNT(id) AS number_orders + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$ignore_free} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + ) o + WHERE 1=1 {$status_sql} {$this->query_vars['currency_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_var( $sql ); + + $total = null === $result + ? 0 + : round( $result, 2 ); + + if ( 'formatted' === $this->query_vars['output'] ) { + $total .= '%'; + $total = esc_html( $total ); + } + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** Order Items **********************************************************/ + + /** + * Calculate order item earnings. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function SQL function. Default `SUM`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type int $product_id Product ID. If empty, an aggregation of the values in the `total` column in the + * `edd_order_items` table will be returned. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return array|float|int Formatted order item earnings. + */ + public function get_order_item_earnings( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_items; + $this->query_vars['column'] = true === $this->query_vars['exclude_taxes'] ? 'total - tax' : 'total'; + $this->query_vars['date_query_column'] = 'date_created'; + $this->query_vars['status'] = edd_get_gross_order_statuses(); + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $product_id = ! empty( $this->query_vars['product_id'] ) + ? $this->get_db()->prepare( "AND {$this->query_vars['table']}.product_id = %d", absint( $this->query_vars['product_id'] ) ) + : ''; + + $price_id = $this->generate_price_id_query_sql(); + + $region = ! empty( $this->query_vars['region'] ) + ? $this->get_db()->prepare( 'AND edd_oa.region = %s', esc_sql( $this->query_vars['region'] ) ) + : ''; + + $country = ! empty( $this->query_vars['country'] ) + ? $this->get_db()->prepare( 'AND edd_oa.country = %s', esc_sql( $this->query_vars['country'] ) ) + : ''; + + $status = ! empty( $this->query_vars['status'] ) + ? " AND {$this->query_vars['table']}.status IN ('" . implode( "', '", $this->query_vars['status'] ) . "')" + : ''; + + $join = $currency = ''; + if ( ! empty( $country ) || ! empty( $region ) ) { + $join .= " INNER JOIN {$this->get_db()->edd_order_addresses} edd_oa ON {$this->query_vars['table']}.order_id = edd_oa.order_id "; + } + + $join .= " INNER JOIN {$this->get_db()->edd_orders} edd_o ON ({$this->query_vars['table']}.order_id = edd_o.id) AND edd_o.status IN ('" . implode( "', '", $this->query_vars['status'] ) . "') "; + + if ( ! empty( $this->query_vars['currency'] ) && array_key_exists( strtoupper( $this->query_vars['currency'] ), edd_get_currencies() ) ) { + $currency = $this->get_db()->prepare( "AND edd_o.currency = %s", strtoupper( $this->query_vars['currency'] ) ); + } + + /** + * The adjustments query needs a different order status check than the order items. This is due to the fact that + * adjustments refunded would end up being double counted, and therefore create an inaccurate revenue report. + */ + $adjustments_join = " INNER JOIN {$this->get_db()->edd_orders} edd_o ON ({$this->query_vars['table']}.order_id = edd_o.id) AND edd_o.type = 'sale' AND edd_o.status IN ('" . implode( "', '", edd_get_net_order_statuses() ) . "') "; + + /** + * With the addition of including fees into the calcualtion, the order_items + * and order_adjustments for the order items needs to be a SUM and then the final function + * (SUM or AVG) needs to be run on the final UNION Query. + */ + $order_item_function = $this->get_amount_column_and_function( array( + 'column_prefix' => $this->query_vars['table'], + 'accepted_functions' => array( 'SUM', 'AVG' ), + 'requested_function' => 'SUM', + ) ); + + $order_adjustment_function = $this->get_amount_column_and_function( array( + 'column_prefix' => 'oadj', + 'accepted_functions' => array( 'SUM', 'AVG' ), + 'requested_function' => 'SUM', + ) ); + + $union_function = $this->get_amount_column_and_function( array( + 'column_prefix' => '', + 'accepted_functions' => array( 'SUM', 'AVG' ), + 'rate' => false, + ) ); + + if ( true === $this->query_vars['grouped'] ) { + $order_items = "SELECT + {$this->query_vars['table']}.product_id, + {$this->query_vars['table']}.price_id, + {$order_item_function} AS total + FROM {$this->query_vars['table']} + {$join} + WHERE 1=1 + {$product_id} + {$price_id} + {$region} + {$country} + {$currency} + {$this->query_vars['where_sql']} + {$this->query_vars['date_query_sql']} + GROUP BY {$this->query_vars['table']}.product_id, {$this->query_vars['table']}.price_id"; + + $order_adjustments = "SELECT + {$this->query_vars['table']}.product_id as product_id, + {$this->query_vars['table']}.price_id as price_id, + {$order_adjustment_function} as total + FROM {$this->get_db()->edd_order_adjustments} oadj + INNER JOIN {$this->query_vars['table']} ON + ({$this->query_vars['table']}.id = oadj.object_id) + {$product_id} + {$price_id} + {$region} + {$country} + {$currency} + {$adjustments_join} + WHERE oadj.object_type = 'order_item' + AND oadj.type != 'discount' + {$this->query_vars['date_query_sql']} + GROUP BY {$this->query_vars['table']}.product_id, {$this->query_vars['table']}.price_id"; + + $sql = "SELECT product_id, price_id, {$union_function} AS total + FROM ({$order_items} UNION {$order_adjustments})a + GROUP BY product_id, price_id + ORDER BY total DESC"; + } else { + $order_items = "SELECT + {$order_item_function} AS total + FROM {$this->query_vars['table']} + {$join} + WHERE 1=1 + {$product_id} + {$price_id} + {$region} + {$country} + {$currency} + {$this->query_vars['where_sql']} + {$this->query_vars['date_query_sql']}"; + + $order_adjustments = "SELECT + {$order_adjustment_function} as total + FROM {$this->get_db()->edd_order_adjustments} oadj + INNER JOIN {$this->query_vars['table']} ON + ({$this->query_vars['table']}.id = oadj.object_id) + {$product_id} + {$price_id} + {$region} + {$country} + {$currency} + {$adjustments_join} + WHERE oadj.object_type = 'order_item' + AND oadj.type != 'discount' + {$this->query_vars['date_query_sql']}"; + + $sql = "SELECT {$union_function} AS total FROM ({$order_items} UNION {$order_adjustments})a"; + } + + $result = $this->get_db()->get_results( $sql ); + + if ( true === $this->query_vars['grouped'] ) { + array_walk( $result, function ( &$value ) { + + // Format resultant object. + $value->product_id = absint( $value->product_id ); + $value->price_id = is_numeric( $value->price_id ) ? absint( $value->price_id ) : null; + $value->total = $this->maybe_format( $value->total ); + + // Add instance of EDD_Download to resultant object. + $value->object = edd_get_download( $value->product_id ); + } ); + } else { + $result = null === $result[0]->total + ? $this->maybe_format( 0.00 ) + : $this->maybe_format( floatval( $result[0]->total ) ); + } + + // Reset query vars. + $this->post_query(); + + return $result; + } + + /** + * Calculate the number of times a specific item has been purchased. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT` and `AVG`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type int $product_id Product ID. If empty, an aggregation of the values in the `total` column in the + * `edd_order_items` table will be returned. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return array|int Number of times a specific item has been purchased. + */ + public function get_order_item_count( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_items; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + $this->query_vars['status'] = array( 'complete', 'partially_refunded' ); + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'column_prefix' => $this->query_vars['table'], + 'accepted_functions' => array( 'COUNT', 'AVG' ), + ) ); + + $product_id = ! empty( $this->query_vars['product_id'] ) + ? $this->get_db()->prepare( 'AND product_id = %d', absint( $this->query_vars['product_id'] ) ) + : ''; + + $price_id = $this->generate_price_id_query_sql(); + + $region = ! empty( $this->query_vars['region'] ) + ? $this->get_db()->prepare( 'AND edd_oa.region = %s', esc_sql( $this->query_vars['region'] ) ) + : ''; + + $country = ! empty( $this->query_vars['country'] ) + ? $this->get_db()->prepare( 'AND edd_oa.country = %s', esc_sql( $this->query_vars['country'] ) ) + : ''; + + $statuses = edd_get_net_order_statuses(); + $status_string = $this->get_placeholder_string( $statuses ); + + $join = $this->get_db()->prepare( + "INNER JOIN {$this->get_db()->edd_orders} edd_o ON ({$this->query_vars['table']}.order_id = edd_o.id) AND edd_o.status IN({$status_string}) AND edd_o.type = 'sale' ", + ...$statuses + ); + + $currency = ''; + if ( ! empty( $country ) || ! empty( $region ) ) { + $join .= " INNER JOIN {$this->get_db()->edd_order_addresses} edd_oa ON {$this->query_vars['table']}.order_id = edd_oa.order_id "; + } + if ( ! empty( $this->query_vars['currency'] ) && array_key_exists( strtoupper( $this->query_vars['currency'] ), edd_get_currencies() ) ) { + $currency = $this->get_db()->prepare( "AND edd_o.currency = %s", strtoupper( $this->query_vars['currency'] ) ); + } + + // Calculating an average requires a subquery. + if ( 'AVG' === $this->query_vars['function'] ) { + $sql = "SELECT AVG(id) AS total + FROM ( + SELECT COUNT({$this->query_vars['table']}.id) AS id + FROM {$this->query_vars['table']} + {$join} + WHERE 1=1 {$product_id} {$price_id} {$region} {$country} {$currency} {$this->query_vars['status_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY order_id + ) AS counts"; + } elseif ( true === $this->query_vars['grouped'] ) { + $sql = "SELECT product_id, price_id, {$function} AS total + FROM {$this->query_vars['table']} + {$join} + WHERE 1=1 {$product_id} {$price_id} {$region} {$country} {$currency} {$this->query_vars['status_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY product_id, price_id + ORDER BY total DESC"; + } else { + $sql = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + {$join} + WHERE 1=1 {$product_id} {$price_id} {$region} {$country} {$currency} {$this->query_vars['status_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } + + $result = $this->get_db()->get_results( $sql ); + + if ( true === $this->query_vars['grouped'] ) { + array_walk( $result, function ( &$value ) { + + // Format resultant object. + $value->product_id = absint( $value->product_id ); + $value->price_id = is_numeric( $value->price_id ) ? absint( $value->price_id ) : null; + $value->total = absint( $value->total ); + + // Add instance of EDD_Download to resultant object. + $value->object = edd_get_download( $value->product_id ); + } ); + } else { + $result = null === $result[0]->total + ? 0 + : absint( $result[0]->total ); + } + + // Reset query vars. + $this->post_query(); + + return $result; + } + + /** + * Calculate most valuable order items. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended to the + * query. + * @type int $number Number of order items to fetch. Default 1. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return array Array of objects with most valuable order items. Each object has the product ID, total earnings, + * and an instance of EDD_Download. + */ + public function get_most_valuable_order_items( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_items; + $this->query_vars['date_query_column'] = 'date_created'; + $this->query_vars['exclude_taxes'] = true; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + // By default, the most valuable customer is returned. + $number = isset( $this->query_vars['number'] ) + ? absint( $this->query_vars['number'] ) + : 1; + + $function = $this->get_amount_column_and_function( array( + 'column_prefix' => $this->query_vars['table'], + 'accepted_functions' => array( 'SUM' ) + ) ); + + $statuses = edd_get_net_order_statuses(); + $status_string = $this->get_placeholder_string( $statuses ); + + $where = $this->get_db()->prepare( + "AND {$this->get_db()->edd_order_items}.status IN('complete','partially_refunded') + AND {$this->get_db()->edd_orders}.status IN({$status_string}) ", + ...$statuses + ); + if ( ! empty( $this->query_vars['currency'] ) && array_key_exists( strtoupper( $this->query_vars['currency'] ), edd_get_currencies() ) ) { + $where .= $this->get_db()->prepare( + " AND {$this->get_db()->edd_orders}.currency = %s ", + strtoupper( $this->query_vars['currency'] ) + ); + } + + $sql = "SELECT product_id, price_id, {$function} AS total + FROM {$this->query_vars['table']} + INNER JOIN {$this->get_db()->edd_orders} ON({$this->get_db()->edd_orders}.id = {$this->query_vars['table']}.order_id) + WHERE 1=1 {$where} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY product_id, price_id + ORDER BY total DESC + LIMIT {$number}"; + + $result = $this->get_db()->get_results( $sql ); + + array_walk( $result, function ( &$value ) { + + // Format resultant object. + $value->product_id = absint( $value->product_id ); + $value->price_id = is_numeric( $value->price_id ) ? absint( $value->price_id ) : null; + $download_model = new \EDD\Models\Download( + $value->product_id, + $value->price_id, + array( + 'start' => $this->query_vars['start'], + 'end' => $this->query_vars['end'], + ) + ); + + $value->sales = absint( $download_model->get_net_sales() ); + $value->total = $this->maybe_format($download_model->get_net_earnings() ); + + // Add instance of EDD_Download to resultant object. + $value->object = edd_get_download( $value->product_id ); + } ); + + // Reset query vars. + $this->post_query(); + + return $result; + } + + /** Discounts ************************************************************/ + + /** + * Calculate the usage count of discount codes. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $discount_code Discount code to fetch the usage count for. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Number of times a discount code has been used. + */ + public function get_discount_usage_count( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_adjustments; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $discount_code = isset( $this->query_vars['discount_code'] ) + ? $this->get_db()->prepare( 'AND type = %s AND description = %s', 'discount', sanitize_text_field( $this->query_vars['discount_code'] ) ) + : $this->get_db()->prepare( 'AND type = %s', 'discount' ); + + $sql = "SELECT COUNT({$this->query_vars['column']}) + FROM {$this->query_vars['table']} + WHERE 1=1 {$discount_code} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_var( $sql ); + + $total = null === $result + ? 0 + : absint( $result ); + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Retrieve the most popular discount code. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $discount_code Discount code to fetch the usage count for. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return array Most popular discounts with usage count. + */ + public function get_most_popular_discounts( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_adjustments; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + // By default, the most valuable discount is returned. + $number = isset( $this->query_vars['number'] ) + ? absint( $this->query_vars['number'] ) + : 1; + + $discount = $this->get_db()->prepare( 'AND type = %s', 'discount' ); + + $sql = "SELECT description AS code, COUNT({$this->query_vars['column']}) AS count + FROM {$this->query_vars['table']} + WHERE 1=1 {$discount} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY description + ORDER BY count DESC + LIMIT {$number}"; + + $result = $this->get_db()->get_results( $sql ); + + array_walk( $result, function ( &$value ) { + + // Add instance of EDD_Discount to resultant object. + $value->object = edd_get_discount_by_code( $value->code ); + + // Format resultant object. + if ( ! empty( $value->object ) ) { + $value->discount_id = absint( $value->object->id ); + $value->count = absint( $value->count ); + } else { + $value->discount_id = 0; + $value->count = '—'; + } + } ); + + // Reset query vars. + $this->post_query(); + + return $result; + } + + /** + * Calculate the savings from using a discount code. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $discount_code Discount code to fetch the savings amount for. Default empty. If empty, the amount + * saved from using any discount will be returned. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return float Savings from using a discount code. + */ + public function get_discount_savings( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_adjustments; + $this->query_vars['column'] = true === $this->query_vars['exclude_taxes'] ? 'total - tax' : 'total'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'SUM' ) + ) ); + + $discount_code = ! empty( $this->query_vars['discount_code'] ) + ? $this->get_db()->prepare( 'AND type = %s AND description = %s', 'discount', sanitize_text_field( $this->query_vars['discount_code'] ) ) + : $this->get_db()->prepare( 'AND type = %s', 'discount' ); + + $sql = "SELECT {$function} + FROM {$this->query_vars['table']} + WHERE 1=1 {$discount_code} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_var( $sql ); + + $total = null === $result + ? 0.00 + : floatval( $result ); + + $total = $this->maybe_format( $total ); + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Calculate the average discount amount applied to an order. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return float Average discount amount applied to an order. + */ + public function get_average_discount_amount( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_order_adjustments; + $this->query_vars['column'] = 'total'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'AVG' ) + ) ); + + $type_discount = $this->get_db()->prepare( 'AND type = %s', 'discount' ); + + $sql = "SELECT {$function} + FROM {$this->query_vars['table']} + WHERE 1=1 {$type_discount} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_var( $sql ); + + $total = null === $result + ? 0.00 + : floatval( $result ); + + $total = $this->maybe_format( $total ); + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Calculate the ratio of discounted to non-discounted orders. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Ratio of discounted to non-discounted orders. Format is A:B where A and B are integers. + */ + public function get_ratio_of_discounted_orders( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $sql = "SELECT COUNT(id) AS total, o.discounted_orders + FROM {$this->query_vars['table']} + CROSS JOIN ( + SELECT COUNT(id) AS discounted_orders + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} AND discount > 0 {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + ) o + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_row( $sql ); + + // No need to calculate the ratio if there are no orders. + if ( 0 === (int) $result->discounted_orders || 0 === (int) $result->total ) { + return 0; + } + + // Calculate GCD. + $result->total = absint( $result->total ); + $result->discounted_orders = absint( $result->discounted_orders ); + + $original_result = clone $result; + + while ( 0 !== $result->total ) { + $remainder = $result->discounted_orders % $result->total; + $result->discounted_orders = $result->total; + $result->total = $remainder; + } + + $ratio = absint( $result->discounted_orders ); + + // Reset query vars. + $this->post_query(); + + // Return the formatted ratio. + return ( $original_result->discounted_orders / $ratio ) . ':' . ( $original_result->total / $ratio ); + } + + /** Gateways *************************************************************/ + + /** + * Perform gateway calculations based on data passed. + * + * @internal This method must remain `private`, it exists to reduce duplicated code. + * + * @since 3.0 + * @access private + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT`, `AVG`, and `SUM`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $gateway Gateway name. This is checked against a list of registered payment gateways. + * If a gateway is not passed, a list of objects are returned for each gateway and the + * number of orders processed with that gateway. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return mixed array|int|float Either a list of payment gateways and counts or just a single value. + */ + private function get_gateway_data( $query = array() ) { + $query = wp_parse_args( $query, array( + 'type' => 'sale', + 'status' => edd_get_gross_order_statuses(), + ) ); + + $this->parse_query( $query ); + + // Set up default values. + $gateways = edd_get_payment_gateways(); + $defaults = array(); + + // Set up an object for each gateway. + foreach ( $gateways as $id => $data ) { + $object = new \stdClass(); + $object->gateway = $id; + $object->total = 0; + + $defaults[] = $object; + } + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'COUNT', 'AVG', 'SUM' ) + ) ); + + $gateway = ! empty( $this->query_vars['gateway'] ) + ? $this->get_db()->prepare( 'AND gateway = %s', sanitize_text_field( $this->query_vars['gateway'] ) ) + : ''; + + $sql = "SELECT gateway, {$function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['type_sql']} {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$gateway} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY gateway"; + + $result = $this->get_db()->get_results( $sql ); + + // Ensure count values are always valid integers if counting sales. + if ( 'COUNT' === $this->query_vars['function'] ) { + array_walk( $result, function ( &$value ) { + $value->total = absint( $value->total ); + } ); + } elseif ( 'SUM' === $this->query_vars['function'] || 'AVG' === $this->query_vars['function'] ) { + array_walk( $result, function ( &$value ) { + $value->total = floatval( abs( $value->total ) ); + } ); + } + + if ( empty( $gateway ) && true === $this->query_vars['grouped'] ) { + $results = array(); + + // Merge defaults with values returned from the database. + foreach ( $defaults as $key => $value ) { + + // Filter based on gateway. + $filter = wp_filter_object_list( $result, array( 'gateway' => $value->gateway ) ); + + $filter = ! empty( $filter ) + ? array_values( $filter ) + : array(); + + if ( ! empty( $filter ) ) { + $results[] = $filter[0]; + } else { + $results[] = $defaults[ $key ]; + } + } + } elseif ( false === $this->query_vars['grouped'] ) { + $total = 0; + + array_walk( $result, function( $value ) use ( &$total ) { + $total += $value->total; + } ); + + $results = 'COUNT' === $this->query_vars['function'] + ? absint( $total ) + : $this->maybe_format( $total ); + } + + if ( ! empty( $gateway ) && true === $this->query_vars['grouped'] ) { + + // Filter based on gateway if passed. + $filter = wp_filter_object_list( $result, array( 'gateway' => $this->query_vars['gateway'] ) ); + + $results = 'COUNT' === $this->query_vars['function'] + ? absint( $filter[0]->total ) + : $this->maybe_format( $filter[0]->total ); + } + + // Reset query vars. + $this->post_query(); + + // Return array of objects with gateway name and count. + return $results; + } + + /** + * Calculate the number of processed by a gateway. + * + * @since 3.0 + * + * @see \EDD\Stats::get_gateway_data() + * + * @param array $query See \EDD\Stats::get_gateway_data(). + * + * @return int|array List of objects containing the number of sales processed either for every gateway or the gateway + * passed as a query parameter. + */ + public function get_gateway_sales( $query = array() ) { + + $query['column'] = 'id'; + $query['function'] = 'COUNT'; + + // Dispatch to \EDD\Stats::get_gateway_data(). + return $this->get_gateway_data( $query ); + } + + /** + * Calculate the total order amount of processed by a gateway. + * + * @since 3.0 + * + * @see \EDD\Stats::get_gateway_data() + * + * @param array $query See \EDD\Stats::get_gateway_data(). + * + * @return array List of objects containing the amount processed either for every gateway or the gateway + * passed as a query parameter. + */ + public function get_gateway_earnings( $query = array() ) { + + // Summation is required as we are returning earnings. + $query['function'] = isset( $query['function'] ) + ? $query['function'] + : 'SUM'; + + // Dispatch to \EDD\Stats::get_gateway_data(). + $result = $this->get_gateway_data( $query ); + + // Rename object var. + if ( is_array( $result ) ) { + array_walk( $result, function ( &$value ) { + $value->earnings = $value->total; + $value->earnings = $this->maybe_format( $value->earnings ); + unset( $value->total ); + } ); + } else { + $result = $this->maybe_format( $result ); + } + + // Reset query vars. + $this->post_query(); + + // Return array of objects with gateway name and earnings. + return $result; + } + + /** + * Calculate the amount for refunded orders processed by a gateway. + * + * @since 3.0 + * + * @see \EDD\Stats::get_gateway_earnings() + * + * @param array $query See \EDD\Stats::get_gateway_earnings(). + * + * @return array List of objects containing the amount for refunded orders processed either for every + * gateway or the gateway passed as a query parameter. + */ + public function get_gateway_refund_amount( $query = array() ) { + + // Ensure orders are refunded. + $this->query_vars['where_sql'] = $this->get_db()->prepare( 'AND status = %s', 'refunded' ); + + // Dispatch to \EDD\Stats::get_gateway_data(). + $result = $this->get_gateway_earnings( $query ); + + // Reset query vars. + $this->post_query(); + + // Return array of objects with gateway name and amount from refunded orders. + return $result; + } + + /** + * Calculate the average order amount of processed by a gateway. + * + * @since 3.0 + * + * @see \EDD\Stats::get_gateway_data() + * + * @param array $query See \EDD\Stats::get_gateway_data(). + * + * @return array List of objects containing the average order value processed either for every gateway + * pr the gateway passed as a query parameter. + */ + public function get_gateway_average_value( $query = array() ) { + + // Function needs to be `AVG`. + $query['function'] = 'AVG'; + + // Dispatch to \EDD\Stats::get_gateway_data(). + $result = $this->get_gateway_data( $query ); + + // Rename object var. + array_walk( $result, function( &$value ) { + $value->earnings = $value->count; + $value->earnings = $this->maybe_format( $value->earnings ); + unset( $value->count ); + } ); + + // Reset query vars. + $this->post_query(); + + // Return array of objects with gateway name and earnings. + return $result; + } + + /** Tax ******************************************************************/ + + /** + * Calculate total tax collected. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `SUM` and `AVG`. Default `SUM`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Formatted amount of total tax collected. + */ + public function get_tax( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'tax'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'SUM', 'AVG' ) + ) ); + + $product_id = ! empty( $this->query_vars['download_id'] ) + ? $this->get_db()->prepare( 'AND product_id = %d', absint( $this->query_vars['download_id'] ) ) + : ''; + + $price_id = $this->generate_price_id_query_sql(); + + if ( true === $this->query_vars['relative'] ) { + $relative_date_query_sql = $this->generate_relative_date_query_sql(); + + $sql = "SELECT IFNULL({$function}, 0) AS total, IFNULL(relative, 0) AS relative + FROM {$this->query_vars['table']} + CROSS JOIN ( + SELECT IFNULL({$function}, 0) AS relative + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$this->query_vars['where_sql']} {$relative_date_query_sql} + ) o + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } elseif ( ! empty( $product_id ) || ! empty( $price_id ) ) { + + // Regenerate SQL clauses due to alias. + $table = $this->query_vars['table']; + $this->query_vars['table'] = 'o'; + $this->pre_query( $query ); + $this->query_vars['table'] = $table; + + $function = $this->get_amount_column_and_function( array( + 'column_prefix' => 'oi', + 'accepted_functions' => array( 'SUM', 'AVG' ) + ) ); + + $sql = "SELECT {$function} AS total + FROM {$this->query_vars['table']} o + INNER JOIN {$this->get_db()->edd_order_items} oi ON o.id = oi.order_id + WHERE 1=1 {$product_id} {$price_id} {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$this->query_vars['date_query_sql']}"; + + $this->pre_query( $query ); + } else { + $sql = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$this->query_vars['date_query_sql']}"; + } + + $result = $this->get_db()->get_row( $sql ); + + $total = null === $result->total + ? 0.00 + : (float) $result->total; + + if ( true === $this->query_vars['relative'] ) { + $total = floatval( $result->total ); + $relative = floatval( $result->relative ); + $total = $this->generate_relative_markup( $total, $relative ); + } else { + $total = $this->maybe_format( $total ); + } + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Calculate total tax collected for country and state passed. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $country Country name. Defaults to store's base country. + * @type string $region Region name. Defaults to store's base state. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Formatted amount of total tax collected for country and state passed. + */ + public function get_tax_by_location( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'tax'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'column_prefix' => $this->query_vars['table'], + 'accepted_functions' => array( 'SUM', 'AVG' ) + ) ); + + $region = ! empty( $this->query_vars['region'] ) + ? $this->get_db()->prepare( 'AND oa.region = %s', esc_sql( $this->query_vars['region'] ) ) + : ''; + + $country = ! empty( $this->query_vars['country'] ) + ? $this->get_db()->prepare( 'AND oa.country = %s', esc_sql( $this->query_vars['country'] ) ) + : ''; + + $product_id = ! empty( $this->query_vars['download_id'] ) + ? $this->get_db()->prepare( 'AND oi.product_id = %d', absint( $this->query_vars['download_id'] ) ) + : ''; + + $price_id = ! is_null( $this->query_vars['price_id'] ) && is_numeric( $this->query_vars['price_id'] ) + ? $this->get_db()->prepare( 'AND oi.price_id = %d', absint( $this->query_vars['price_id'] ) ) + : ''; + + $join = ! empty( $product_id ) + ? "INNER JOIN {$this->get_db()->edd_order_items} oi ON {$this->query_vars['table']}.id = oi.order_id" + : ''; + + // Re-parse function to fetch tax from the order items table. + if ( ! empty( $product_id ) && 'tax' === $this->query_vars['column'] ) { + $function = $this->get_amount_column_and_function( array( + 'column_prefix' => 'oi', + 'accepted_functions' => array( 'SUM', 'AVG' ) + ) ); + } + + $sql = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + INNER JOIN {$this->get_db()->edd_order_addresses} oa ON {$this->query_vars['table']}.id = oa.order_id + {$join} + WHERE 1=1 {$region} {$country} {$product_id} {$price_id} {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_row( $sql ); + + $total = null === $result->total + ? 0.00 + : (float) $result->total; + + $total = $this->maybe_format( $total ); + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** Customers ************************************************************/ + + /** + * Calculate the number of customers. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Number of customers. + */ + public function get_customer_count( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_customers; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $where = $this->query_vars['where_sql']; + // Allow `purchase_count` to be set to `true` to query only customers with orders. + if ( isset( $query['purchase_count'] ) && true === $query['purchase_count'] ) { + $where .= " AND {$this->query_vars['table']}.purchase_count > 0"; + } + + if ( true === $this->query_vars['relative'] ) { + $relative_date_query_sql = $this->generate_relative_date_query_sql(); + + $sql = "SELECT IFNULL(COUNT(id), 0) AS total, IFNULL(relative, 0) AS relative + FROM {$this->query_vars['table']} + CROSS JOIN ( + SELECT IFNULL(COUNT(id), 0) AS relative + FROM {$this->query_vars['table']} + WHERE 1=1 {$where} {$relative_date_query_sql} + ) o + WHERE 1=1 {$where} {$this->query_vars['date_query_sql']}"; + } else { + $sql = "SELECT COUNT(id) AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$where} {$this->query_vars['date_query_sql']}"; + } + + $result = $this->get_db()->get_row( $sql ); + + $total = null === $result->total + ? 0 + : absint( $result->total ); + + if ( 'array' === $this->query_vars['output'] ) { + $output = array( + 'value' => $total, + 'relative_data' => ( true === $this->query_vars['relative'] ) ? $this->generate_relative_data( absint( $result->total ), absint( $result->relative ) ) : array(), + ); + } else { + if ( true === $this->query_vars['relative'] ) { + $output = $this->generate_relative_markup( absint( $result->total ), absint( $result->relative ) ); + } else { + $output = $this->maybe_format( $total ); + } + } + + // Reset query vars. + $this->post_query(); + + return $output; + } + + /** + * Calculate the lifetime value of a customer. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function SQL function. Accepts `AVG` and `SUM`. Default `SUM`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type int $customer_id Customer ID. Default empty. + * @type int $user_id User ID. Default empty. + * @type string $email Email address. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return string Formatted lifetime value of a customer. + */ + public function get_customer_lifetime_value( $query = array() ) { + $this->parse_query( $query ); + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'total'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'SUM', 'AVG' ) + ) ); + + $user = isset( $this->query_vars['user_id'] ) + ? $this->get_db()->prepare( 'AND user_id = %d', absint( $this->query_vars['user_id'] ) ) + : ''; + + $customer = isset( $this->query_vars['customer'] ) + ? $this->get_db()->prepare( 'AND customer_id = %d', absint( $this->query_vars['customer'] ) ) + : ''; + + $email = isset( $this->query_vars['email'] ) + ? $this->get_db()->prepare( 'AND email = %s', absint( $this->query_vars['email'] ) ) + : ''; + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'SUM', 'AVG' ), + 'rate' => false + ) ); + + $inner_function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'SUM' ) + ) ); + + $sql = "SELECT {$function} AS total + FROM ( + SELECT {$inner_function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['currency_sql']} {$user} {$customer} {$email} {$this->query_vars['date_query_sql']} + GROUP BY customer_id + ) o"; + + $result = $this->get_db()->get_row( $sql ); + + $total = null === $result->total + ? 0.00 + : (float) $result->total; + + $total = $this->maybe_format( $total ); + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Calculate the number of orders made by a customer. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `AVG` and `SUM`. Default `SUM`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type int $customer_id Customer ID. Default empty. + * @type int $user_id User ID. Default empty. + * @type string $email Email address. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Number of orders made by a customer. + */ + public function get_customer_order_count( $query = array() ) { + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $function = $this->get_amount_column_and_function( array( + 'accepted_functions' => array( 'COUNT', 'AVG' ) + ) ); + + $user = isset( $this->query_vars['user_id'] ) + ? $this->get_db()->prepare( 'AND user_id = %d', absint( $this->query_vars['user_id'] ) ) + : ''; + + $customer = isset( $this->query_vars['customer'] ) + ? $this->get_db()->prepare( 'AND customer_id = %d', absint( $this->query_vars['customer'] ) ) + : ''; + + $email = isset( $this->query_vars['email'] ) + ? $this->get_db()->prepare( 'AND email = %s', sanitize_email( $this->query_vars['email'] ) ) + : ''; + + if ( true === $this->query_vars['relative'] ) { + $relative_date_query_sql = $this->generate_relative_date_query_sql(); + + if ( 'AVG(id)' === $function ) { + $sql = "SELECT COUNT(id) / COUNT(DISTINCT customer_id) AS total, IFNULL(relative, 0) AS relative + FROM {$this->query_vars['table']} + CROSS JOIN ( + SELECT COUNT(id) / COUNT(DISTINCT customer_id) AS relative + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$user} {$customer} {$email} {$this->query_vars['where_sql']} {$relative_date_query_sql} + ) o + WHERE 1=1 {$this->query_vars['status_sql']} {$user} {$customer} {$email} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } else { + $sql = "SELECT COUNT(id) AS total, IFNULL(relative, 0) AS relative + FROM {$this->query_vars['table']} + CROSS JOIN ( + SELECT COUNT(id), IFNULL(relative, 0) AS relative + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$user} {$customer} {$email} {$this->query_vars['where_sql']} {$relative_date_query_sql} + ) o + WHERE 1=1 {$this->query_vars['status_sql']} {$user} {$customer} {$email} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } + } else { + if ( 'AVG(id)' === $function ) { + $sql = "SELECT COUNT(id) / COUNT(DISTINCT customer_id) AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$user} {$customer} {$email} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } else { + $sql = "SELECT COUNT(id) as total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$user} {$customer} {$email} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } + } + $result = $this->get_db()->get_row( $sql ); + + $total = null === $result + ? 0 + : absint( $result->total ); + + if ( true === $this->query_vars['relative'] ) { + $total = absint( $result->total ); + $relative = absint( $result->relative ); + $total = $this->generate_relative_markup( $total, $relative ); + } else { + $total = $this->maybe_format( $total ); + } + + // Reset query vars. + $this->post_query(); + return $total; + } + + /** + * Calculate the average age of a customer. + * + * @since 3.0 + * + * @see \EDD\Stats::get_order_count() + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int|float Average age of a customer. + */ + public function get_customer_age( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_customers; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $sql = "SELECT AVG(DATEDIFF(NOW(), date_created)) + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['date_query_sql']}"; + + $result = $this->get_db()->get_var( $sql ); + + // Reset query vars. + $this->post_query(); + + return null === $result + ? 0 + : round( $result, 2 ); + } + + /** + * Calculate the most valuable customers. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type bool $exclude_taxes If taxes should be excluded from calculations. Default `false`. + * @type string $function This method does not allow any SQL functions to be passed. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type int $number Number of customers to fetch. Default 1. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return array Array of objects with most valuable customers. Each object has the customer ID, total amount spent + * by that customer and an instance of EDD_Customer. + */ + public function get_most_valuable_customers( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_orders; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + // By default, the most valuable customer is returned. + $number = isset( $this->query_vars['number'] ) + ? absint( $this->query_vars['number'] ) + : 1; + + $column = true === $this->query_vars['exclude_taxes'] + ? 'total - tax' + : 'total'; + + $sql = "SELECT customer_id, SUM({$column}) AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['status_sql']} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY customer_id + ORDER BY total DESC + LIMIT {$number}"; + + $result = $this->get_db()->get_results( $sql ); + + array_walk( $result, function ( &$value ) { + + // Format resultant object. + $value->customer_id = absint( $value->customer_id ); + $value->total = $this->maybe_format( $value->total ); + + // Add instance of EDD_Download to resultant object. + $value->object = edd_get_customer( $value->customer_id ); + } ); + + // Reset query vars. + $this->post_query(); + + return $result; + } + + /** File Downloads *******************************************************/ + + /** + * Calculate the number of file downloads. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT` and `AVG`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Number of file downloads. + */ + public function get_file_download_count( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_logs_file_downloads; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + // Only `COUNT` and `AVG` are accepted by this method. + $accepted_functions = array( 'COUNT', 'AVG' ); + + $function = isset( $this->query_vars['function'] ) && in_array( strtoupper( $this->query_vars['function'] ), $accepted_functions, true ) + ? $this->query_vars['function'] . "({$this->query_vars['column']})" + : 'COUNT(id)'; + + $product_id = ! empty( $this->query_vars['download_id'] ) + ? $this->get_db()->prepare( 'AND product_id = %d', absint( $this->query_vars['download_id'] ) ) + : ''; + + $price_id = $this->generate_price_id_query_sql(); + + if ( true === $this->query_vars['relative'] ) { + $relative_date_query_sql = $this->generate_relative_date_query_sql(); + + $sql = "SELECT IFNULL({$function}, 0) AS total, IFNULL(relative, 0) AS relative + FROM {$this->query_vars['table']} + CROSS JOIN ( + SELECT IFNULL({$function}, 0) AS relative + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['where_sql']} {$relative_date_query_sql} + ) o + WHERE 1=1 {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']}"; + } else { + $sql = "SELECT {$function} AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$product_id} {$price_id} {$this->query_vars['date_query_sql']}"; + } + + $result = $this->get_db()->get_row( $sql ); + + $total = null === $result->total + ? 0 + : absint( $result->total ); + + if ( true === $this->query_vars['relative'] ) { + $total = absint( $result->total ); + $relative = absint( $result->relative ); + $total = $this->generate_relative_markup( $total, $relative ); + } else { + $total = $this->maybe_format( $total ); + } + + // Reset query vars. + $this->post_query(); + + return $total; + } + + /** + * Calculate most downloaded products. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT` and `AVG`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return array Array of objects with most valuable order items. Each object has the product ID, number of downloads, + * and an instance of EDD_Download. + */ + public function get_most_downloaded_products( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_logs_file_downloads; + $this->query_vars['column'] = 'id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + // By default, the most valuable customer is returned. + $number = isset( $this->query_vars['number'] ) + ? absint( $this->query_vars['number'] ) + : 1; + + $sql = "SELECT product_id, file_id, COUNT(id) AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY product_id + ORDER BY total DESC + LIMIT {$number}"; + + $result = $this->get_db()->get_results( $sql ); + + array_walk( $result, function ( &$value ) { + + // Format resultant object. + $value->product_id = absint( $value->product_id ); + $value->file_id = absint( $value->file_id ); + $value->total = absint( $value->total ); + + // Add instance of EDD_Download to resultant object. + $value->object = edd_get_download( $value->product_id ); + } ); + + // Reset query vars. + $this->post_query(); + + return $result; + } + + /** + * Calculate average number of file downloads. + * + * @since 3.0 + * + * @param array $query { + * Optional. Array of query parameters. + * Default empty. + * + * Each method accepts query parameters to be passed. Parameters passed to methods override the ones passed in + * the constructor. This is by design to allow for multiple calculations to be executed from one instance of + * this class. + * + * @type string $start Start day and time (based on the beginning of the given day). + * @type string $end End day and time (based on the end of the given day). + * @type string $range Date range. If a range is passed, this will override and `start` and `end` + * values passed. See \EDD\Reports\get_dates_filter_options() for valid date ranges. + * @type string $function SQL function. Accepts `COUNT` and `AVG`. Default `COUNT`. + * @type string $where_sql Reserved for internal use. Allows for additional WHERE clauses to be appended + * to the query. + * @type string $output The output format of the calculation. Accepts `raw` and `formatted`. Default `raw`. + * } + * + * @return int Average file downloads. + */ + public function get_average_file_download_count( $query = array() ) { + + // Add table and column name to query_vars to assist with date query generation. + $this->query_vars['table'] = $this->get_db()->edd_logs_file_downloads; + $this->query_vars['column'] = 'customer_id'; + $this->query_vars['date_query_column'] = 'date_created'; + + // Run pre-query checks and maybe generate SQL. + $this->pre_query( $query ); + + $product_id = ! empty( $this->query_vars['download_id'] ) + ? $this->get_db()->prepare( 'AND product_id = %d', absint( $this->query_vars['download_id'] ) ) + : ''; + + $price_id = $this->generate_price_id_query_sql(); + + $file_id = ! empty( $this->query_vars['file_id'] ) + ? $this->get_db()->prepare( 'AND file_id = %d', absint( $this->query_vars['file_id'] ) ) + : ''; + + $sql = "SELECT AVG(total) AS total + FROM ( + SELECT {$this->query_vars['column']}, COUNT(id) AS total + FROM {$this->query_vars['table']} + WHERE 1=1 {$product_id} {$price_id} {$this->query_vars['where_sql']} {$this->query_vars['date_query_sql']} + GROUP BY {$this->query_vars['column']} + ) o"; + + $result = $this->get_db()->get_var( $sql ); + + $result = null === $result + ? 0 + : absint( $result ); + + // Reset query vars. + $this->post_query(); + + return $result; + } + + /** Private Methods ******************************************************/ + + /** + * Parse query vars to be passed to the calculation methods. + * + * @since 3.0 + * @access private + * + * @see \EDD\Stats::__construct() + * + * @param array $query Array of arguments. See \EDD\Stats::__construct(). + */ + private function parse_query( $query = array() ) { + $query_var_defaults = array( + 'start' => '', + 'end' => '', + 'range' => '', + 'exclude_taxes' => false, + 'currency' => false, + 'currency_sql' => '', + 'status' => array(), + 'status_sql' => '', + 'type' => array(), + 'type_sql' => '', + 'where_sql' => '', + 'date_query_sql' => '', + 'date_query_column' => '', + 'column' => '', + 'table' => '', + 'function' => 'SUM', + 'output' => 'raw', + 'relative' => false, + 'relative_start' => '', + 'relative_end' => '', + 'grouped' => false, + 'product_id' => '', + 'price_id' => null, + 'revenue_type' => 'gross', + 'country' => '', + 'region' => '', + ); + + if ( empty( $this->query_vars ) ) { + $this->query_vars_defaults = $this->query_vars = wp_parse_args( $query, $query_var_defaults ); + } else { + $this->query_vars = wp_parse_args( $query, $this->query_vars ); + } + + // Use Carbon to set up start and end date based on range passed. + if ( ! empty( $this->query_vars['range'] ) && isset( $this->date_ranges[ $this->query_vars['range'] ] ) ) { + + if ( ! empty( $this->date_ranges[ $this->query_vars['range'] ]['start'] ) ) { + $this->query_vars['start'] = $this->date_ranges[ $this->query_vars['range'] ]['start']->format( 'mysql' ); + } + + if ( ! empty( $this->date_ranges[ $this->query_vars['range'] ]['end'] ) ) { + $this->query_vars['end'] = $this->date_ranges[ $this->query_vars['range'] ]['end']->format( 'mysql' ); + } + } + + // Use Carbon to set up start and end date based on range passed. + if ( true === $this->query_vars['relative'] && ! empty( $this->query_vars['range'] ) && isset( $this->relative_date_ranges[ $this->query_vars['range'] ] ) ) { + + if ( ! empty( $this->relative_date_ranges[ $this->query_vars['range'] ]['start'] ) ) { + $this->query_vars['relative_start'] = $this->relative_date_ranges[ $this->query_vars['range'] ]['start']->format( 'mysql' ); + } + + if ( ! empty( $this->relative_date_ranges[ $this->query_vars['range'] ]['end'] ) ) { + $this->query_vars['relative_end'] = $this->relative_date_ranges[ $this->query_vars['range'] ]['end']->format( 'mysql' ); + } + } + + // Validate currency. + if ( empty( $this->query_vars['currency'] ) ) { + $this->query_vars['currency'] = false; + } elseif ( array_key_exists( strtoupper( $this->query_vars['currency'] ), edd_get_currencies() ) ) { + $this->query_vars['currency'] = strtoupper( $this->query_vars['currency'] ); + } else { + $this->query_vars['currency'] = 'convert'; + } + + // Correctly format functions and column names. + if ( ! empty( $this->query_vars['function'] ) ) { + $this->query_vars['function'] = strtoupper( $this->query_vars['function'] ); + } + + if ( ! empty( $this->query_vars['column'] ) ) { + $this->query_vars['column'] = strtolower( $this->query_vars['column'] ); + } + + /** Parse country ****************************************************/ + $country = isset( $this->query_vars['country'] ) + ? sanitize_text_field( $this->query_vars['country'] ) + : ''; + + if ( $country ) { + $country_list = array_filter( edd_get_country_list() ); + + // Maybe convert country code to country name. + $country = in_array( $country, array_flip( $country_list ), true ) + ? $country_list[ $country ] + : $country; + + // Ensure a valid county has been passed. + $country = in_array( $country, $country_list, true ) + ? $country + : null; + + // Convert back to country code for SQL query. + $country_list = array_flip( $country_list ); + $this->query_vars['country'] = is_null( $country ) + ? '' + : $country_list[ $country ]; + } + + /** Parse state ******************************************************/ + + $state = isset( $this->query_vars['region'] ) + ? sanitize_text_field( $this->query_vars['region'] ) + : ''; + + // Only parse state if one was passed. + if ( $state ) { + $state_list = array_filter( edd_get_shop_states( $this->query_vars['country'] ) ); + + // Maybe convert state code to state name. + $state = in_array( $state, array_flip( $state_list ), true ) + ? $state_list[ $state ] + : $state; + + // Ensure a valid state has been passed. + $state = in_array( $state, $state_list, true ) + ? $state + : null; + + // Convert back to state code for SQL query. + $state_codes = array_flip( $state_list ); + $this->query_vars['region'] = is_null( $state ) + ? '' + : $state_codes[ $state ]; + } + + /** + * Fires after the item query vars have been parsed. + * + * @since 3.0 + * + * @param \EDD\Stats &$this The \EDD\Stats (passed by reference). + */ + do_action_ref_array( 'edd_order_stats_parse_query', array( &$this ) ); + } + + /** + * Ensures arguments exist before going ahead and calculating statistics. + * + * @since 3.0 + * @access private + * + * @param array $query + */ + private function pre_query( $query = array() ) { + + // Maybe parse query. + if ( ! empty( $query ) ) { + $this->parse_query( $query ); + } + + // Generate date query SQL if dates have been set. + if ( ! empty( $this->query_vars['start'] ) || ! empty( $this->query_vars['end'] ) ) { + $date_query_sql = ' AND '; + + if ( ! empty( $this->query_vars['start'] ) ) { + $start_date = EDD()->utils->date( $this->query_vars['start'], edd_get_timezone_id(), false )->format( 'mysql' ); + $date_query_sql .= "{$this->query_vars['table']}.{$this->query_vars['date_query_column']} "; + $date_query_sql .= $this->get_db()->prepare( '>= %s', $start_date ); + } + + // Join dates with `AND` if start and end date set. + if ( ! empty( $this->query_vars['start'] ) && ! empty( $this->query_vars['end'] ) ) { + $date_query_sql .= ' AND '; + } + + if ( ! empty( $this->query_vars['end'] ) ) { + $end_date = EDD()->utils->date( $this->query_vars['end'], edd_get_timezone_id(), false )->format( 'mysql' ); + $date_query_sql .= $this->get_db()->prepare( "{$this->query_vars['table']}.{$this->query_vars['date_query_column']} <= %s", $end_date ); + } + + $this->query_vars['date_query_sql'] = $date_query_sql; + } + + // Generate status SQL if statuses have been set. + if ( ! empty( $this->query_vars['status'] ) ) { + if ( 'any' === $this->query_vars['status'] ) { + $this->query_vars['status_sql'] = ''; + } else { + $this->query_vars['status'] = array_map( 'sanitize_text_field', $this->query_vars['status'] ); + + $placeholders = $this->get_placeholder_string( $this->query_vars['status'] ); + + $this->query_vars['status_sql'] = $this->get_db()->prepare( "AND {$this->query_vars['table']}.status IN ({$placeholders})", $this->query_vars['status'] ); + } + } + + if ( ! empty( $this->query_vars['type'] ) ) { + + // We always want to format this as an array, so account for a possible string. + if ( ! is_array( $this->query_vars['type'] ) ) { + $this->query_vars['type'] = array( $this->query_vars['type'] ); + } + + $this->query_vars['type'] = array_map( 'sanitize_text_field', $this->query_vars['type'] ); + + $placeholders = $this->get_placeholder_string( $this->query_vars['type'] ); + + $this->query_vars['type_sql'] = $this->get_db()->prepare( "AND {$this->query_vars['table']}.type IN ({$placeholders})", $this->query_vars['type'] ); + } + + if ( ! empty( $this->query_vars['currency'] ) && 'convert' !== strtolower( $this->query_vars['currency'] ) ) { + $this->query_vars['currency_sql'] = $this->get_db()->prepare( "AND {$this->query_vars['table']}.currency = %s", $this->query_vars['currency'] ); + } + } + + /** + * Runs after a query. Resets query vars back to the originals passed in via the constructor. + * + * @since 3.0 + * @access private + */ + private function post_query() { + $this->query_vars = $this->query_var_originals; + } + + /** + * Format the data if requested via the query parameter. + * + * @since 3.0 + * @access private + * + * @param mixed $data Data to format. + * + * @return mixed Raw or formatted data depending on query parameter. + */ + private function maybe_format( $data = null ) { + + // Bail if nothing was passed. + if ( null === $data ) { + return $data; + } + + $allowed_output_formats = array( 'raw', 'typed', 'formatted' ); + + // Output format. Default raw. + $output = isset( $this->query_vars['output'] ) && in_array( $this->query_vars['output'], $allowed_output_formats, true ) + ? $this->query_vars['output'] + : 'raw'; + + // Return data as is if the format is raw. + if ( 'raw' === $output ) { + return $data; + } + + $currency = $this->query_vars['currency']; + if ( empty( $currency ) || 'convert' === strtolower( $currency ) ) { + $currency = edd_get_currency(); + } + + if ( is_object( $data ) ) { + foreach ( array_keys( get_object_vars( $data ) ) as $field ) { + if ( is_numeric( $data->{$field} ) ) { + $data->{$field} = edd_format_amount( $data->{$field}, true, $currency, $output ); + + if ( 'formatted' === $output ) { + $data->{$field} = edd_currency_filter( $data->{$field}, $currency ); + } + } + } + } elseif ( is_array( $data ) ) { + foreach ( array_keys( $data ) as $field ) { + if ( is_numeric( $data[ $field ] ) ) { + $data[ $field ] = edd_format_amount( $data[ $field ], true, $currency, $output ); + + if ( 'formatted' === $output ) { + $data[ $field ] = edd_currency_filter( $data[ $field ], $currency ); + } + } + } + } else { + if ( is_numeric( $data ) ) { + $data = edd_format_amount( $data, true, $currency, $output ); + + if ( 'formatted' === $output ) { + $data = edd_currency_filter( $data, $currency ); + } + } + } + + return $data; + } + + /** + * Generate date query SQL for relative time periods. + * + * @since 3.0 + * @access protected + * + * @return string Date query SQL. + */ + private function generate_relative_date_query_sql() { + + // Bail if relative calculation not requested. + if ( false === $this->query_vars['relative'] ) { + return ''; + } + + // Generate date query SQL if dates have been set. + if ( ! empty( $this->query_vars['relative_start'] ) || ! empty( $this->query_vars['relative_end'] ) ) { + $date_query_sql = "AND {$this->query_vars['table']}.{$this->query_vars['date_query_column']} "; + + if ( ! empty( $this->query_vars['relative_start'] ) ) { + $date_query_sql .= $this->get_db()->prepare( '>= %s', $this->query_vars['relative_start'] ); + } + + // Join dates with `AND` if start and end date set. + if ( ! empty( $this->query_vars['relative_start'] ) && ! empty( $this->query_vars['relative_end'] ) ) { + $date_query_sql .= ' AND '; + } + + if ( ! empty( $this->query_vars['relative_end'] ) ) { + $date_query_sql .= $this->get_db()->prepare( "{$this->query_vars['table']}.{$this->query_vars['date_query_column']} <= %s", $this->query_vars['relative_end'] ); + } + + return $date_query_sql; + } + } + + /** + * Generates price ID query SQL. + * + * @since 3.0 + * @return string + */ + private function generate_price_id_query_sql() { + return ! is_null( $this->query_vars['price_id'] ) && is_numeric( $this->query_vars['price_id'] ) + ? $this->get_db()->prepare( "AND {$this->query_vars['table']}.price_id = %d", absint( $this->query_vars['price_id'] ) ) + : ''; + } + + /** Private Getters *******************************************************/ + + /** + * Return the global database interface. + * + * @since 3.0 + * @access private + * @static + * + * @return \wpdb|\stdClass + */ + private static function get_db() { + return isset( $GLOBALS['wpdb'] ) + ? $GLOBALS['wpdb'] + : new \stdClass(); + } + + /** Private Setters ******************************************************/ + + /** + * Set up the date ranges available. + * + * @since 3.0 + * @access private + */ + private function set_date_ranges() { + + // Retrieve the time in UTC for the date ranges to be correctly parsed. + $date = EDD()->utils->date( 'now', edd_get_timezone_id(), false ); + + $date_filters = Reports\get_dates_filter_options(); + $filter = Reports\get_filter_value( 'dates' ); + + foreach ( $date_filters as $range => $label ) { + $this->date_ranges[ $range ] = Reports\parse_dates_for_range( $range ); + $this->relative_date_ranges[ $range ] = Reports\parse_relative_dates_for_range( $range ); + } + + } + + /** + * Based on the query_vars['revenue_type'], use gross or net statuses. + * + * @since 3.0 + * + * @return array The statuses of orders to use for the stats generation. + */ + private function get_revenue_type_statuses() { + if ( 'net' === $this->query_vars['revenue_type'] ) { + return edd_get_net_order_statuses(); + } + + return edd_get_gross_order_statuses(); + } + + /** + * Based on the query_vars['revenue_type'], use just sale or also include refunds. + * + * @since 3.0 + * + * @return array The order types to use when generating stats. + */ + private function get_revenue_type_order_types() { + $order_types = array( 'sale' ); + if ( 'net' === $this->query_vars['revenue_type'] ) { + $order_types[] = 'refund'; + } + + return $order_types; + } + + /** + * Calculates the relative change between two datasets + * and outputs an array of details about comparison. + * + * @since 3.1 + * + * @param int|float $total The primary value result for the stat. + * @param int|float $relative The value relative to the previous date range. + * @param bool $reverse If the stat being displayed is a 'reverse' state, where lower is better. + * + * @return array Details about the relative change between two datasets. + */ + public function generate_relative_data( $total = 0, $relative = 0, $reverse = false ) { + $output = array( + 'comparable' => true, + 'no_change' => false, + 'percentage_change' => false, + 'formatted_percentage_change' => false, + 'positive_change' => false, + 'total' => $total, + 'relative' => $relative, + 'reverse' => $reverse, + ); + + if ( ( floatval( 0 ) === floatval( $total ) && floatval( 0 ) === floatval( $relative ) ) || ( $total === $relative ) ) { + // There is no change between datasets. + $output['no_change'] = true; + } else if ( floatval( 0 ) !== floatval( $relative ) ) { + // There is a calculatable difference between datasets. + $percentage_change = ( $total - $relative ) / $relative * 100; + $formatted_percentage_change = absint( $percentage_change ); + $positive_change = false; + + if ( absint( $percentage_change ) < 100 ) { + $formatted_percentage_change = number_format( $percentage_change, 2 ); + $formatted_percentage_change = $formatted_percentage_change < 1 ? $formatted_percentage_change * -1 : $formatted_percentage_change; + } + + // Check if stat is in a 'reverse' state, where lower is better. + $positive_change = (bool) ! $reverse; + if ( 0 > $percentage_change ) { + $positive_change = (bool) $reverse; + } + + $output['percentage_change'] = $percentage_change; + $output['formatted_percentage_change'] = $formatted_percentage_change; + $output['positive_change'] = $positive_change; + } else { + // There is no data to compare. + $output['comparable'] = false; + } + + return $output; + } + + /** + * Generates output for the report tiles when a relative % change is requested. + * + * @since 3.0 + * + * @param int|float $total The primary value result for the stat. + * @param int|float $relative The value relative to the previous date range. + * @param bool $reverse If the stat being displayed is a 'reverse' state, where lower is better. + */ + private function generate_relative_markup( $total = 0, $relative = 0, $reverse = false ) { + + $relative_data = $this->generate_relative_data( $total, $relative, $reverse ); + $total_output = $this->maybe_format( $relative_data['total'] ); + $relative_markup = ''; + + if ( $relative_data['no_change'] ) { + $relative_output = esc_html__( 'No Change', 'easy-digital-downloads' ); + } else if ( $relative_data['comparable'] ) { + if ( 0 < $relative_data['percentage_change'] ) { + $direction = $relative_data['reverse'] ? 'up reverse' : 'up'; + $relative_output = ' ' . $relative_data['formatted_percentage_change'] . '%'; + } else { + $direction = $relative_data['reverse'] ? 'down reverse' : 'down'; + $relative_output = ' ' . $relative_data['formatted_percentage_change'] . '%'; + } + } else { + $relative_output = '' . __( 'No data to compare', 'easy-digital-downloads' ) . ''; + } + + $relative_markup = $total_output; + if ( ! empty( $relative_output ) ) { + $relative_markup .= '
    ' . $relative_output . '
    '; + } + + return $relative_markup; + } + + /** + * Gets a placeholder string from an array. + * + * @since 3.1 + * @param array $array + * @return string + */ + private function get_placeholder_string( $array ) { + return implode( ', ', array_fill( 0, count( $array ), '%s' ) ); + } +} diff --git a/includes/class-utilities.php b/includes/class-utilities.php new file mode 100644 index 00000000000..351e890eb01 --- /dev/null +++ b/includes/class-utilities.php @@ -0,0 +1,415 @@ +set_gmt_offset(); + $this->set_date_format(); + $this->set_time_format(); + $this->set_time_zone(); + $this->includes(); + } + + /** + * Loads needed files for core utilities. + * + * @since 3.0 + */ + private function includes() { + $utils_dir = EDD_PLUGIN_DIR . 'includes/utils/'; + + // Interfaces. + require_once $utils_dir . 'interface-static-registry.php'; + require_once $utils_dir . 'interface-error-logger.php'; + + // Exceptions. + require_once $utils_dir . 'class-edd-exception.php'; + require_once $utils_dir . 'exceptions/class-attribute-not-found.php'; + require_once $utils_dir . 'exceptions/class-invalid-argument.php'; + require_once $utils_dir . 'exceptions/class-invalid-parameter.php'; + + // Date management. + require_once $utils_dir . 'class-date.php'; + + // Registry. + require_once $utils_dir . 'class-registry.php'; + } + + /** + * Retrieves a given registry instance by name. + * + * @since 3.0 + * + * @param string $name Registry name. + * @return \EDD\Utils\Registry|\WP_Error The registry instance if it exists, otherwise a WP_Error.. + */ + public function get_registry( $name ) { + switch ( $name ) { + case 'reports': + if ( ! did_action( 'edd_reports_init' ) ) { + _doing_it_wrong( __FUNCTION__, 'The Report registry cannot be retrieved prior to the edd_reports_init hook.', 'EDD 3.0' ); + } elseif ( class_exists( '\EDD\Reports\Data\Report_Registry' ) ) { + $registry = Reports\Data\Report_Registry::instance(); + } + break; + + case 'reports:endpoints': + if ( ! did_action( 'edd_reports_init' ) ) { + _doing_it_wrong( __FUNCTION__, 'The Endpoints registry cannot be retrieved prior to the edd_reports_init hook.', 'EDD 3.0' ); + } elseif ( class_exists( '\EDD\Reports\Data\Endpoint_Registry' ) ) { + $registry = Reports\Data\Endpoint_Registry::instance(); + } + break; + + case 'reports:endpoints:views': + if ( ! did_action( 'edd_reports_init' ) ) { + _doing_it_wrong( __FUNCTION__, 'The Endpoint Views registry cannot be retrieved prior to the edd_reports_init hook.', 'EDD 3.0' ); + } elseif ( class_exists( '\EDD\Reports\Data\Endpoint_View_Registry' ) ) { + $registry = Reports\Data\Endpoint_View_Registry::instance(); + } + break; + + default: + $registry = new \WP_Error( 'invalid_registry', "The '{$name}' registry does not exist." ); + break; + } + + return $registry; + } + + /** + * Retrieves a date format string based on a given short-hand format. + * + * @see edd_get_date_format() + * @see edd_get_date_picker_format() + * + * @since 3.0 + * + * @param string $format Shorthand date format string. Accepts 'date', 'time', 'mysql', 'datetime', + * 'picker-field' or 'picker-js'. If none of the accepted values, the + * original value will simply be returned. Default is the value of the + * `$date_format` property, derived from the core 'date_format' option. + * @return string date_format()-compatible date format string. + */ + public function get_date_format_string( $format = 'date' ) { + + // Default to 'date' if empty + if ( empty( $format ) ) { + $format = 'date'; + } + + // Bail if format is not known + if ( ! in_array( $format, array( 'date', 'time', 'datetime', 'mysql', 'date-attribute', 'date-js', 'date-mysql', 'time-mysql' ), true ) ) { + return $format; + } + + // What known format are we getting? + switch ( $format ) { + + // jQuery UI Datepicker fields, placeholders, etc... + case 'date-attribute': + $retval = 'yyyy-mm-dd'; + break; + + // jQuery UI Datepicker JS variable + case 'date-js': + $retval = 'yy-mm-dd'; + break; + + // Date in MySQL format + case 'date-mysql': + $retval = 'Y-m-d'; + break; + + // Time in MySQL format + case 'time-mysql': + $retval = 'H:i:s'; + break; + + // MySQL datetime columns + case 'mysql': + $retval = 'Y-m-d H:i:s'; + break; + + // WordPress date_format + time_format + case 'datetime': + $retval = $this->get_date_format() . ' ' . $this->get_time_format(); + break; + + // WordPress time_format only + case 'time': + $retval = $this->get_time_format(); + break; + + // WordPress date_format only + case 'date': + default: + $retval = $this->get_date_format(); + break; + } + + return $retval; + } + + /** + * Retrieves a date instance for the WP timezone (and offset) based on the given date string. + * + * @since 3.0 + * + * @param string $date_string Optional. Date string. Default 'now'. + * @param string $timezone Optional. Timezone to generate the Carbon instance for. + * Default is the timezone set in WordPress settings. + * @param bool $localize Optional. Whether to apply the offset in seconds to the generated + * date. Default false. + * + * @return \EDD\Utils\Date Date instance. Time is returned as UTC. + * @throws \Exception + */ + public function date( $date_string = 'now', $timezone = null, $localize = false ) { + + // Fallback to this time zone + if ( null === $timezone && true === $localize ) { + $timezone = $this->get_time_zone(); + } elseif ( null === $timezone && false === $localize ) { + $timezone = 'UTC'; + } + + // If the date string cannot be property converted to a valid time, reset it to now. + if ( ! strtotime( $date_string ) ) { + $date_string = 'now'; + } + + /* + * Create the DateTime object with the "local" WordPress timezone. + * + * Note that supplying the timezone during DateTime instantiation doesn't actually + * convert the UNIX timestamp, it just lays the groundwork for deriving the offset. + */ + $date = new Utils\Date( $date_string, new \DateTimezone( $timezone ) ); + + if ( false === $localize ) { + /* + * The offset is automatically applied when the Date object is instantiated. + * + * If $apply_offset is false, the interval needs to be removed again after the fact. + */ + $offset = $date->getOffset(); + $interval = \DateInterval::createFromDateString( "-{$offset} seconds" ); + $date->add( $interval ); + } + + return $date; + } + + /** + * Retrieves the WordPress GMT offset property, as cached at run-time. + * + * @since 3.0 + * + * @param bool $refresh Optional. Whether to refresh the `$gmt_offset` value before retrieval. + * Default false. + * @return int Value of the gmt_offset property. + */ + public function get_gmt_offset( $refresh = false ) { + if ( is_null( $this->gmt_offset ) || ( true === $refresh ) ) { + $this->set_gmt_offset(); + } + + return $this->gmt_offset; + } + + /** + * Retrieves the WordPress date format, as cached at run-time. + * + * @since 3.0 + * + * @param bool $refresh Optional. Whether to refresh the `$gmt_offset` value before retrieval. + * Default false. + * @return string Value of the `$date_format` property. + */ + public function get_date_format( $refresh = false ) { + if ( is_null( $this->date_format ) || ( true === $refresh ) ) { + $this->set_date_format(); + } + + return $this->date_format; + } + + /** + * Retrieves the WordPress time format, as cached at run-time. + * + * @since 3.0 + * + * @param bool $refresh Optional. Whether to refresh the `$gmt_offset` value before retrieval. + * Default false. + * @return string Value of the `$time_format` property. + */ + public function get_time_format( $refresh = false ) { + if ( is_null( $this->time_format ) || ( true === $refresh ) ) { + $this->set_time_format(); + } + + return $this->time_format; + } + + /** + * Retrieves the WordPress time zone, as cached at run-time. + * + * @since 3.0 + * + * @param bool $refresh Optional. Whether to refresh the `$time_zone` value before retrieval. + * Default false. + * @return string Value of the `$time_zone` property. + */ + public function get_time_zone( $refresh = false ) { + if ( is_null( $this->time_zone ) || ( true === $refresh ) ) { + $this->set_time_zone(); + } + + return $this->time_zone; + } + + /** + * Gets a valid date string in the format Y-m-d HH:MM:00 + * + * @since 3.0 + * @param string $date A valid date string. + * @param int $hour The hour. + * @param int $minute The minute. + * @return string + */ + public function get_date_string( $date = '', $hour = 0, $minute = 0 ) { + if ( empty( $date ) || ! strtotime( $date ) ) { + $date = date( 'Y-m-d' ); + } + + $hour = absint( $hour ); + if ( $hour > 23 ) { + $hour = 23; + } + $hour = str_pad( $hour, 2, '0', STR_PAD_LEFT ); + + $minute = absint( $minute ); + if ( $minute > 59 ) { + $minute = 59; + } + $minute = str_pad( $minute, 2, '0', STR_PAD_LEFT ); + + return "{$date} {$hour}:{$minute}:00"; + } + + /** Private Setters *******************************************************/ + + /** + * Private setter for GMT offset + * + * @since 3.0 + */ + private function set_gmt_offset() { + $this->gmt_offset = get_option( 'gmt_offset', 0 ) * HOUR_IN_SECONDS; + } + + /** + * Private setter for date format + * + * @since 3.0 + */ + private function set_date_format() { + $this->date_format = get_option( 'date_format', 'M j, Y' ); + } + + /** + * Private setter for time format + * + * @since 3.0 + */ + private function set_time_format() { + $this->time_format = get_option( 'time_format', 'g:i a' ); + } + + /** + * Private setter for time zone + * + * @since 3.0 + */ + private function set_time_zone() { + + // Default return value + $retval = 'UTC'; + + // Get some useful values + $timezone = get_option( 'timezone_string' ); + $gmt_offset = $this->get_gmt_offset(); + + // Use timezone string if it's available + if ( ! empty( $timezone ) ) { + $retval = $timezone; + + // Use GMT offset to calculate + } elseif ( is_numeric( $gmt_offset ) ) { + $hours = abs( floor( $gmt_offset / HOUR_IN_SECONDS ) ); + $minutes = abs( floor( ( $gmt_offset / MINUTE_IN_SECONDS ) % MINUTE_IN_SECONDS ) ); + $math = ( $gmt_offset >= 0 ) ? '+' : '-'; + $value = ! empty( $minutes ) ? "{$hours}:{$minutes}" : $hours; + $retval = "GMT{$math}{$value}"; + } + + // Set + $this->time_zone = $retval; + } +} diff --git a/includes/compat-functions.php b/includes/compat-functions.php new file mode 100644 index 00000000000..503a6009db8 --- /dev/null +++ b/includes/compat-functions.php @@ -0,0 +1,102 @@ + $value ) { + if ( substr( $name, 0, 5 ) == 'HTTP_' ) { + $headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $name, 5 ) ) ) ) ) ] = $value; + } + } + return $headers; + } +endif; + +if ( ! function_exists( 'wp_timezone_string' ) ) : + /** + * Polyfill for wp_timezone_string() function added in WP 5.3.0 + * + * Retrieves the timezone of the site as a string. + * + * @since 3.1.0.3 + * @return string PHP timezone name or a ±HH:MM offset. + */ + function wp_timezone_string() { + $timezone_string = get_option( 'timezone_string' ); + + if ( $timezone_string ) { + return $timezone_string; + } + + $offset = (float) get_option( 'gmt_offset' ); + $hours = (int) $offset; + $minutes = ( $offset - $hours ); + + $sign = ( $offset < 0 ) ? '-' : '+'; + $abs_hour = abs( $hours ); + $abs_mins = abs( $minutes * 60 ); + $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); + + return $tz_offset; + } +endif; diff --git a/includes/compat/class-base.php b/includes/compat/class-base.php new file mode 100644 index 00000000000..ecf65f6d4b3 --- /dev/null +++ b/includes/compat/class-base.php @@ -0,0 +1,78 @@ +hooks(); + + $this->show_notices = apply_filters( 'edd_show_deprecated_notices', ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ); + $this->show_backtrace = apply_filters( 'edd_show_backtrace', ( defined( 'WP_DEBUG' ) && WP_DEBUG ) && ! defined( 'EDD_DOING_TESTS' ) ); + } + + /** + * Getter for component. + * + * @since 3.0 + * + * @return string Component. + */ + public function get_component() { + return $this->component; + } + + /** + * Backwards compatibility hooks for component. + * + * @since 3.0 + * @access protected + */ + abstract protected function hooks(); +} \ No newline at end of file diff --git a/includes/compat/class-customer.php b/includes/compat/class-customer.php new file mode 100644 index 00000000000..a6ed560fafe --- /dev/null +++ b/includes/compat/class-customer.php @@ -0,0 +1,303 @@ +edd_customers; + + case 'primary_key' : + return 'id'; + + case 'version' : + $table = edd_get_component_interface( 'customer', 'table' ); + + return $table instanceof Table ? $table->get_version() : false; + case 'meta_type' : + return 'customer'; + + case 'date_key' : + return 'date_created'; + + case 'cache_group' : + return 'customers'; + } + + return null; + } + + /** + * Magic method to handle calls to method that no longer exist. + * + * @since 3.0 + * + * @param string $name Name of the method. + * @param array $arguments Enumerated array containing the parameters passed to the $name'ed method. + * @return mixed Dependent on the method being dispatched to. + */ + public function __call( $name, $arguments ) { + switch ( $name ) { + case 'add': + case 'insert': + return edd_add_customer( $arguments[0] ); + + case 'update': + return edd_update_customer( $arguments[0], $arguments[1] ); + + case 'delete': + if ( ! is_bool( $arguments[0] ) ) { + return false; + } + + $column = is_email( $arguments[0] ) ? 'email' : 'id'; + $customer = edd_get_customer_by( $column, $arguments[0] ); + edd_delete_customer( $customer->id ); + break; + case 'exists': + return (bool) edd_get_customer_by( 'email', $arguments[0] ); + + case 'get_customer_by': + return edd_get_customer_by( $arguments[0], $arguments[1] ); + + case 'get_customers': + return edd_get_customers( $arguments[0] ); + + case 'count': + return edd_count_customers(); + + case 'get_column': + return edd_get_customer_by( $arguments[0], $arguments[1] ); + + case 'attach_payment': + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( $arguments[0] ); + + if ( ! $customer ) { + return false; + } + + return $customer->attach_payment( $arguments[1], false ); + + case 'remove_payment': + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( $arguments[0] ); + + if ( ! $customer ) { + return false; + } + + return $customer->remove_payment( $arguments[1], false ); + + case 'increment_stats': + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( $arguments[0] ); + + if ( ! $customer ) { + return false; + } + + $increased_count = $customer->increase_purchase_count(); + $increased_value = $customer->increase_value( $arguments[1] ); + + return ( $increased_count && $increased_value ) + ? true + : false; + + case 'decrement_stats': + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( $arguments[0] ); + + if ( ! $customer ) { + return false; + } + + $decreased_count = $customer->decrease_purchase_count(); + $decreased_value = $customer->decrease_value( $arguments[1] ); + + return ( $decreased_count && $decreased_value ) + ? true + : false; + } + } + + /** + * Backwards compatibility hooks for customers. + * + * @since 3.0 + * @access protected + */ + protected function hooks() { + + /** Filters **********************************************************/ + + add_filter( 'get_user_metadata', array( $this, 'get_user_meta' ), 99, 4 ); + add_filter( 'update_user_metadata', array( $this, 'update_user_meta' ), 99, 5 ); + add_filter( 'add_user_metadata', array( $this, 'update_user_meta' ), 99, 5 ); + } + + /** + * Backwards compatibility filters for get_user_meta() calls on customers. + * + * @since 3.0 + * + * @param mixed $value The value get_post_meta would return if we don't filter. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param bool $single If a single value or an array of the value is requested. + * + * @return mixed The value to return. + */ + public function get_user_meta( $value, $object_id, $meta_key, $single ) { + if ( 'get_user_metadata' !== current_filter() ) { + $message = __( 'This function is not meant to be called directly. It is only here for backwards compatibility purposes.', 'easy-digital-downloads' ); + _doing_it_wrong( __FUNCTION__, esc_html( $message ), 'EDD 3.0' ); + } + + if ( '_edd_user_address' !== $meta_key ) { + return $value; + } + + $value = edd_get_customer_address( $object_id ); + + if ( $this->show_notices ) { + _doing_it_wrong( 'get_user_meta()', 'User addresses being stored in meta have been deprecated since Easy Digital Downloads 3.0! Use edd_get_customer_address() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + return array( $value ); + } + + /** + * Listen for calls to update_user_meta() for customers and see if we need to filter them. + * + * This is here for backwards compatibility purposes with the migration to custom tables in EDD 3.0. + * + * @since 3.0 + * + * @param null|bool $check Whether to allow updating metadata for the given type. + * @param int $object_id Object ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. Must be serializable if non-scalar. + * @param mixed $prev_value Optional. If specified, only update existing metadata entries with the specified value. + * Otherwise, update all entries. + * + * @return mixed Returns 'null' if no action should be taken and WordPress core can continue, or non-null to avoid usermeta. + */ + public function update_user_meta( $check, $object_id, $meta_key, $meta_value, $prev_value ) { + if ( '_edd_user_address' !== $meta_key ) { + return $check; + } + + // Fetch saved primary address. + $addresses = edd_get_customer_addresses( + array( + 'number' => 1, + 'is_primary' => true, + 'customer_id' => $object_id, + ) + ); + + // Defaults. + $defaults = array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'state' => '', + 'country' => '', + 'zip' => '', + ); + + $address = wp_parse_args( (array) $meta_value, $defaults ); + + if ( is_array( $addresses ) && ! empty( $addresses[0] ) ) { + $customer_address = $addresses[0]; + + edd_update_customer_address( + $customer_address->id, + array( + 'address' => $address['line1'], + 'address2' => $address['line2'], + 'city' => $address['city'], + 'region' => $address['state'], + 'postal_code' => $address['zip'], + 'country' => $address['country'], + ) + ); + } else { + $customer = edd_get_customer_by( 'user_id', absint( $object_id ) ); + + if ( $customer ) { + edd_add_customer_address( + array( + 'customer_id' => $customer->id, + 'address' => $address['line1'], + 'address2' => $address['line2'], + 'city' => $address['city'], + 'region' => $address['state'], + 'postal_code' => $address['zip'], + 'country' => $address['country'], + 'is_primary' => true, + ) + ); + } + } + + if ( $this->show_notices ) { + _doing_it_wrong( 'add_user_meta()/update_user_meta()', 'User addresses being stored in meta have been deprecated since Easy Digital Downloads 3.0! Use edd_add_customer_address()/edd_update_customer_address()() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + return $check; + } + +} diff --git a/includes/compat/class-customermeta.php b/includes/compat/class-customermeta.php new file mode 100644 index 00000000000..0a2d5bfd358 --- /dev/null +++ b/includes/compat/class-customermeta.php @@ -0,0 +1,100 @@ +edd_customermeta; + + case 'primary_key' : + return 'meta_id'; + + case 'version' : + $table = edd_get_component_interface( 'customer', 'meta' ); + + return $table instanceof Table ? $table->get_version() : false; + } + + return null; + } + + /** + * Magic method to handle calls to method that no longer exist. + * + * @since 3.0 + * + * @param string $name Name of the method. + * @param array $arguments Enumerated array containing the parameters passed to the $name'ed method. + * @return mixed Dependent on the method being dispatched to. + */ + public function __call( $name, $arguments ) { + switch ( $name ) { + case 'get_meta' : + return edd_get_customer_meta( + isset( $arguments[0] ) ? $arguments[0] : 0, + isset( $arguments[1] ) ? $arguments[1] : '', + isset( $arguments[2] ) ? $arguments[2] : false + ); + + case 'add_meta' : + return edd_add_customer_meta( + isset( $arguments[0] ) ? $arguments[0] : 0, + isset( $arguments[1] ) ? $arguments[1] : '', + isset( $arguments[2] ) ? $arguments[2] : false, + isset( $arguments[3] ) ? $arguments[3] : false + ); + + case 'update_meta' : + return edd_update_customer_meta( + isset( $arguments[0] ) ? $arguments[0] : 0, + isset( $arguments[1] ) ? $arguments[1] : '', + isset( $arguments[2] ) ? $arguments[2] : false, + isset( $arguments[3] ) ? $arguments[3] : '' + ); + + case 'delete_meta' : + return edd_delete_customer_meta( + isset( $arguments[0] ) ? $arguments[0] : 0, + isset( $arguments[1] ) ? $arguments[1] : '', + isset( $arguments[2] ) ? $arguments[2] : '' + ); + } + + return null; + } + + /** + * Backwards compatibility hooks for customer meta. + * + * @since 3.0 + * @access protected + */ + protected function hooks() { + // No hooks. + } +} diff --git a/includes/compat/class-discount-query.php b/includes/compat/class-discount-query.php new file mode 100644 index 00000000000..01640a2dc6f --- /dev/null +++ b/includes/compat/class-discount-query.php @@ -0,0 +1,81 @@ +swap_types( $query ) ); + } + + /** + * Swap out types in an item. + * + * @since 3.0 + * + * @param array $item Array of item arguments + * @return array + */ + public function filter_item( $item = array() ) { + return parent::filter_item( $this->swap_types( $item ) ); + } + + /** + * Swap out the type arguments. + * + * @since 3.0 + * + * @param array $args + * @return array + */ + private function swap_types( $args = array() ) { + + // Switch `type` to `amount_type` + if ( empty( $args['amount_type'] ) && ! empty( $args['type'] ) ) { + $args['amount_type'] = $args['type']; + } + + // Force `type` to `discount` + $args['type'] = 'discount'; + + // Return swapped arguments + return $args; + } +} diff --git a/includes/compat/class-discount.php b/includes/compat/class-discount.php new file mode 100644 index 00000000000..a3795052454 --- /dev/null +++ b/includes/compat/class-discount.php @@ -0,0 +1,546 @@ +get( 'post_type' ) ) { + return; + } + + // Force filters to run + $query->set( 'suppress_filters', false ); + + // Setup doing-it-wrong message + $message = sprintf( + __( 'As of Easy Digital Downloads 3.0, discounts no longer exist in the %1$s table. They have been migrated to %2$s. Discounts should be accessed using %3$s, %4$s or instantiating a new instance of %5$s. See %6$s for more information.', 'easy-digital-downloads' ), + '' . $wpdb->posts . '', + '' . edd_get_component_interface( 'adjustment', 'table' )->table_name . '', + 'edd_get_discounts()', + 'edd_get_discount()', + 'EDD_Discount', + 'https://easydigitaldownloads.com/development/' + ); + + _doing_it_wrong( 'get_posts()/get_post()', $message, '3.0' ); + } + + /** + * Backwards compatibility layer for wp_count_posts(). + * + * @since 3.0 + * + * @param string $query SQL query. + * @return string $request Rewritten SQL query. + */ + public function wp_count_posts( $query ) { + global $wpdb; + + $expected = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = 'edd_discount' GROUP BY post_status"; + + if ( $expected === $query ) { + $query = "SELECT status AS post_status, COUNT( * ) AS num_posts FROM {$wpdb->edd_adjustments} WHERE type = 'discount' GROUP BY post_status"; + } + + return $query; + } + + /** + * Fill the returned WP_Post objects with the data from the discounts table. + * + * @since 3.0 + * + * @param array $posts Posts returned from the SQL query. + * @param \WP_Query $query Instance of WP_Query. + * + * @return array New WP_Post objects. + */ + public function posts_results( $posts, $query ) { + if ( 'posts_results' !== current_filter() ) { + $message = __( 'This function is not meant to be called directly. It is only here for backwards compatibility purposes.', 'easy-digital-downloads' ); + _doing_it_wrong( __FUNCTION__, esc_html( $message ), 'EDD 3.0' ); + } + + if ( 'edd_discount' === $query->get( 'post_type' ) ) { + $new_posts = array(); + + foreach ( $posts as $post ) { + $discount = edd_get_discount( $post->id ); + + $object_vars = array( + 'ID' => $discount->id, + 'post_title' => $discount->name, + 'post_status' => $discount->status, + 'post_type' => 'edd_discount', + 'post_date' => EDD()->utils->date( $discount->date_created, null, true )->toDateTimeString(), + 'post_date_gmt' => $discount->date_created, + 'post_modified' => EDD()->utils->date( $discount->date_modified, null, true )->toDateTimeString(), + 'post_modified_gmt' => $discount->date_created, + ); + + foreach ( $object_vars as $object_var => $value ) { + $post->{$object_var} = $value; + } + + $post = new \WP_Post( $post ); + + $new_posts[] = $post; + } + + return $new_posts; + } + + return $posts; + } + + /** + * Hijack the SQL query and rewrite it to fetch data from the discounts table. + * + * @since 3.0 + * + * @param string $request SQL query. + * @param \WP_Query $query Instance of WP_Query. + * + * @return string $request Rewritten SQL query. + */ + public function posts_request( $request, $query ) { + global $wpdb; + + if ( 'posts_request' !== current_filter() ) { + $message = __( 'This function is not meant to be called directly. It is only here for backwards compatibility purposes.', 'easy-digital-downloads' ); + _doing_it_wrong( __FUNCTION__, esc_html( $message ), '3.0' ); + } + + if ( 'edd_discount' === $query->get( 'post_type' ) ) { + $defaults = array( + 'number' => 30, + 'status' => array( 'active', 'inactive', 'expired' ), + 'order' => 'DESC', + 'orderby' => 'date_created', + ); + + $args = array( + 'number' => $query->get( 'posts_per_page' ), + 'status' => $query->get( 'post_status', array( 'active', 'inactive' ) ), + ); + + $orderby = $query->get( 'orderby', false ); + if ( $orderby ) { + switch ( $orderby ) { + case 'none': + case 'ID': + case 'author': + case 'post__in': + case 'type': + case 'post_type': + $args['orderby'] = 'id'; + break; + case 'title': + $args['orderby'] = 'name'; + break; + case 'date': + case 'post_date': + case 'modified': + case 'post_modified': + $args['orderby'] = 'date_created'; + break; + default: + $args['orderby'] = 'id'; + break; + } + } + + $offset = $query->get( 'offset', false ); + if ( $offset ) { + $args['offset'] = absint( $offset ); + } else { + $args['offset'] = 0; + } + + if ( 'any' === $args['status'] ) { + $args['status'] = $defaults['status']; + } + + $args = wp_parse_args( $args, $defaults ); + + if ( array_key_exists( 'number', $args ) ) { + $args['number'] = absint( $args['number'] ); + } + + $table_name = edd_get_component_interface( 'adjustment', 'table' )->table_name; + + $meta_query = $query->get( 'meta_query' ); + + $clauses = array(); + $sql_where = "WHERE type = 'discount'"; + + $meta_key = $query->get( 'meta_key', false ); + $meta_value = $query->get( 'meta_value', false ); + $columns = wp_list_pluck( edd_get_component_interface( 'adjustment', 'schema' )->columns, 'name' ); + + // 'meta_key' and 'meta_value' passed as arguments + if ( $meta_key && $meta_value ) { + /** + * Check that the key exists as a column in the table. + * Note: there is no backwards compatibility support for product requirements and excluded + * products as these would be serialized under the old schema. + */ + if ( in_array( $meta_key, $columns, true ) ) { + $sql_where .= ' ' . $wpdb->prepare( "{$meta_key} = %s", $meta_value ); + } + } + + if ( ! empty( $meta_query ) ) { + foreach ( $meta_query as $key => $query ) { + $relation = 'AND'; // Default relation + + if ( is_string( $query ) && 'relation' === $key ) { + $relation = $query; + } + + if ( is_array( $query ) ) { + if ( array_key_exists( 'key', $query ) ) { + $query['key'] = str_replace( '_edd_discount_', '', $query['key'] ); + + /** + * Check that the key exists as a column in the table. + * Note: there is no backwards compatibility support for product requirements and excluded + * products as these would be serialized under the old schema. + */ + if ( in_array( $query['key'], $columns, true ) && array_key_exists( 'value', $query ) ) { + $meta_compare = ! empty( $query['compare'] ) ? $query['compare'] : '='; + $meta_compare = strtoupper( $meta_compare ); + + $meta_value = $query['value']; + + $where = null; + + switch ( $meta_compare ) { + case 'IN': + case 'NOT IN': + $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; + $where = $wpdb->prepare( $meta_compare_string, $meta_value ); + break; + + case 'BETWEEN': + case 'NOT BETWEEN': + $meta_value = array_slice( $meta_value, 0, 2 ); + $where = $wpdb->prepare( '%1$s AND %1$s', $meta_value ); + break; + + case 'LIKE': + case 'NOT LIKE': + $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; + $where = $wpdb->prepare( '%s', $meta_value ); + break; + + // EXISTS with a value is interpreted as '='. + case 'EXISTS': + $where = $wpdb->prepare( '%s', $meta_value ); + break; + + // 'value' is ignored for NOT EXISTS. + case 'NOT EXISTS': + $where = $query['key'] . ' IS NULL'; + break; + + default: + $where = $wpdb->prepare( '%s', $meta_value ); + break; + } + + if ( ! is_null( $where ) ) { + $clauses['where'][] = $query['key'] . ' ' . $meta_compare . ' ' . $where; + } + } + } + + if ( ! empty( $clauses['where'] ) && is_array( $clauses['where'] ) ) { + $sql_where .= ' AND ( ' . implode( ' ' . $relation . ' ', $clauses['where'] ) . ' )'; + } + } + } + } + + $request = "SELECT id FROM {$table_name} {$sql_where} ORDER BY {$args['orderby']} {$args['order']} LIMIT {$args['offset']}, {$args['number']};"; + } + + return $request; + } + + /** + * Backwards compatibility filters for get_post_meta() calls on discounts. + * + * @since 3.0 + * + * @param mixed $value The value get_post_meta would return if we don't filter. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param bool $single If a single value or an array of the value is requested. + * + * @return mixed The value to return. + */ + public function get_post_metadata( $value, $object_id, $meta_key, $single ) { + + $meta_keys = apply_filters( 'edd_post_meta_discount_backwards_compat_keys', array( + '_edd_discount_status', + '_edd_discount_amount', + '_edd_discount_uses', + '_edd_discount_name', + '_edd_discount_code', + '_edd_discount_expiration', + '_edd_discount_start', + '_edd_discount_is_single_use', + '_edd_discount_is_not_global', + '_edd_discount_product_condition', + '_edd_discount_min_price', + '_edd_discount_max_uses' + ) ); + + // Bail early of not a back-compat key + if ( ! in_array( $meta_key, $meta_keys, true ) ) { + return $value; + } + + // Bail if discount does not exist + $discount = edd_get_discount( $object_id ); + if ( empty( $discount->id ) ) { + return $value; + } + + switch ( $meta_key ) { + case '_edd_discount_name': + case '_edd_discount_status': + case '_edd_discount_amount': + case '_edd_discount_uses': + case '_edd_discount_code': + case '_edd_discount_expiration': + case '_edd_discount_start': + case '_edd_discount_product_condition': + case '_edd_discount_min_price': + case '_edd_discount_max_uses': + $key = str_replace( '_edd_discount_', '', $meta_key ); + + $value = $discount->{$key}; + + if ( $this->show_notices ) { + _doing_it_wrong( 'get_post_meta()', 'All discount postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_adjustment_meta() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + break; + + case '_edd_discount_is_single_use': + $value = $discount->get_once_per_customer(); + + if ( $this->show_notices ) { + _doing_it_wrong( 'get_post_meta()', 'All discount postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_adjustment_meta() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + break; + + case '_edd_discount_is_not_global': + $value = $discount->get_scope(); + + if ( $this->show_notices ) { + _doing_it_wrong( 'get_post_meta()', 'All discount postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_adjustment_meta() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + break; + default: + /* + * Developers can hook in here with add_filter( 'edd_get_post_meta_discount_backwards_compat-meta_key... in order to + * Filter their own meta values for backwards compatibility calls to get_post_meta instead of EDD_Discount::get_meta + */ + $value = apply_filters( 'edd_get_post_meta_discount_backwards_compat-' . $meta_key, $value, $object_id ); + break; + } + + return $value; + } + + + /** + * Backwards compatibility filters for add/update_post_meta() calls on discounts. + * + * @since 3.0 + * + * @param mixed $check Comes in 'null' but if returned not null, WordPress Core will not interact with the postmeta table. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param mixed $meta_value The value get_post_meta would return if we don't filter. + * @param mixed $prev_value The previous value of the meta + * + * @return mixed Returns 'null' if no action should be taken and WordPress core can continue, or non-null to avoid postmeta. + */ + public function update_post_metadata( $check, $object_id, $meta_key, $meta_value, $prev_value ) { + + $meta_keys = apply_filters( 'edd_update_post_meta_discount_backwards_compat_keys', array( + '_edd_discount_status', + '_edd_discount_amount', + '_edd_discount_uses', + '_edd_discount_name', + '_edd_discount_code', + '_edd_discount_expiration', + '_edd_discount_start', + '_edd_discount_is_single_use', + '_edd_discount_is_not_global', + '_edd_discount_product_condition', + '_edd_discount_min_price', + '_edd_discount_max_uses' + ) ); + + // Bail early of not a back-compat key + if ( ! in_array( $meta_key, $meta_keys, true ) ) { + return $check; + } + + // Bail if discount does not exist + $discount = edd_get_discount( $object_id ); + if ( empty( $discount->id ) ) { + return $check; + } + + switch ( $meta_key ) { + case '_edd_discount_name': + case '_edd_discount_status': + case '_edd_discount_amount': + case '_edd_discount_uses': + case '_edd_discount_code': + case '_edd_discount_expiration': + case '_edd_discount_start': + case '_edd_discount_product_condition': + case '_edd_discount_min_price': + case '_edd_discount_max_uses': + $key = str_replace( '_edd_discount_', '', $meta_key ); + $discount->{$key} = $meta_value; + $check = $discount->save(); + + if ( $this->show_notices ) { + _doing_it_wrong( 'add_post_meta()/update_post_meta()', 'All discount postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_add_adjustment_meta()/edd_update_adjustment_meta() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + break; + case '_edd_discount_is_single_use': + $discount->once_per_customer = $meta_value; + $check = $discount->save(); + + // Since the old discounts data was simply stored in a single post meta entry, just don't let it be added. + if ( $this->show_notices ) { + _doing_it_wrong( 'add_post_meta()/update_post_meta()', 'All discount postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_add_adjustment_meta()/edd_update_adjustment_meta() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + break; + case '_edd_discount_is_not_global': + $discount->scope = $meta_value; + $check = $discount->save(); + + // Since the old discounts data was simply stored in a single post meta entry, just don't let it be added. + if ( $this->show_notices ) { + _doing_it_wrong( 'add_post_meta()/update_post_meta()', 'All discount postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_add_adjustment_meta()/edd_update_adjustment_meta() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + break; + default: + /* + * Developers can hook in here with add_filter( 'edd_get_post_meta_discount_backwards_compat-meta_key... in order to + * Filter their own meta values for backwards compatibility calls to get_post_meta instead of EDD_Discount::get_meta + */ + $check = apply_filters( 'edd_update_post_meta_discount_backwards_compat-' . $meta_key, $check, $object_id, $meta_value, $prev_value ); + break; + } + + return $check; + + } +} diff --git a/includes/compat/class-edd-payment-compat.php b/includes/compat/class-edd-payment-compat.php new file mode 100644 index 00000000000..19328a6ed36 --- /dev/null +++ b/includes/compat/class-edd-payment-compat.php @@ -0,0 +1,972 @@ +ID = absint( $payment_id ); + $this->payment = get_post( $this->ID ); + if ( ! $this->payment instanceof WP_Post ) { + return false; + } + $this->setup(); + } + + /** + * Sets up the payment object. + * + * @since 3.0 + * @return void + */ + private function setup() { + $this->payment_meta = $this->get_meta(); + $this->cart_details = $this->setup_cart_details(); + $this->status = $this->setup_status(); + $this->completed_date = $this->setup_completed_date(); + $this->mode = $this->setup_mode(); + $this->total = $this->setup_total(); + $this->tax = $this->setup_tax(); + $this->tax_rate = $this->setup_tax_rate(); + $this->fees_total = $this->setup_fees_total(); + $this->subtotal = $this->setup_subtotal(); + $this->discounts = $this->setup_discounts(); + $this->currency = $this->setup_currency(); + $this->fees = $this->setup_fees(); + $this->gateway = $this->setup_gateway(); + $this->transaction_id = $this->setup_transaction_id(); + $this->ip = $this->setup_ip(); + $this->customer_id = $this->setup_customer_id(); + $this->user_id = $this->setup_user_id(); + $this->email = $this->setup_email(); + $this->user_info = $this->setup_user_info(); + $this->address = $this->setup_address(); + $this->key = $this->setup_payment_key(); + $this->number = $this->setup_payment_number(); + $this->downloads = $this->setup_downloads(); + $this->has_unlimited_downloads = $this->setup_has_unlimited(); + $this->order = $this->_shim_order(); + } + + /** + * Get a post meta item for the payment + * + * @since 3.0 + * @param string $meta_key The Meta Key + * @param boolean $single Return single item or array + * @return mixed The value from the post meta + */ + private function get_meta( $meta_key = '_edd_payment_meta', $single = true ) { + + $meta = get_post_meta( $this->ID, $meta_key, $single ); + if ( '_edd_payment_meta' === $meta_key ) { + + if ( empty( $meta ) ) { + $meta = array(); + } + + // #5228 Fix possible data issue introduced in 2.6.12 + if ( is_array( $meta ) && isset( $meta[0] ) ) { + $bad_meta = $meta[0]; + unset( $meta[0] ); + + if ( is_array( $bad_meta ) ) { + $meta = array_merge( $meta, $bad_meta ); + } + + update_post_meta( $this->ID, '_edd_payment_meta', $meta ); + } + + // Payment meta was simplified in EDD v1.5, so these are here for backwards compatibility + if ( empty( $meta['key'] ) ) { + $meta['key'] = $this->setup_payment_key(); + } + + if ( empty( $meta['email'] ) ) { + $meta['email'] = $this->setup_email(); + } + + if ( empty( $meta['date'] ) ) { + $meta['date'] = get_post_field( 'post_date', $this->ID ); + } + } + + $meta = apply_filters( 'edd_get_payment_meta_' . $meta_key, $meta, $this->ID ); + + if ( is_serialized( $meta ) ) { + preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $meta, $matches ); + if ( ! empty( $matches ) ) { + $meta = array(); + } + } + + return apply_filters( 'edd_get_payment_meta', $meta, $this->ID, $meta_key ); + } + + /** + * Setup the payment completed date + * + * @since 3.0 + * @return string The date the payment was completed + */ + private function setup_completed_date() { + if ( in_array( $this->payment->post_status, array( 'pending', 'preapproved', 'processing' ), true ) ) { + return false; // This payment was never completed + } + $date = $this->get_meta( '_edd_completed_date', true ); + + // phpcs:ignore WordPress.PHP.DisallowShortTernary + return $date ?: $this->payment->date; + } + + /** + * Setup the payment mode + * + * @since 3.0 + * @return string The payment mode + */ + private function setup_mode() { + return $this->get_meta( '_edd_payment_mode' ); + } + + /** + * Setup the payment total + * + * @since 3.0 + * @return float The payment total + */ + private function setup_total() { + $amount = $this->get_meta( '_edd_payment_total', true ); + + if ( empty( $amount ) && '0.00' != $amount ) { + $meta = $this->get_meta( '_edd_payment_meta', true ); + $meta = maybe_unserialize( $meta ); + + if ( isset( $meta['amount'] ) ) { + $amount = $meta['amount']; + } + } + + return $amount; + } + + /** + * Setup the payment tax + * + * @since 3.0 + * @return float The tax for the payment + */ + private function setup_tax() { + $tax = $this->get_meta( '_edd_payment_tax', true ); + + // We don't have tax as its own meta and no meta was passed + if ( '' === $tax ) { + + $tax = isset( $this->payment_meta['tax'] ) ? $this->payment_meta['tax'] : 0; + + } + + return $tax; + + } + + /** + * Setup the payment tax rate + * + * @since 3.0 + * @return float The tax rate for the payment + */ + private function setup_tax_rate() { + return $this->get_meta( '_edd_payment_tax_rate', true ); + } + + /** + * Setup the payment fees + * + * @since 3.0 + * @return float The fees total for the payment + */ + private function setup_fees_total() { + $fees_total = (float) 0.00; + + $payment_fees = isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array(); + if ( ! empty( $payment_fees ) ) { + foreach ( $payment_fees as $fee ) { + $fees_total += (float) $fee['amount']; + } + } + + return $fees_total; + + } + + /** + * Setup the payment subtotal + * + * @since 3.0 + * @return float The subtotal of the payment + */ + private function setup_subtotal() { + $subtotal = 0; + $cart_details = $this->cart_details; + + if ( is_array( $cart_details ) ) { + + foreach ( $cart_details as $item ) { + + if ( isset( $item['subtotal'] ) ) { + + $subtotal += $item['subtotal']; + + } + } + } else { + + $subtotal = $this->total; + $tax = edd_use_taxes() ? $this->tax : 0; + $subtotal -= $tax; + + } + + return $subtotal; + } + + /** + * Setup the payments discount codes + * + * @since 3.0 + * @return array Array of discount codes on this payment + */ + private function setup_discounts() { + return ! empty( $this->payment_meta['user_info']['discount'] ) ? $this->payment_meta['user_info']['discount'] : array(); + } + + /** + * Setup the currency code + * + * @since 3.0 + * @return string The currency for the payment + */ + private function setup_currency() { + return isset( $this->payment_meta['currency'] ) ? $this->payment_meta['currency'] : apply_filters( 'edd_payment_currency_default', edd_get_currency(), $this ); + } + + /** + * Setup any fees associated with the payment + * + * @since 3.0 + * @return array The Fees + */ + private function setup_fees() { + return isset( $this->payment_meta['fees'] ) ? $this->payment_meta['fees'] : array(); + } + + /** + * Setup the gateway used for the payment + * + * @since 3.0 + * @return string The gateway + */ + private function setup_gateway() { + return $this->get_meta( '_edd_payment_gateway', true ); + } + + /** + * Setup the transaction ID + * + * @since 3.0 + * @return string The transaction ID for the payment + */ + private function setup_transaction_id() { + $transaction_id = $this->get_meta( '_edd_payment_transaction_id', true ); + + if ( empty( $transaction_id ) || (int) $transaction_id === (int) $this->ID ) { + + $gateway = $this->gateway; + $transaction_id = apply_filters( 'edd_get_payment_transaction_id-' . $gateway, $this->ID ); + + } + + return $transaction_id; + } + + /** + * Setup the IP Address for the payment + * + * @since 3.0 + * @return string The IP address for the payment + */ + private function setup_ip() { + return $this->get_meta( '_edd_payment_user_ip', true ); + } + + /** + * Setup the customer ID + * + * @since 3.0 + * @return int The Customer ID + */ + private function setup_customer_id() { + return $this->get_meta( '_edd_payment_customer_id', true ); + } + + /** + * Setup the User ID associated with the purchase + * + * @since 3.0 + * @return int The User ID + */ + private function setup_user_id() { + $user_id = $this->get_meta( '_edd_payment_user_id', true ); + $customer = new EDD_Customer( $this->customer_id ); + + // Make sure it exists, and that it matches that of the associated customer record + if ( ! empty( $customer->user_id ) && ( empty( $user_id ) || (int) $user_id !== (int) $customer->user_id ) ) { + + $user_id = $customer->user_id; + + // Backfill the user ID, or reset it to be correct in the event of data corruption + update_post_meta( $this->ID, '_edd_payment_user_id', $user_id ); + + } + + return $user_id; + } + + /** + * Setup the email address for the purchase + * + * @since 3.0 + * @return string The email address for the payment + */ + private function setup_email() { + $email = $this->get_meta( '_edd_payment_user_email', true ); + + if ( empty( $email ) ) { + $email = EDD()->customers->get_column( 'email', $this->customer_id ); + } + + return $email; + } + + /** + * Setup the user info + * + * @since 3.0 + * @return array The user info associated with the payment + */ + private function setup_user_info() { + $defaults = array( + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'discount' => $this->discounts, + ); + + $user_info = isset( $this->payment_meta['user_info'] ) ? $this->payment_meta['user_info'] : array(); + + if ( is_serialized( $user_info ) ) { + preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $user_info, $matches ); + if ( ! empty( $matches ) ) { + $user_info = array(); + } + } + + // As per Github issue #4248, we need to run maybe_unserialize here still. + $user_info = wp_parse_args( maybe_unserialize( $user_info ), $defaults ); + + // Ensure email index is in the old user info array + if ( empty( $user_info['email'] ) ) { + $user_info['email'] = $this->email; + } + + if ( empty( $user_info ) ) { + // Get the customer, but only if it's been created + $customer = new EDD_Customer( $this->customer_id ); + + if ( $customer->id > 0 ) { + $name = explode( ' ', $customer->name, 2 ); + $user_info = array( + 'first_name' => $name[0], + 'last_name' => $name[1], + 'email' => $customer->email, + 'discount' => 'none', + ); + } + } else { + // Get the customer, but only if it's been created + $customer = new EDD_Customer( $this->customer_id ); + if ( $customer->id > 0 ) { + foreach ( $user_info as $key => $value ) { + if ( ! empty( $value ) ) { + continue; + } + + switch ( $key ) { + case 'first_name': + $name = explode( ' ', $customer->name, 2 ); + + $user_info[ $key ] = $name[0]; + break; + + case 'last_name': + $name = explode( ' ', $customer->name, 2 ); + $last_name = ! empty( $name[1] ) ? $name[1] : ''; + + $user_info[ $key ] = $last_name; + break; + + case 'email': + $user_info[ $key ] = $customer->email; + break; + } + } + } + } + + return $user_info; + } + + /** + * Setup the Address for the payment + * + * @since 3.0 + * @return array The Address information for the payment + */ + private function setup_address() { + $address = ! empty( $this->payment_meta['user_info']['address'] ) ? $this->payment_meta['user_info']['address'] : array(); + $defaults = array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'country' => '', + 'state' => '', + 'zip' => '', + ); + + return wp_parse_args( $address, $defaults ); + } + + /** + * Setup the payment key + * + * @since 3.0 + * @return string The Payment Key + */ + private function setup_payment_key() { + return $this->get_meta( '_edd_payment_purchase_key', true ); + } + + /** + * Setup the payment number + * + * @since 3.0 + * @return int|string Integer by default, or string if sequential order numbers is enabled + */ + private function setup_payment_number() { + $number = false; + if ( edd_get_option( 'enable_sequential' ) ) { + $number = $this->get_meta( '_edd_payment_number', true ); + } + + return $number ?: $this->ID; + } + + /** + * Setup the cart details + * + * @since 3.0 + * @return array The cart details + */ + private function setup_cart_details() { + return isset( $this->payment_meta['cart_details'] ) ? maybe_unserialize( $this->payment_meta['cart_details'] ) : array(); + } + + /** + * Setup the downloads array + * + * @since 3.0 + * @return array Downloads associated with this payment + */ + private function setup_downloads() { + return isset( $this->payment_meta['downloads'] ) ? maybe_unserialize( $this->payment_meta['downloads'] ) : array(); + } + + /** + * Setup the Unlimited downloads setting + * + * @since 3.0 + * @return bool If this payment has unlimited downloads + */ + private function setup_has_unlimited() { + return (bool) $this->get_meta( '_edd_payment_unlimited_downloads', true ); + } + + /** + * Sets up the payment status. + * + * @since 3.0 + * @return string + */ + private function setup_status() { + return 'publish' === $this->payment->post_status ? 'complete' : $this->payment->post_status; + } + + /** + * Shims the payment, as much as possible, into an EDD Order object. + * @todo deprecate in 3.1 + * + * @return EDD\Orders\Order + */ + public function _shim_order() { + return new \EDD\Orders\Order( + array( + 'id' => $this->ID, + 'parent' => $this->payment->parent, + 'order_number' => $this->number, + 'status' => $this->status, + 'type' => 'sale', + 'user_id' => $this->user_id, + 'customer_id' => $this->customer_id, + 'email' => $this->email, + 'ip' => $this->ip, + 'gateway' => $this->gateway, + 'mode' => $this->mode, + 'currency' => $this->currency, + 'payment_key' => $this->key, + 'subtotal' => $this->subtotal, + 'discount' => $this->discounted_amount, + 'tax' => $this->tax, + 'total' => $this->total, + 'rate' => $this->get_order_tax_rate(), + 'date_created' => $this->payment->post_date_gmt, + 'date_modified' => $this->payment->post_modified, + 'date_completed' => $this->completed_date, + 'items' => $this->get_order_items(), + ) + ); + } + + /** + * Updates the payment tax rate to match the expected order tax rate. + * + * @since 3.0 + * @return float + */ + private function get_order_tax_rate() { + $tax_rate = (float) $this->tax_rate; + if ( $tax_rate < 1 ) { + $tax_rate = $tax_rate * 100; + } + + return $tax_rate; + } + + /** + * Gets an array of order item objects from the cart details. + * + * @since 3.0 + * @return array + */ + private function get_order_items() { + $order_items = array(); + if ( empty( $this->cart_details ) ) { + return $order_items; + } + foreach ( $this->cart_details as $key => $cart_item ) { + $product_name = isset( $cart_item['name'] ) + ? $cart_item['name'] + : ''; + $price_id = $this->get_valid_price_id_for_cart_item( $cart_item ); + if ( ! empty( $product_name ) ) { + $option_name = edd_get_price_option_name( $cart_item['id'], $price_id ); + if ( ! empty( $option_name ) ) { + $product_name .= ' — ' . $option_name; + } + } + $order_item_args = array( + 'order_id' => $this->ID, + 'product_id' => $cart_item['id'], + 'product_name' => $product_name, + 'price_id' => $price_id, + 'cart_index' => $key, + 'type' => 'download', + 'status' => $this->status, + 'quantity' => ! empty( $cart_item['quantity'] ) ? $cart_item['quantity'] : 1, + 'amount' => ! empty( $cart_item['item_price'] ) ? (float) $cart_item['item_price'] : (float) $cart_item['price'], + 'subtotal' => (float) $cart_item['subtotal'], + 'discount' => ! empty( $cart_item['discount'] ) ? (float) $cart_item['discount'] : 0.00, + 'tax' => $cart_item['tax'], + 'total' => (float) $cart_item['price'], + 'date_created' => $this->payment->post_date_gmt, + 'date_modified' => $this->payment->post_modified_gmt, + ); + + $order_items[] = new EDD\Orders\Order_Item( $order_item_args ); + } + + return $order_items; + } + + /** + * Retrieves a valid price ID for a given cart item. + * If the product does not have variable prices, then `null` is always returned. + * If the supplied price ID does not match a price ID that actually exists, then the default + * variable price is returned instead of the supplied one. + * + * @since 3.0 + * + * @param array $cart_item Array of cart item details. + * + * @return int|null + */ + private function get_valid_price_id_for_cart_item( $cart_item ) { + // If the product doesn't have variable prices, just return `null`. + if ( ! edd_has_variable_prices( $cart_item['id'] ) ) { + return null; + } + + $variable_prices = edd_get_variable_prices( $cart_item['id'] ); + if ( ! is_array( $variable_prices ) || empty( $variable_prices ) ) { + return null; + } + + // Get the price ID that's set to the cart item right now. + $price_id = isset( $cart_item['item_number']['options']['price_id'] ) && is_numeric( $cart_item['item_number']['options']['price_id'] ) + ? absint( $cart_item['item_number']['options']['price_id'] ) + : null; + + // Now let's confirm it's actually a valid price ID. + $variable_price_ids = array_map( 'intval', array_column( $variable_prices, 'index' ) ); + + return in_array( $price_id, $variable_price_ids, true ) ? $price_id : edd_get_default_variable_price( $cart_item['id'] ); + } +} diff --git a/includes/compat/class-log.php b/includes/compat/class-log.php new file mode 100644 index 00000000000..912f96cb86d --- /dev/null +++ b/includes/compat/class-log.php @@ -0,0 +1,344 @@ +{$key}; + break; + } + + if ( $this->show_notices ) { + _doing_it_wrong( 'get_post_meta()', 'All log postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_api_request_log() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + return $value; + } + + /** + * Listen for calls to update_post_meta for API request logs and see if we need to filter them. + * + * This is here for backwards compatibility purposes with the migration to custom tables in EDD 3.0. + * + * @since 3.0 + * + * @param mixed $check Comes in 'null' but if returned not null, WordPress Core will not interact with the + * postmeta table. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param mixed $meta_value The value get_post_meta would return if we don't filter. + * @param mixed $prev_value The previous value of the meta. + * @return mixed Returns 'null' if no action should be taken and WordPress core can continue, or non-null to avoid postmeta. + */ + public function api_request_log_update_post_meta( $check, $object_id, $meta_key, $meta_value, $prev_value ) { + $meta_keys = array( + '_edd_log_request_ip', + '_edd_log_user', + '_edd_log_key', + '_edd_log_token', + '_edd_log_time', + '_edd_log_version', + ); + + if ( ! in_array( $meta_key, $meta_keys, true ) ) { + return $check; + } + + $api_request_log = edd_get_api_request_log( $object_id ); + + if ( ! $api_request_log ) { + return $check; + } + + switch ( $meta_key ) { + case '_edd_log_request_ip': + case '_edd_log_user': + case '_edd_log_key': + case '_edd_log_token': + case '_edd_log_time': + case '_edd_log_version': + $key = str_replace( '_edd_log_', '', $meta_key ); + + switch ( $key ) { + case 'request_ip': + $key = 'ip'; + break; + case 'key': + $key = 'api_key'; + break; + case 'user': + $key = 'user_id'; + break; + } + + $check = edd_update_api_request_log( $object_id, array( + $key => $meta_value, + ) ); + break; + } + + if ( $this->show_notices ) { + _doing_it_wrong( 'add_post_meta()/update_post_meta()', 'All log postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_add_order_meta()/edd_update_order_meta()() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + return $check; + } + + /** + * Backwards compatibility filters for get_post_meta() calls on file download logs. + * + * @since 3.0 + * + * @param mixed $value The value get_post_meta would return if we don't filter. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param bool $single If a single value or an array of the value is requested. + * + * @return mixed The value to return. + */ + public function file_download_log_get_post_meta( $value, $object_id, $meta_key, $single ) { + if ( 'get_post_metadata' !== current_filter() ) { + $message = __( 'This function is not meant to be called directly. It is only here for backwards compatibility purposes.', 'easy-digital-downloads' ); + _doing_it_wrong( __FUNCTION__, $message, 'EDD 3.0' ); + } + + $meta_keys = array( + '_edd_log_user_info', + '_edd_log_user_id', + '_edd_log_file_id', + '_edd_log_ip', + '_edd_log_payment_id', + '_edd_log_price_id', + '_edd_log_customer_id', + ); + + if ( ! in_array( $meta_key, $meta_keys, true ) ) { + return $value; + } + + $file_download_log = edd_get_file_download_log( $object_id ); + + if ( ! $file_download_log ) { + return $value; + } + + switch ( $meta_key ) { + case '_edd_log_user_id': + case '_edd_log_file_id': + case '_edd_log_ip': + case '_edd_log_payment_id': + case '_edd_log_price_id': + case '_edd_log_customer_id': + $key = str_replace( '_edd_log_', '', $meta_key ); + + switch ( $key ) { + case 'request_ip': + $key = 'ip'; + break; + case 'key': + $key = 'api_key'; + break; + case 'user': + $key = 'user_id'; + break; + case 'payment_id': + $key = 'order_id'; + break; + } + + if ( isset( $file_download_log->{$key} ) ) { + $value = $file_download_log->{$key}; + } + + if ( 'user_id' === $key ) { + $customer = new \EDD_Customer( $file_download_log->customer_id ); + $value = ! empty( $customer->user_id ) ? $customer->user_id : 0; + } + break; + } + + if ( $this->show_notices ) { + _doing_it_wrong( 'get_post_meta()', __( 'All log postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_api_request_log() instead.', 'easy-digital-downloads' ), 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + return $value; + } + + /** + * Listen for calls to update_post_meta for file download logs and see if we need to filter them. + * + * This is here for backwards compatibility purposes with the migration to custom tables in EDD 3.0. + * + * @since 3.0 + * + * @param mixed $check Comes in 'null' but if returned not null, WordPress Core will not interact with + * the postmeta table. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param mixed $meta_value The value get_post_meta would return if we don't filter. + * @param mixed $prev_value The previous value of the meta + * + * @return mixed Returns 'null' if no action should be taken and WordPress core can continue, or non-null to avoid postmeta + */ + public function file_download_log_update_post_meta( $check, $object_id, $meta_key, $meta_value, $prev_value ) { + $meta_keys = array( + '_edd_log_user_info', + '_edd_log_user_id', + '_edd_log_file_id', + '_edd_log_ip', + '_edd_log_payment_id', + '_edd_log_price_id', + '_edd_log_customer_id', + ); + + if ( ! in_array( $meta_key, $meta_keys, true ) ) { + return $check; + } + + $file_download_log = edd_get_file_download_log( $object_id ); + + if ( ! $file_download_log ) { + return $check; + } + + switch ( $meta_key ) { + case '_edd_log_user_id': + case '_edd_log_file_id': + case '_edd_key_ip': + case '_edd_log_payment_id': + case '_edd_log_price_id': + case '_edd_log_customer_id': + $key = str_replace( '_edd_log_', '', $meta_key ); + + if ( 'payment_id' === $key ) { + $key = 'order_id'; + } + + $check = edd_update_file_download_log( $object_id, array( + $key => $meta_value, + ) ); + break; + } + + return $check; + } +} diff --git a/includes/compat/class-payment.php b/includes/compat/class-payment.php new file mode 100644 index 00000000000..2493a3ad5cf --- /dev/null +++ b/includes/compat/class-payment.php @@ -0,0 +1,313 @@ +posts} WHERE post_type = 'edd_payment' GROUP BY post_status"; + + if ( $expected === $query ) { + $query = "SELECT status AS post_status, COUNT( * ) AS num_posts FROM {$wpdb->edd_orders} GROUP BY post_status"; + } + + return $query; + } + + /** + * Add a message for anyone to trying to get payments via get_post/get_posts/WP_Query. + * Force filters to run for all queries that have `edd_discount` as the post type. + * + * This is here for backwards compatibility purposes with the migration to custom tables in EDD 3.0. + * + * @since 3.0 + * + * @param \WP_Query $query + */ + public function pre_get_posts( $query ) { + global $wpdb; + + if ( 'pre_get_posts' !== current_filter() ) { + $message = __( 'This function is not meant to be called directly. It is only here for backwards compatibility purposes.', 'easy-digital-downloads' ); + _doing_it_wrong( __FUNCTION__, $message, 'EDD 3.0' ); + } + + // Bail if not a payment + if ( 'edd_payment' !== $query->get( 'post_type' ) ) { + return; + } + + // Force filters to run + $query->set( 'suppress_filters', false ); + + // Setup doing-it-wrong message + $message = sprintf( + __( 'As of Easy Digital Downloads 3.0, orders no longer exist in the %1$s table. They have been migrated to %2$s. Orders should be accessed using %3$s or %4$s. See %5$s for more information.', 'easy-digital-downloads' ), + '' . $wpdb->posts . '', + '' . edd_get_component_interface( 'order', 'table' )->table_name . '', + 'edd_get_orders()', + 'edd_get_order()', + 'https://easydigitaldownloads.com/development/' + ); + + _doing_it_wrong( 'get_posts()/get_post()/WP_Query', $message, 'EDD 3.0' ); + } + + /** + * Backwards compatibility filters for get_post_meta() calls on payments. + * + * @since 3.0 + * + * @param mixed $value The value get_post_meta would return if we don't filter. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param bool $single If a single value or an array of the value is requested. + * + * @return mixed The value to return. + */ + public function get_post_metadata( $value, $object_id, $meta_key, $single ) { + + if ( 'get_post_metadata' !== current_filter() ) { + $message = __( 'This function is not meant to be called directly. It is only here for backwards compatibility purposes.', 'easy-digital-downloads' ); + _doing_it_wrong( __FUNCTION__, esc_html( $message ), 'EDD 3.0' ); + } + + // Bail early of not a back-compat key + if ( ! in_array( $meta_key, $this->get_meta_key_whitelist(), true ) ) { + return $value; + } + + // Bail if order does not exist + $order = $this->_shim_edd_get_order( $object_id ); + if ( empty( $order ) ) { + return $value; + } + + switch ( $meta_key ) { + case '_edd_payment_purchase_key': + $value = $order->payment_key; + break; + case '_edd_payment_transaction_id': + $value = $order->get_transaction_id(); + break; + case '_edd_payment_user_email': + $value = $order->email; + break; + case '_edd_payment_meta': + $p = edd_get_payment( $object_id ); + $value = array( $p->get_meta( '_edd_payment_meta' ) ); + break; + case '_edd_completed_date': + $value = $order->date_completed; + break; + case '_edd_payment_gateway': + $value = $order->gateway; + break; + case '_edd_payment_user_id': + $value = $order->user_id; + break; + case '_edd_payment_user_ip': + $value = $order->ip; + break; + case '_edd_payment_mode': + $value = $order->mode; + break; + case '_edd_payment_tax_rate': + $value = $order->get_tax_rate(); + /* + * Tax rates are now stored as percentages (e.g. `20.00`) but previously they were stored as + * decimals (e.g. `0.2`) so we convert it back to a decimal. + */ + if ( is_numeric( $value ) ) { + $value = $value / 100; + } + break; + case '_edd_payment_customer_id': + $value = $order->customer_id; + break; + case '_edd_payment_total': + $value = $order->total; + break; + case '_edd_payment_tax': + $value = $order->tax; + break; + case '_edd_payment_number': + $value = $order->get_number(); + break; + default : + $value = edd_get_order_meta( $order->id, $meta_key, true ); + break; + } + + if ( $this->show_notices ) { + _doing_it_wrong( 'get_post_meta()', 'All payment postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_order() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + return $value; + } + + /** + * Backwards compatibility filters for add/update_post_meta() calls on payments. + * + * @since 3.0 + * + * @param mixed $check Comes in 'null' but if returned not null, WordPress Core will not interact with the postmeta table. + * @param int $object_id The object ID post meta was requested for. + * @param string $meta_key The meta key requested. + * @param mixed $meta_value The value get_post_meta would return if we don't filter. + * @param mixed $prev_value The previous value of the meta + * + * @return mixed Returns 'null' if no action should be taken and WordPress core can continue, or non-null to avoid postmeta. + */ + public function update_post_metadata( $check, $object_id, $meta_key, $meta_value, $prev_value ) { + + // Bail early of not a back-compat key + if ( ! in_array( $meta_key, $this->get_meta_key_whitelist(), true ) ) { + return $check; + } + + // Bail if payment does not exist + $payment = edd_get_payment( $object_id ); + if ( empty( $payment ) ) { + return $check; + } + + $check = $payment->update_meta( $meta_key, $meta_value ); + + if ( $this->show_notices ) { + _doing_it_wrong( 'add_post_meta()/update_post_meta()', 'All payment postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_add_order_meta()/edd_update_order_meta()() instead.', 'EDD 3.0' ); + + if ( $this->show_backtrace ) { + $backtrace = debug_backtrace(); + trigger_error( print_r( $backtrace, 1 ) ); + } + } + + return $check; + } + + /** + * Retrieves a list of whitelisted meta keys that we want to catch in get/update post meta calls. + * + * @since 3.0 + * @return array + */ + private function get_meta_key_whitelist() { + $meta_keys = array( + '_edd_payment_purchase_key', + '_edd_payment_transaction_id', + '_edd_payment_meta', + '_edd_completed_date', + '_edd_payment_gateway', + '_edd_payment_user_id', + '_edd_payment_user_email', + '_edd_payment_user_ip', + '_edd_payment_mode', + '_edd_payment_tax_rate', + '_edd_payment_customer_id', + '_edd_payment_total', + '_edd_payment_tax', + '_edd_payment_number', + '_edd_sl_upgraded_payment_id', // EDD SL + '_edd_sl_is_renewal', // EDD SL + '_edds_stripe_customer_id', // EDD Stripe + ); + + /** + * Allows the whitelisted post meta keys to be filtered. Extensions should add their meta key(s) to this + * list if they want add/update/get post meta calls to be routed to order meta. + * + * @param array $meta_keys + * + * @since 3.0 + */ + $meta_keys = apply_filters( 'edd_30_post_meta_key_whitelist', $meta_keys ); + + return (array) $meta_keys; + } + + /** + * Gets the order from the database. + * This is a duplicate of edd_get_order, but is defined separately here + * for pending migration purposes. + * + * @todo deprecate in 3.1 + * + * @param int $order_id + * @return false|EDD\Orders\Order + */ + private function _shim_edd_get_order( $order_id ) { + $orders = new \EDD\Database\Queries\Order(); + + // Return order + return $orders->get_item( $order_id ); + } +} diff --git a/includes/compat/class-tax.php b/includes/compat/class-tax.php new file mode 100644 index 00000000000..a41b2953649 --- /dev/null +++ b/includes/compat/class-tax.php @@ -0,0 +1,97 @@ + $tax_rate['country'], + 'scope' => $scope, + 'amount' => floatval( $tax_rate['rate'] ), + 'description' => $region, + ); + + edd_add_tax_rate( $adjustment_data ); + } + + // Return the value so it is stored for backwards compatibility purposes. + return $value; + } +} diff --git a/includes/compat/class-template.php b/includes/compat/class-template.php new file mode 100644 index 00000000000..86924574b3f --- /dev/null +++ b/includes/compat/class-template.php @@ -0,0 +1,119 @@ +exists( $template ) && $wp_filesystem->is_writable( $template ) ) { + $contents = $wp_filesystem->get_contents( $template ); + + $get_post_call_exists = strstr( $contents, 'get_post( $edd_receipt_args[\'id\'] )' ); + + if ( false === $get_post_call_exists ) { + return; + } + + $contents = str_replace( 'get_post( $edd_receipt_args[\'id\'] )', 'edd_get_payment( $edd_receipt_args[\'id\'] )', $contents ); + $updated = $wp_filesystem->put_contents( $template, $contents ); + + // Only display a notice if we could not update the file. + if ( ! $updated ) { + add_action( 'admin_notices', function() use ( $template ) { + ?> +
    +

    +

    ', '' ); ?>

    +

    +
    + $name, + 'schema' => '\\EDD\\Database\\Schema', + 'table' => '\\EDD\\Database\\Table', + 'query' => '\\EDD\\Database\\Query', + 'object' => '\\EDD\\Database\\Row', + 'meta' => false + ) ); + + // Setup the component + EDD()->components[ $name ] = new EDD\Component( $r ); + + // Component registered + do_action( 'edd_registered_component', $name, $r, $args ); +} + +/** + * Get an EDD Component object + * + * @since 3.0 + * @param string $name + * + * @return EDD\Component|false False if not exists, EDD\Component if exists + */ +function edd_get_component( $name = '' ) { + $name = sanitize_key( $name ); + + // Return component if exists, or false + return isset( EDD()->components[ $name ] ) + ? EDD()->components[ $name ] + : false; +} + +/** + * Get an EDD Component interface + * + * @since 3.0 + * @param string $component + * @param string $interface + * + * @return mixed False if not exists, EDD Component interface if exists + */ +function edd_get_component_interface( $component = '', $interface = '' ) { + + // Get component + $c = edd_get_component( $component ); + + // Bail if no component + if ( empty( $c ) ) { + return $c; + } + + // Return interface, or false if not exists + return $c->get_interface( $interface ); +} + +/** + * Setup all EDD components + * + * @since 3.0 + */ +function edd_setup_components() { + static $setup = false; + + // Never register components more than 1 time per request + if ( false !== $setup ) { + return; + } + + // Register customer. + edd_register_component( 'customer', array( + 'schema' => '\\EDD\\Database\\Schemas\\Customers', + 'table' => '\\EDD\\Database\\Tables\\Customers', + 'meta' => '\\EDD\\Database\\Tables\\Customer_Meta', + 'query' => '\\EDD\\Database\\Queries\\Customer', + 'object' => 'EDD_Customer' + ) ); + + // Register customer address. + edd_register_component( 'customer_address', array( + 'schema' => '\\EDD\\Database\\Schemas\\Customer_Addresses', + 'table' => '\\EDD\\Database\\Tables\\Customer_Addresses', + 'query' => '\\EDD\\Database\\Queries\\Customer_Address', + 'object' => '\\EDD\\Customers\\Customer_Address', + 'meta' => false + ) ); + + // Register customer email address. + edd_register_component( 'customer_email_address', array( + 'schema' => '\\EDD\\Database\\Schemas\\Customer_Email_Addresses', + 'table' => '\\EDD\\Database\\Tables\\Customer_Email_Addresses', + 'query' => '\\EDD\\Database\\Queries\\Customer_Email_Address', + 'object' => '\\EDD\\Customers\\Customer_Email_Address', + 'meta' => false + ) ); + + // Register adjustment. + edd_register_component( 'adjustment', array( + 'schema' => '\\EDD\\Database\\Schemas\\Adjustments', + 'table' => '\\EDD\\Database\\Tables\\Adjustments', + 'meta' => '\\EDD\\Database\\Tables\\Adjustment_Meta', + 'query' => '\\EDD\\Database\\Queries\\Adjustment', + 'object' => '\\EDD\\Adjustments\\Adjustment' + ) ); + + // Register note. + edd_register_component( 'note', array( + 'schema' => '\\EDD\\Database\\Schemas\\Notes', + 'table' => '\\EDD\\Database\\Tables\\Notes', + 'meta' => '\\EDD\\Database\\Tables\\Note_Meta', + 'query' => '\\EDD\\Database\\Queries\\Note', + 'object' => '\\EDD\\Notes\\Note' + ) ); + + // Register order. + edd_register_component( 'order', array( + 'schema' => '\\EDD\\Database\\Schemas\\Orders', + 'table' => '\\EDD\\Database\\Tables\\Orders', + 'meta' => '\\EDD\\Database\\Tables\\Order_Meta', + 'query' => '\\EDD\\Database\\Queries\\Order', + 'object' => '\\EDD\\Orders\\Order' + ) ); + + // Register order item. + edd_register_component( 'order_item', array( + 'schema' => '\\EDD\\Database\\Schemas\\Order_Items', + 'table' => '\\EDD\\Database\\Tables\\Order_Items', + 'meta' => '\\EDD\\Database\\Tables\\Order_Item_Meta', + 'query' => '\\EDD\\Database\\Queries\\Order_Item', + 'object' => '\\EDD\\Orders\\Order_Item' + ) ); + + // Register order adjustment. + edd_register_component( 'order_adjustment', array( + 'schema' => '\\EDD\\Database\\Schemas\\Order_Adjustments', + 'table' => '\\EDD\\Database\\Tables\\Order_Adjustments', + 'meta' => '\\EDD\\Database\\Tables\\Order_Adjustment_Meta', + 'query' => '\\EDD\\Database\\Queries\\Order_Adjustment', + 'object' => '\\EDD\\Orders\\Order_Adjustment', + ) ); + + // Register order address. + edd_register_component( 'order_address', array( + 'schema' => '\\EDD\\Database\\Schemas\\Order_Addresses', + 'table' => '\\EDD\\Database\\Tables\\Order_Addresses', + 'query' => '\\EDD\\Database\\Queries\\Order_Address', + 'object' => '\\EDD\\Orders\\Order_Address', + 'meta' => false + ) ); + + // Register order transaction. + edd_register_component( 'order_transaction', array( + 'schema' => '\\EDD\\Database\\Schemas\\Order_Transactions', + 'table' => '\\EDD\\Database\\Tables\\Order_Transactions', + 'query' => '\\EDD\\Database\\Queries\\Order_Transaction', + 'object' => '\\EDD\\Orders\\Order_Transaction', + 'meta' => false + ) ); + + // Register log. + edd_register_component( 'log', array( + 'schema' => '\\EDD\\Database\\Schemas\\Logs', + 'table' => '\\EDD\\Database\\Tables\\Logs', + 'meta' => '\\EDD\\Database\\Tables\\Log_Meta', + 'query' => '\\EDD\\Database\\Queries\\Log', + 'object' => '\\EDD\\Logs\\Log' + ) ); + + // Register log API request. + edd_register_component( 'log_api_request', array( + 'schema' => '\\EDD\\Database\\Schemas\\Logs_Api_Requests', + 'table' => '\\EDD\\Database\\Tables\\Logs_Api_Requests', + 'meta' => '\\EDD\\Database\\Tables\\Logs_Api_Request_Meta', + 'query' => '\\EDD\\Database\\Queries\\Log_Api_Request', + 'object' => '\\EDD\\Logs\\Api_Request_Log', + ) ); + + // Register log file download. + edd_register_component( 'log_file_download', array( + 'schema' => '\\EDD\\Database\\Schemas\\Logs_File_Downloads', + 'table' => '\\EDD\\Database\\Tables\\Logs_File_Downloads', + 'meta' => '\\EDD\\Database\\Tables\\Logs_File_Download_Meta', + 'query' => '\\EDD\\Database\\Queries\\Log_File_Download', + 'object' => '\\EDD\\Logs\\File_Download_Log', + ) ); + + edd_register_component( 'notification', array( + 'schema' => '\\EDD\\Database\\Schemas\\Notifications', + 'table' => '\\EDD\\Database\\Tables\\Notifications', + 'query' => '\\EDD\\Database\\Queries\\Notification', + 'object' => '\\EDD\\Notifications\\Notification', + 'meta' => false, + ) ); + + // Set the locally static setup var. + $setup = true; + + // Action to allow third party components to be setup. + do_action( 'edd_setup_components' ); +} + +/** + * Install all component database tables + * + * This function installs all database tables used by all components (including + * third-party and add-ons that use the Component API) + * + * This is used by unit tests and tools. + * + * @since 3.0 + */ +function edd_install_component_database_tables() { + + // Get the components + $components = EDD()->components; + + // Bail if no components setup yet + if ( empty( $components ) ) { + return; + } + + // Drop all component tables + foreach ( $components as $component ) { + + // Objects + $object = $component->get_interface( 'table' ); + if ( $object instanceof \EDD\Database\Table && ! $object->exists() ) { + $object->install(); + } + + // Meta + $meta = $component->get_interface( 'meta' ); + if ( $meta instanceof \EDD\Database\Table && ! $meta->exists() ) { + $meta->install(); + } + } +} + +/** + * Uninstall all component database tables + * + * This function is destructive and disastrous, so do not call it directly + * unless you fully intend to destroy all data (including third-party add-ons + * that use the Component API) + * + * This is used by unit tests and tools. + * + * @since 3.0 + */ +function edd_uninstall_component_database_tables() { + + // Get the components + $components = EDD()->components; + + // Bail if no components setup yet + if ( empty( $components ) ) { + return; + } + + // Drop all component tables + foreach ( $components as $component ) { + + // Objects + $object = $component->get_interface( 'table' ); + if ( $object instanceof \EDD\Database\Table && $object->exists() ) { + $object->uninstall(); + } + + // Meta + $meta = $component->get_interface( 'meta' ); + if ( $meta instanceof \EDD\Database\Table && $meta->exists() ) { + $meta->uninstall(); + } + } +} diff --git a/includes/country-functions.php b/includes/country-functions.php new file mode 100755 index 00000000000..68fcecf759d --- /dev/null +++ b/includes/country-functions.php @@ -0,0 +1,420 @@ +get_states( $country ); + + return apply_filters( 'edd_shop_states', $states, $country ); +} + +/** + * Get Country List + * + * @since 1.0 + * @return array $countries A list of the available countries + */ +function edd_get_country_list() { + return apply_filters( 'edd_countries', include EDD_PLUGIN_DIR . 'i18n/countries.php' ); +} + +/** + * Get States List + * + * @since 1.2 + * @return array + */ +function edd_get_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_us_states', $countries->get_states( 'US' ) ); +} + +/** + * Get Angola States + * + * @since 2.8.5 + * @return array $states A list of states + */ +function edd_get_angola_provinces_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_angola_provinces', $countries->get_states( 'AO' ) ); +} + +/** + * Get Provinces List + * + * @since 1.2 + * @return array + */ +function edd_get_provinces_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_canada_provinces', $countries->get_states( 'CA' ) ); +} + +/** + * Get Australian States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_australian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_australian_states', $countries->get_states( 'AU' ) ); +} + +/** + * Get Bangladeshi States (districts) + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_bangladeshi_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_bangladeshi_states', $countries->get_states( 'BD' ) ); +} + +/** + * Get Brazil States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_brazil_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_brazil_states', $countries->get_states( 'BR' ) ); +} + +/** + * Get Bulgarian States + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_bulgarian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_bulgarian_states', $countries->get_states( 'BG' ) ); +} + +/** + * Get Hong Kong States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_hong_kong_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_hong_kong_states', $countries->get_states( 'HK' ) ); +} + +/** + * Get Hungary States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_hungary_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_hungary_states', $countries->get_states( 'HU' ) ); +} + +/** + * Get Japanese States + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_japanese_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_japanese_states', $countries->get_states( 'JP' ) ); +} + +/** + * Get Chinese States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_chinese_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_chinese_states', $countries->get_states( 'CN' ) ); +} + +/** + * Get United Kingdom States + * + * @since 2.9 + * @return array $states A list of states + */ +function edd_get_united_kingdom_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_united_kingdom_states', $countries->get_states( 'GB' ) ); +} + +/** + * Get New Zealand States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_new_zealand_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_new_zealand_states', $countries->get_states( 'NZ' ) ); +} + +/** + * Get Peruvian States + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_peruvian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_peruvian_states', $countries->get_states( 'PE' ) ); +} + +/** + * Get Indonesian States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_indonesian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_indonesia_states', $countries->get_states( 'ID' ) ); +} + +/** + * Get Indian States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_indian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_indian_states', $countries->get_states( 'IN' ) ); +} + +/** + * Get Iranian States + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_iranian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_iranian_states', $countries->get_states( 'IR' ) ); +} + +/** + * Get Italian Provinces + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_italian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_italian_states', $countries->get_states( 'IT' ) ); +} + +/** + * Get Malaysian States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_malaysian_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_malaysian_states', $countries->get_states( 'MY' ) ); +} + +/** + * Get Mexican States + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_mexican_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_mexican_states', $countries->get_states( 'MX' ) ); +} + +/** + * Get Nepalese States (Districts) + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_nepalese_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_nepalese_states', $countries->get_states( 'NP' ) ); +} + +/** + * Get South African States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_south_african_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_south_african_states', $countries->get_states( 'ZA' ) ); +} + +/** + * Get Thailand States + * + * @since 1.6 + * @return array $states A list of states + */ +function edd_get_thailand_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_thailand_states', $countries->get_states( 'TH' ) ); +} + +/** + * Get Turkey States + * + * @since 2.2.3 + * @return array $states A list of states + */ +function edd_get_turkey_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_turkey_states', $countries->get_states( 'TR' ) ); +} + +/** + * Get Spain States + * + * @since 2.2 + * @return array $states A list of states + */ +function edd_get_spain_states_list() { + $countries = new EDD\Utils\Countries(); + + return apply_filters( 'edd_spain_states', $countries->get_states( 'ES' ) ); +} + +/** + * Returns a list of Netherland's provinces. + * + * @since 3.0 + * @return array $states A list of Netherland's provinces. + */ +function edd_get_netherlands_provinces_list() { + $countries = new EDD\Utils\Countries(); + + /** + * Filters the list of Netherland's provinces. + * + * @since 3.0 + * + * @param array $states A list of Netherland's provinces. + */ + return apply_filters( 'edd_netherlands_provinces', $countries->get_states( 'NL' ) ); +} + +/** + * Given a country code, return the country name + * + * @since 2.8.7 + * @param string $country_code The ISO Code for the country + * + * @return string + */ +function edd_get_country_name( $country_code = '' ) { + $country_list = edd_get_country_list(); + $country_name = isset( $country_list[ $country_code ] ) ? $country_list[ $country_code ] : $country_code; + + return apply_filters( 'edd_get_country_name', $country_name, $country_code ); +} + +/** + * Given a country and state code, return the state name + * + * @since 2.9 + * @param string $country_code The ISO Code for the country + * @param string $state_code The ISO Code for the state + * + * @return string + */ +function edd_get_state_name( $country_code = '', $state_code = '' ) { + if ( empty( $country_code ) ) { + $country_code = edd_get_shop_country(); + } + $countries = new EDD\Utils\Countries(); + $state_name = $countries->get_state_name( $country_code, $state_code ); + + return apply_filters( 'edd_get_state_name', $state_name, $state_code ); +} diff --git a/includes/css/colorbox.css b/includes/css/colorbox.css deleted file mode 100644 index 9308e797a43..00000000000 --- a/includes/css/colorbox.css +++ /dev/null @@ -1,52 +0,0 @@ -/* - ColorBox Core Style: - The following CSS is consistent between example themes and should not be altered. -*/ -#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} -#cboxOverlay{position:fixed; width:100%; height:100%;} -#cboxMiddleLeft, #cboxBottomLeft{clear:left;} -#cboxContent{position:relative;} -#cboxLoadedContent{overflow:auto;} -#cboxTitle{margin:0;} -#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;} -#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} -.cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:none;} -.cboxIframe{width:100%; height:100%; display:block; border:0;} -#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box;} - -/* - User Style: - Change the following styles to modify the appearance of ColorBox. They are - ordered & tabbed in a way that represents the nesting of the generated HTML. -*/ -#cboxOverlay{background:#000;} -#colorbox{} - #cboxTopLeft{width:14px; height:14px; background:url(../images/colorbox/controls.png) no-repeat 0 0;} - #cboxTopCenter{height:14px; background:url(../images/colorbox/border.png) repeat-x top left;} - #cboxTopRight{width:14px; height:14px; background:url(../images/colorbox/controls.png) no-repeat -36px 0;} - #cboxBottomLeft{width:14px; height:43px; background:url(../images/colorbox/controls.png) no-repeat 0 -32px;} - #cboxBottomCenter{height:43px; background:url(../images/colorbox/border.png) repeat-x bottom left;} - #cboxBottomRight{width:14px; height:43px; background:url(../images/colorbox/controls.png) no-repeat -36px -32px;} - #cboxMiddleLeft{width:14px; background:url(../images/colorbox/controls.png) repeat-y -175px 0;} - #cboxMiddleRight{width:14px; background:url(../images/colorbox/controls.png) repeat-y -211px 0;} - #cboxContent{background:#fff; overflow:visible;} - .cboxIframe{background:#fff;} - #cboxError{padding:50px; border:1px solid #ccc;} - #cboxLoadedContent{margin-bottom:5px;} - #cboxLoadingOverlay{background:url(../images/colorbox/loading_background.png) no-repeat center center;} - #cboxLoadingGraphic{background:url(../images/colorbox/loading.gif) no-repeat center center;} - #cboxTitle{position:absolute; bottom:-25px; left:0; text-align:center; width:100%; font-weight:bold; color:#7C7C7C;} - #cboxCurrent{position:absolute; bottom:-25px; left:58px; font-weight:bold; color:#7C7C7C;} - - #cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{position:absolute; bottom:-29px; background:url(../images/colorbox/controls.png) no-repeat 0px 0px; width:23px; height:23px; text-indent:-9999px;} - #cboxPrevious{left:0px; background-position: -51px -25px;} - #cboxPrevious:hover{background-position:-51px 0px;} - #cboxNext{left:27px; background-position:-75px -25px;} - #cboxNext:hover{background-position:-75px 0px;} - #cboxClose{right:0; background-position:-100px -25px;} - #cboxClose:hover{background-position:-100px 0px;} - - .cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px; right:27px;} - .cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px;} - .cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px; right:27px;} - .cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px;} \ No newline at end of file diff --git a/includes/css/edd-admin.css b/includes/css/edd-admin.css deleted file mode 100755 index 0cb815f7a1d..00000000000 --- a/includes/css/edd-admin.css +++ /dev/null @@ -1,54 +0,0 @@ -/** - * EDD Admin CSS - * - * @package Easy Digital Downloads - * @subpackage Admin CSS - * @copyright Copyright (c) 2012, Pippin Williamson - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License -*/ - -/* =Add Ons Styles --------------------------------------------------------------- */ -#edd-add-ons h2 { margin: 0 0 15px; } -#edd-add-ons .edd-extension { float: left; margin: 0 30px 30px 0; background: #f0f0f0; border: 1px solid #ccc; width: 180px; padding: 8px; height: 315px; position: relative; } -#edd-add-ons .edd-extension h3 { margin: 0 0 8px; } -#edd-add-ons .edd-extension .button-secondary { position: absolute; bottom: 8px; left: 8px; } -#edd-add-ons .edd-browse-all { clear:both; width:100%; } - -/* Payment History Styles --------------------------------------------------------------- */ -tr.status-refunded td { background: #cecece; border-top-color: #ccc; } - -/* Metabox Styles --------------------------------------------------------------- */ -.edd_file_help_labels { } -.edd_files_name_label { width: 225px; float: left; } -.edd_files_url_label { width: 220px; float: left; } -#postbox-container-1 .edd_files_name_label { width: 80px; } -#postbox-container-1 .edd_files_url_label { width: 80px; } - -.edd_remove_repeatable { margin: 11px 0 0 0; cursor: pointer; width: 10px; height: 10px; display: inline-block; text-indent: -9999px; overflow: hidden; } -.edd_remove_repeatable:active, .edd_remove_repeatable:hover { background-position: -10px 0!important } - -textarea#edd_product_notes { width: 100%; height: 4em; margin: 0; } - - -/* Dashboard Widget Styles --------------------------------------------------------------- */ -#edd_dashboard_sales .table { margin: 20px 0 0 0; padding: 0; position: relative; } -#edd_dashboard_sales .table_current_month { float: left; width: 45%; border-top: 1px solid #ececec; } -#edd_dashboard_sales .table_totals { float: right; width: 45%; border-top: 1px solid #ececec; } -#edd_dashboard_sales p.sub { margin: -7px -14px !important; padding: 5px 0 15px; color: #8f8f8f; font-size: 14px; position: absolute; top: -22px; left: 15px; } -#edd_dashboard_sales .inside { font-size: 12px; } -#edd_dashboard_sales table td { padding: 3px 0; white-space: nowrap; } -#edd_dashboard_sales td.b { font-size: 14px; font-family: Georgia,"Times New Roman","Bitstream Charter",Times,serif; text-align: right; padding-right: 6px; width: auto; } -#edd_dashboard_sales .t { font-size: 12px; padding-right: 12px; padding-top: 6px; color: #777; } - -.edd_add_repeatable { display: inline-block; } -#downloadinformation .edd_meta_table_wrap table input, -#downloadinformation .edd_meta_table_wrap table select, -#downloadinformation .edd_meta_table_wrap table textarea { margin: 4px 0; width: 100%; } -#downloadinformation .edd_meta_table_wrap table .submit input { width: auto; } -.edd_repeatable_upload_wrapper td { position: relative; } -.edd_upload_file { position: absolute; top: 9px; right: 9px; padding: 3px 8px 2px; display: block; background: #fff; } - diff --git a/includes/css/edd.css b/includes/css/edd.css deleted file mode 100755 index 96f3da76fcf..00000000000 --- a/includes/css/edd.css +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Easy Digital Downloads Styles - * - * @package Easy Digital Downloads - * @subpackage CSS - * @copyright Copyright (c) 2012, Pippin Williamson - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License -*/ - -/* =Checkout Form --------------------------------------------------------------- */ -.edd_clearfix:after { display: block; visibility: hidden; float: none; clear: both; text-indent: -9999px; content: "."; } - -/* cart contents */ -#edd_checkout_cart { text-align: left; width: 100%; border-top: 1px solid #f0f0f0; border-bottom: none; margin: 0 0 10px; } -#edd_checkout_cart th, -#edd_checkout_cart td { text-align: left; padding: 3px 5px; border-bottom: 1px solid #f0f0f0; border-top: none; color: #666; } -#edd_checkout_cart th { font-weight: bold; background: #f5f5f5; } -#edd_checkout_cart td { line-height: 25px; vertical-align: middle; } -#edd_checkout_cart th.edd_cart_actions, -#edd_checkout_cart td.edd_cart_actions, -#edd_checkout_cart th:last-child, -#edd_checkout_cart td:last-child, -#edd_checkout_cart th.edd_cart_total { text-align: right; } -#edd_checkout_cart td img { float: left; margin: 0 8px 0 0; background: none; padding: none; border: none; } - -/* checkout fields */ -#edd_purchase_form input.edd-input { display: inline-block; width: 200px; } -#edd_purchase_form input.edd-input.error { border-color: #c4554e; } -#edd_purchase_form label { display: inline-block; width: 200px; } -#edd_purchase_form input.card-expiry-month { width: 30px; } -#edd_purchase_form input.card-expiry-year { width: 60px; } -#edd_purchase_form span.exp-divider { } -#edd_checkout_form_wrap p { margin: 0 0 10px; } -#edd_checkout_form_wrap input[type="text"], -#edd_checkout_form_wrap input[type="email"], -#edd_checkout_form_wrap input[type="password"] { padding: 4px 6px; width: 200px; } -#edd_checkout_form_wrap .edd-payment-icons { height: 32px; } -#edd_checkout_form_wrap .edd-payment-icons img.payment-icon{ margin: 0 3px 0 0; float: left; background: none; padding: none; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none;} -#edd_checkout_form_wrap fieldset { border: none; padding: 0; margin: 0; } -#edd_checkout_form_wrap fieldset#edd_register_account_fields, -#edd_checkout_form_wrap fieldset#edd_login_fields { border: 1px solid #f0f0f0; padding: 10px; margin: 0 0 10px; } -#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_register_password, -#edd_checkout_form_wrap fieldset#edd_register_account_fields p.edd_login_password { margin: 0; } -#edd_checkout_form_wrap fieldset#edd_register_account_fields input[type="text"], -#edd_checkout_form_wrap fieldset#edd_register_account_fields input[type="email"], -#edd_checkout_form_wrap fieldset#edd_register_account_fields input[type="password"] { width: 188px; } - - -/* =Theme Specific styling --------------------------------------------------------------- */ - -/* Twenty Twelve */ -#edd_purchase_form input.edd-input.required { color: #000; } - - -/* =Cart Widget --------------------------------------------------------------- */ - - - -/* =Purchase History --------------------------------------------------------------- */ -#edd_user_history { text-align: left; width: 100%; border-top: 1px solid #f0f0f0; border-bottom: none;} -#edd_user_history th, -#edd_user_history td { text-align: left; padding: 3px 5px; border-bottom: 1px solid #f0f0f0; border-top: none; } -#edd_user_history th { font-weight: bold; background: #f5f5f5;} -#edd_user_history td { line-height: 25px; vertical-align: middle; } - -/* =Registration Form --------------------------------------------------------------- */ -#edd_registration_form .edd-input { display: inline-block; width: 200px; } -#edd_registration_form label { display: inline-block; width: 200px; } - -/* =Alerts --------------------------------------------------------------- */ -.edd_added_to_cart_alert { padding: 5px; font-size: 14px; border: 1px solid #046a9e; background: #9ecce2; color: #333; margin: 8px 0;} -.edd_added_to_cart_alert a.edd_alert_checkout_link { color: #000 !important; } - -/* =Purchase buttons --------------------------------------------------------------- */ -input.edd_submit_plain { background: none !important; border: none !important; padding: 0 !important; display: inline; cursor: pointer; } -.edd_price_options { margin: 0 0 15px; } - -/* general */ -.edd_button { - display: inline-block; *display: block; zoom: 1; position: relative; - height: 32px; line-height: 28px; text-decoration: none; color: #555; cursor: pointer; -} -.edd_button_outer { - padding: 0 0 0 5px; display: block; height: 32px; line-height: 26px; - background: url(../images/button-start.png) no-repeat; -} -.edd_button_outer br, .edd_button_outer p { display: none !important; } -.edd_button_inner { - display: block; height: 32px; background: url(../images/button-end.png) no-repeat right top; - padding: 0 4px 0 0; -} -.edd_button_text, input.edd_button_text, input#edd-purchase-button, input#edd_next_button { - display: block; height: 32px !important; background-image: url(../images/button-mid.png) !important; - padding: 0 4px !important; margin: 0; border: none !important; text-shadow: none; color: #555; width: auto; - -webkit-border-radius: 0 !important; -moz-border-radius: 0 !important; border-radius: 0 !important; cursor: pointer; -} -input.edd_button_text, input#edd-purchase-button, input#edd_next_button { - padding: 0 4px 4px !important; margin: 0 !important; -} -.edd_button:hover, input.edd_button_text:hover, input#edd-purchase-button:hover, input#edd_next_button:hover { - filter: alpha(opacity=100) !important; -moz-opacity: 1 !important; -khtml-opacity: 1 !important; opacity: 1 !important; -} -.edd_button:active, input.edd_button_text:active, input#edd-purchase-button:active, input#edd_next_button:active { - line-height: 30px; -} - -/* gray */ -.edd_button.edd_gray { color: #555; text-shadow: 0 0 1px #fff; } -.edd_button.edd_gray:hover .edd_button_outer, -.edd_button.edd_gray:hover .edd_button_text, -.edd_button.edd_gray:hover input#edd-purchase-button, -.edd_button.edd_gray:hover input#edd_next_button { background-position: 0 -33px !important; } -.edd_button.edd_gray:hover .edd_button_inner { background-position: right -33px !important; } -.edd_button.edd_gray:active .edd_button_outer, -.edd_button.edd_gray:active .edd_button_text, -.edd_button.edd_gray:active input#edd-purchase-button, -.edd_button.edd_gray:active input#edd_next_button { background-position: 0 -65px !important; } -.edd_button.edd_gray:active .edd_button_inner { background-position: right -65px !important; } - -/* pink */ -.edd_button.edd_pink { color: #913944; text-shadow: 0 0 1px #f9a0ad; } -.edd_button.edd_pink .edd_button_outer, -.edd_button.edd_pink .edd_button_text { background-position: 0 -98px !important; } -.edd_button.edd_pink .edd_button_inner { background-position: right -98px !important; } -.edd_button.edd_pink:hover .edd_button_outer, -.edd_button.edd_pink:hover .edd_button_text { background-position: 0 -132px !important; } -.edd_button.edd_pink:hover .edd_button_inner { background-position: right -132px !important; } -.edd_button.edd_pink:active .edd_button_outer, -.edd_button.edd_pink:active .edd_button_text { background-position: 0 -164px !important; } -.edd_button.edd_pink:active .edd_button_inner { background-position: right -164px !important; } - -/* blue */ -.edd_button.edd_blue { color: #42788e; text-shadow: 0 0 1px #99cade; } -.edd_button.edd_blue .edd_button_outer, -.edd_button.edd_blue .edd_button_text { background-position: 0 -196px !important; } -.edd_button.edd_blue .edd_button_inner { background-position: right -196px !important; } -.edd_button.edd_blue:hover .edd_button_outer, -.edd_button.edd_blue:hover .edd_button_text { background-position: 0 -230px !important; } -.edd_button.edd_blue:hover .edd_button_inner { background-position: right -230px !important; } -.edd_button.edd_blue:active .edd_button_outer, -.edd_button.edd_blue:active .edd_button_text { background-position: 0 -262px !important; } -.edd_button.edd_blue:active .edd_button_inner { background-position: right -262px !important; } - -/* green */ -.edd_button.edd_green { color: #5d7731; text-shadow: 0 0 1px #d0e5a4; } -.edd_button.edd_green .edd_button_outer, -.edd_button.edd_green .edd_button_text { background-position: 0 -294px !important; } -.edd_button.edd_green .edd_button_inner { background-position: right -294px !important; } -.edd_button.edd_green:hover .edd_button_outer, -.edd_button.edd_green:hover .edd_button_text { background-position: 0 -328px !important; } -.edd_button.edd_green:hover .edd_button_inner { background-position: right -328px !important; } -.edd_button.edd_green:active .edd_button_outer, -.edd_button.edd_green:active .edd_button_text { background-position: 0 -360px !important; } -.edd_button.edd_green:active .edd_button_inner { background-position: right -360px !important; } - -/* teal */ -.edd_button.edd_teal { color: #437b7d; text-shadow: 0 0 1px #bef3f5; } -.edd_button.edd_teal .edd_button_outer, -.edd_button.edd_teal .edd_button_text { background-position: 0 -391px !important; } -.edd_button.edd_teal .edd_button_inner { background-position: right -391px !important; } -.edd_button.edd_teal:hover .edd_button_outer, -.edd_button.edd_teal:hover .edd_button_text { background-position: 0 -424px !important; } -.edd_button.edd_teal:hover .edd_button_inner { background-position: right -424px !important; } -.edd_button.edd_teal:active .edd_button_outer, -.edd_button.edd_teal:active .edd_button_text { background-position: 0 -457px !important; } -.edd_button.edd_teal:active .edd_button_inner { background-position: right -457px !important; } - -/* black */ -.edd_button.edd_black { color: #fff; text-shadow: 0 0 1px #2f2f2f; } -.edd_button.edd_black .edd_button_outer, -.edd_button.edd_black .edd_button_text { background-position: 0 -489px !important; color: #fff; } -.edd_button.edd_black .edd_button_inner { background-position: right -489px !important; } -.edd_button.edd_black:hover .edd_button_outer, -.edd_button.edd_black:hover .edd_button_text { background-position: 0 -521px !important; } -.edd_button.edd_black:hover .edd_button_inner { background-position: right -521px !important; } -.edd_button.edd_black:active .edd_button_outer, -.edd_button.edd_black:active .edd_button_text { background-position: 0 -552px !important; } -.edd_button.edd_black:active .edd_button_inner { background-position: right -552px !important; } - -/* dark gray */ -.edd_button.edd_dark_gray { color: #555; text-shadow: 0 0 1px #d6d6d6; } -.edd_button.edd_dark_gray .edd_button_outer, -.edd_button.edd_dark_gray .edd_button_text { background-position: 0 -584px !important; } -.edd_button.edd_dark_gray .edd_button_inner { background-position: right -584px !important; } -.edd_button.edd_dark_gray:hover .edd_button_outer, -.edd_button.edd_dark_gray:hover .edd_button_text { background-position: 0 -617px !important; } -.edd_button.edd_dark_gray:hover .edd_button_inner { background-position: right -617px !important; } -.edd_button.edd_dark_gray:active .edd_button_outer, -.edd_button.edd_dark_gray:active .edd_button_text { background-position: 0 -649px !important; } -.edd_button.edd_dark_gray:active .edd_button_inner { background-position: right -649px !important; } - -/* orange */ -.edd_button.edd_orange { color: #996633; text-shadow: 0 0 1px #fedd9b; } -.edd_button.edd_orange .edd_button_outer, -.edd_button.edd_orange .edd_button_text { background-position: 0 -681px !important; } -.edd_button.edd_orange .edd_button_inner { background-position: right -681px !important; } -.edd_button.edd_orange:hover .edd_button_outer, -.edd_button.edd_orange:hover .edd_button_text { background-position: 0 -713px !important; } -.edd_button.edd_orange:hover .edd_button_inner { background-position: right -713px !important; } -.edd_button.edd_orange:active .edd_button_outer, -.edd_button.edd_orange:active .edd_button_text { background-position: 0 -745px !important; } -.edd_button.edd_orange:active .edd_button_inner { background-position: right -745px !important; } - -/* purple */ -.edd_button.edd_purple { color: #7b5777; text-shadow: 0 0 1px #eacae6; } -.edd_button.edd_purple .edd_button_outer, -.edd_button.edd_purple .edd_button_text { background-position: 0 -776px !important; } -.edd_button.edd_purple .edd_button_inner { background-position: right -776px !important; } -.edd_button.edd_purple:hover .edd_button_outer, -.edd_button.edd_purple:hover .edd_button_text { background-position: 0 -808px !important; } -.edd_button.edd_purple:hover .edd_button_inner { background-position: right -808px !important; } -.edd_button.edd_purple:active .edd_button_outer, -.edd_button.edd_purple:active .edd_button_text { background-position: 0 -841px !important; } -.edd_button.edd_purple:active .edd_button_inner { background-position: right -841px !important; } - -/* slate */ -.edd_button.edd_slate { color: #515f6a; text-shadow: 0 0 1px #c4d0da; } -.edd_button.edd_slate .edd_button_outer, -.edd_button.edd_slate .edd_button_text { background-position: 0 -873px !important; } -.edd_button.edd_slate .edd_button_inner { background-position: right -873px !important; } -.edd_button.edd_slate:hover .edd_button_outer, -.edd_button.edd_slate:hover .edd_button_text { background-position: 0 -905px !important; } -.edd_button.edd_slate:hover .edd_button_inner { background-position: right -905px !important; } -.edd_button.edd_slate:active .edd_button_outer, -.edd_button.edd_slate:active .edd_button_text { background-position: 0 -938px !important; } -.edd_button.edd_slate:active .edd_button_inner { background-position: right -938px !important; } - -/* =Downloads Shortcode --------------------------------------------------------------- */ -.edd_download_inner { padding: 0 8px 8px; margin: 0 0 10px; } -.edd_download_image { max-width:100%; } -.edd_download .edd_price { margin-bottom: 10px; } - -/* =Misc styles --------------------------------------------------------------- */ -.edd-cart-ajax { margin: 0 8px 0 4px; position: relative; top: 2px; } - -/* =Error styles --------------------------------------------------------------- */ - -.edd_errors { -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; border: 1px solid #E6DB55; margin: 20px 0; background: #FFFFE0; color: #333; } -.edd_error { padding:10px; } -p.edd_error { margin: 0 !important; } \ No newline at end of file diff --git a/includes/currency/functions.php b/includes/currency/functions.php new file mode 100644 index 00000000000..d9bacec36bc --- /dev/null +++ b/includes/currency/functions.php @@ -0,0 +1,119 @@ + __( 'US Dollars ($)', 'easy-digital-downloads' ), + 'EUR' => __( 'Euros (€)', 'easy-digital-downloads' ), + 'GBP' => __( 'Pound Sterling (£)', 'easy-digital-downloads' ), + 'AUD' => __( 'Australian Dollars ($)', 'easy-digital-downloads' ), + 'BRL' => __( 'Brazilian Real (R$)', 'easy-digital-downloads' ), + 'CAD' => __( 'Canadian Dollars ($)', 'easy-digital-downloads' ), + 'CZK' => __( 'Czech Koruna', 'easy-digital-downloads' ), + 'DKK' => __( 'Danish Krone', 'easy-digital-downloads' ), + 'HKD' => __( 'Hong Kong Dollar ($)', 'easy-digital-downloads' ), + 'HUF' => __( 'Hungarian Forint', 'easy-digital-downloads' ), + 'ILS' => __( 'Israeli Shekel (₪)', 'easy-digital-downloads' ), + 'JPY' => __( 'Japanese Yen (¥)', 'easy-digital-downloads' ), + 'MYR' => __( 'Malaysian Ringgits', 'easy-digital-downloads' ), + 'MXN' => __( 'Mexican Peso ($)', 'easy-digital-downloads' ), + 'NZD' => __( 'New Zealand Dollar ($)', 'easy-digital-downloads' ), + 'NOK' => __( 'Norwegian Krone', 'easy-digital-downloads' ), + 'PHP' => __( 'Philippine Pesos', 'easy-digital-downloads' ), + 'PLN' => __( 'Polish Zloty', 'easy-digital-downloads' ), + 'SGD' => __( 'Singapore Dollar ($)', 'easy-digital-downloads' ), + 'SEK' => __( 'Swedish Krona', 'easy-digital-downloads' ), + 'CHF' => __( 'Swiss Franc', 'easy-digital-downloads' ), + 'TWD' => __( 'Taiwan New Dollars', 'easy-digital-downloads' ), + 'THB' => __( 'Thai Baht (฿)', 'easy-digital-downloads' ), + 'INR' => __( 'Indian Rupee (₹)', 'easy-digital-downloads' ), + 'TRY' => __( 'Turkish Lira (₺)', 'easy-digital-downloads' ), + 'RIAL' => __( 'Iranian Rial (﷼)', 'easy-digital-downloads' ), + 'RUB' => __( 'Russian Rubles', 'easy-digital-downloads' ), + 'AOA' => __( 'Angolan Kwanza', 'easy-digital-downloads' ), + ); + + return apply_filters( 'edd_currencies', $currencies ); +} + +/** + * Accepts an amount (ideally from the database, unmodified) and formats it + * for display. The amount itself is formatted and the currency prefix/suffix + * is applied and positioned. + * + * @since 3.0 + * + * @param int|float|string $amount + * @param string $currency + * + * @return string + */ +function edd_display_amount( $amount, $currency ) { + $formatter = new Money_Formatter( $amount, new Currency( $currency ) ); + + return $formatter->format_for_display() + ->apply_symbol(); +} + +/** + * Get the store's set currency + * + * @since 1.5.2 + * @return string The currency code + */ +function edd_get_currency() { + $currency = edd_get_option( 'currency', 'USD' ); + return apply_filters( 'edd_currency', $currency ); +} + +/** + * Given a currency determine the symbol to use. If no currency given, site default is used. + * If no symbol is determined, the currency string is returned. + * + * @since 2.2 + * + * @param string $currency The currency string + * + * @return string The symbol to use for the currency + */ +function edd_currency_symbol( $currency = '' ) { + if ( empty( $currency ) ) { + $currency = edd_get_currency(); + } + + $currency = new Currency( $currency ); + + return $currency->symbol; +} + +/** + * Get the name of a currency + * + * @since 2.2 + * + * @param string $code The currency code + * + * @return string The currency's name + */ +function edd_get_currency_name( $code = 'USD' ) { + $currencies = edd_get_currencies(); + $name = isset( $currencies[ $code ] ) ? $currencies[ $code ] : $code; + return apply_filters( 'edd_currency_name', $name ); +} + diff --git a/includes/customer-functions.php b/includes/customer-functions.php new file mode 100644 index 00000000000..201a38ad597 --- /dev/null +++ b/includes/customer-functions.php @@ -0,0 +1,971 @@ +add_item( $data ); +} + +/** + * Delete a customer. + * + * @since 3.0 + * + * @param int ID of customer to delete. + * @return int|false `1` if the customer was deleted successfully, false on error. + */ +function edd_delete_customer( $customer_id = 0 ) { + $customers = new EDD\Database\Queries\Customer(); + + return $customers->delete_item( $customer_id ); +} + +/** + * Destroy a customer. + * + * Completely deletes a customer, and the addresses and email addresses with it. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @return int|false `1` if the customer was deleted successfully, false on error. + */ +function edd_destroy_customer( $customer_id = 0 ) { + + // Bail if a customer ID was not passed. + if ( empty( $customer_id ) ) { + return false; + } + + // Get email addresses. + $email_addresses = edd_get_customer_email_addresses( array( + 'customer_id' => $customer_id, + 'no_found_rows' => true, + ) ); + + // Destroy email addresses. + if ( ! empty( $email_addresses ) ) { + foreach ( $email_addresses as $email_address ) { + edd_delete_customer_email_address( $email_address->id ); + } + } + + // Get addresses. + $addresses = edd_get_customer_addresses( array( + 'customer_id' => $customer_id, + 'no_found_rows' => true, + ) ); + + // Destroy addresses. + if ( ! empty( $addresses ) ) { + foreach ( $addresses as $address ) { + edd_delete_customer_address( $address->id ); + } + } + + // Delete the customer. + $customer_destroyed = edd_delete_customer( $customer_id ); + if ( ! empty( $customer_destroyed ) ) { + /** + * Action that runs when a customer is destroyed with the edd_destroy_customer function. + * + * This action is similar to edd_customer_deleted and includes running that action, + * but also includes deleting all customer addresses and email addresses. + * + * @since 3.0.4 + * + * @param int $customer_id Customer ID being destroyed. + */ + do_action( 'edd_customer_destroyed', $customer_id ); + } + + return $customer_destroyed; +} + +/** + * Update a customer. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @param array $data { + * Array of customer data. Default empty. + * + * @type int $user_id WordPress user ID linked to the customer account. + * Default 0. + * @type string $email Customer's primary email address. Default empty. + * @type string $name Customer's name. Default empty. + * @type string $status Customer's status. Default `active`. + * @type float $purchase_value Aggregated purchase value of the customer. + * Default 0. + * @type int $purchase_count Aggregated purchase count of the customer. + * Default 0. + * @type string $date_created Optional. Automatically calculated on add/edit. + * The date & time the customer was created. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * @type string $date_modified Optional. Automatically calculated on add/edit. + * The date & time the customer was last modified. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * } + * + * @return int|false Number of rows updated if successful, false otherwise. + */ +function edd_update_customer( $customer_id = 0, $data = array() ) { + $customers = new EDD\Database\Queries\Customer(); + + return $customers->update_item( $customer_id, $data ); +} + +/** + * Get a customer item by ID. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @return EDD_Customer|false Customer object if successful, false otherwise. + */ +function edd_get_customer( $customer_id = 0 ) { + $customers = new EDD\Database\Queries\Customer(); + + // Return customer. + return $customers->get_item( $customer_id ); +} + +/** + * Get a customer item by a specific field value. + * + * @since 3.0 + * + * @param string $field Database table field. + * @param string $value Value of the row. + * + * @return EDD_Customer|false Customer object if successful, false otherwise. + */ +function edd_get_customer_by( $field = '', $value = '' ) { + // For backwards compatibility in filters only. + $customers_db = EDD()->customers; + + /** + * Filters the Customer before querying the database. + * + * Return a non-null value to bypass the default query and return early. + * + * @since 2.9.23 + * + * @param mixed|null $customer Customer to return instead. Default null to use default method. + * @param string $field The field to retrieve by. + * @param mixed $value The value to search by. + * @param EDD\Compat\Customer $edd_customers_db Customer database class. Deprecated in 3.0. + */ + $found = apply_filters( 'edd_pre_get_customer', null, $field, $value, $customers_db ); + + if ( null !== $found ) { + return $found; + } + + $customers = new EDD\Database\Queries\Customer(); + if ( 'email' === $field ) { + $customer_emails = new EDD\Database\Queries\Customer_Email_Address(); + $customer_email = $customer_emails->get_item_by( 'email', $value ); + + $customer = false; + if ( ! empty( $customer_email->customer_id ) ) { + $customer = $customers->get_item_by( 'id', $customer_email->customer_id ); + } + } else { + $customer = $customers->get_item_by( $field, $value ); + } + + /** + * Filters the single Customer retrieved from the database based on field. + * + * @since 2.9.23 + * + * @param EDD_Customer|false $customer Customer query result. False if no Customer is found. + * @param array $args Arguments used to query the Customer. + * @param EDD\Compat\Customer $edd_customers_db Customer database class. Deprecated in 3.0. + */ + $customer = apply_filters( "edd_get_customer_by_{$field}", $customer, $customers->query_vars, $customers_db ); + + /** + * Filters the single Customer retrieved from the database. + * + * @since 2.9.23 + * + * @param EDD_Customer|false $customer Customer query result. False if no Customer is found. + * @param array $args Arguments used to query the Customer. + * @param EDD_DB_Customers $edd_customers_db Customer database class. + */ + $customer = apply_filters( 'edd_get_customer', $customer, $customers->query_vars, $customers_db ); + + return $customer; +} + +/** + * Get a field from a customer object. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. Default `0`. + * @param string $field Field to retrieve from object. Default empty. + * + * @return mixed Null if customer does not exist. Value of Customer if exists. + */ +function edd_get_customer_field( $customer_id = 0, $field = '' ) { + $customer = edd_get_customer( $customer_id ); + + // Check that field exists. + return isset( $customer->{$field} ) + ? $customer->{$field} + : null; +} + +/** + * Query for customers. + * + * @see \EDD\Database\Queries\Customer::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer` for + * accepted arguments. + * @return \EDD_Customer[] Array of `EDD_Customer` objects. + */ +function edd_get_customers( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'number' => 30 + ) ); + + if ( -1 == $r['number'] ) { + _doing_it_wrong( __FUNCTION__, esc_html__( 'Do not use -1 to retrieve all results.', 'easy-digital-downloads' ), '3.0' ); + $r['number'] = 9999999; + } + + // Instantiate a query object + $customers = new EDD\Database\Queries\Customer(); + + // Return customers + return $customers->query( $r ); +} + +/** + * Get total number of customers. + * + * @see \EDD\Database\Queries\Customer::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer` for + * accepted arguments. + * @return int Number of customers returned based on query arguments passed. + */ +function edd_count_customers( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'count' => true + ) ); + + // Query for count(s) + $customers = new EDD\Database\Queries\Customer( $r ); + + // Return count(s) + return absint( $customers->found_items ); +} + +/** + * Query for and return array of customer counts, keyed by status. + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer` for + * accepted arguments. + * @return array Customer counts keyed by status. + */ +function edd_get_customer_counts( $args = array() ) { + + // Parse arguments + $r = wp_parse_args( $args, array( + 'count' => true, + 'groupby' => 'status' + ) ); + + // Query for count + $counts = new EDD\Database\Queries\Customer( $r ); + + // Format & return + return edd_format_counts( $counts, $r['groupby'] ); +} + +/** + * Return the role used to edit customers. + * + * @since 3.0 + * + * @return string Role used to edit customers. + */ +function edd_get_edit_customers_role() { + + /** + * Filter role used to edit customers. + * + * @since 2.3 + * + * @param string WordPress role used to edit customers. Default `edit_shop_payments`. + */ + return apply_filters( 'edd_edit_customers_role', 'edit_shop_payments' ); +} + +/** + * Retrieve all of the IP addresses used by a customer. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * + * @return array Array of objects containing IP addresses. + */ +function edd_get_customer_ip_addresses( $customer_id = 0 ) { + + // Bail if no customer ID was passed. + if ( empty( $customer_id ) ) { + return array(); + } + + $customer = edd_get_customer( $customer_id ); + + return $customer->get_ips(); +} + +/** Meta **********************************************************************/ + +/** + * Add meta data field to a customer. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @param string $meta_key Meta data name. + * @param mixed $meta_value Meta data value. Must be serializable if non-scalar. + * @param bool $unique Optional. Whether the same key should not be added. + * Default false. + * + * @return int|false Meta ID on success, false on failure. + */ +function edd_add_customer_meta( $customer_id, $meta_key, $meta_value, $unique = false ) { + return add_metadata( 'edd_customer', $customer_id, $meta_key, $meta_value, $unique ); +} + +/** + * Remove meta data matching criteria from a customer. + * + * You can match based on the key, or key and value. Removing based on key and + * value, will keep from removing duplicate meta data with the same key. It also + * allows removing all meta data matching key, if needed. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @param string $meta_key Meta data name. + * @param mixed $meta_value Optional. Meta data value. Must be serializable if + * non-scalar. Default empty. + * + * @return bool True on success, false on failure. + */ +function edd_delete_customer_meta( $customer_id, $meta_key, $meta_value = '' ) { + return delete_metadata( 'edd_customer', $customer_id, $meta_key, $meta_value ); +} + +/** + * Retrieve customer meta field for a customer. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @param string $key Optional. The meta key to retrieve. By default, returns + * data for all keys. Default empty. + * @param bool $single Optional, default is false. + * If true, return only the first value of the specified meta_key. + * This parameter has no effect if meta_key is not specified. + * + * @return mixed Will be an array if $single is false. Will be value of meta data + * field if $single is true. + */ +function edd_get_customer_meta( $customer_id, $key = '', $single = false ) { + return get_metadata( 'edd_customer', $customer_id, $key, $single ); +} + +/** + * Update customer meta field based on customer ID. + * + * Use the $prev_value parameter to differentiate between meta fields with the + * same key and customer ID. + * + * If the meta field for the customer does not exist, it will be added. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @param string $meta_key Meta data key. + * @param mixed $meta_value Meta data value. Must be serializable if non-scalar. + * @param mixed $prev_value Optional. Previous value to check before removing. + * Default empty. + * + * @return int|bool Meta ID if the key didn't exist, true on successful update, + * false on failure. + */ +function edd_update_customer_meta( $customer_id, $meta_key, $meta_value, $prev_value = '' ) { + return update_metadata( 'edd_customer', $customer_id, $meta_key, $meta_value, $prev_value ); +} + +/** + * Delete everything from customer meta matching meta key. + * + * @since 3.0 + * + * @param string $meta_key Key to search for when deleting. + * + * @return bool Whether the customer meta key was deleted from the database. + */ +function edd_delete_customer_meta_by_key( $meta_key ) { + return delete_metadata( 'edd_customer', null, $meta_key, '', true ); +} + +/** Customer Addresses **********************************************************/ + +/** + * Get a customer address by ID. + * + * @internal This method is named edd_fetch_customer_address as edd_get_customer_address + * exists for backwards compatibility purposes and returns an array instead of + * an object. + * + * @since 3.0 + * + * @param int $customer_address_id Customer address ID. + * @return EDD\Customers\Customer_Address Customer address object. + */ +function edd_fetch_customer_address( $customer_address_id = 0 ) { + $customer_addresses = new EDD\Database\Queries\Customer_Address(); + + // Return customer address. + return $customer_addresses->get_item( $customer_address_id ); +} + +/** + * Add a customer address. + * + * @since 3.0 + * + * @param array $data { + * Array of customer address data. Default empty. + * + * The `date_created` and `date_modified` parameters do not need to be passed. + * They will be automatically populated if empty. + * + * @type int $customer_id Customer ID. Default `0`. + * @type string $type Address type. Default `billing`. + * @type string $status Address status, if used or not. Default `active`. + * @type string $address First line of address. Default empty. + * @type string $address2 Second line of address. Default empty. + * @type string $city City. Default empty. + * @type string $region Region. See `edd_get_shop_states()` for + * accepted values. Default empty. + * @type string $postal_code Postal code. Default empty. + * @type string $country Country. See `edd_get_country_list()` for + * accepted values. Default empty. + * @type string $date_created Optional. Automatically calculated on add/edit. + * The date & time the address was inserted. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * @type string $date_modified Optional. Automatically calculated on add/edit. + * The date & time the address was last modified. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * } + * @return int|false ID of newly created customer address, false on error. + */ +function edd_add_customer_address( $data = array() ) { + + // A customer ID must be supplied for every address inserted. + if ( empty( $data['customer_id'] ) ) { + return false; + } + + $customer_addresses = new EDD\Database\Queries\Customer_Address(); + + return $customer_addresses->add_item( $data ); +} + +/** + * Delete a customer address. + * + * @since 3.0 + * + * @param int $customer_address_id Customer address ID. + * @return int|false `1` if the adjustment was deleted successfully, false on error. + */ +function edd_delete_customer_address( $customer_address_id = 0 ) { + $customer_addresses = new EDD\Database\Queries\Customer_Address(); + + return $customer_addresses->delete_item( $customer_address_id ); +} + +/** + * Update a customer address. + * + * @since 3.0 + * + * @param int $customer_address_id Customer address ID. + * @param array $data { + * Array of customer address data. Default empty. + * + * @type int $customer_id Customer ID. Default `0`. + * @type string $type Address type. Default `billing`. + * @type string $status Address status, if used or not. Default `active`. + * @type string $address First line of address. Default empty. + * @type string $address2 Second line of address. Default empty. + * @type string $city City. Default empty. + * @type string $region Region. See `edd_get_shop_states()` for + * accepted values. Default empty. + * @type string $postal_code Postal code. Default empty. + * @type string $country Country. See `edd_get_country_list()` for + * accepted values. Default empty. + * @type string $date_created Optional. Automatically calculated on add/edit. + * The date & time the adjustment was inserted. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * @type string $date_modified Optional. Automatically calculated on add/edit. + * The date & time the adjustment was last modified. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * } + * + * @return int|false Number of rows updated if successful, false otherwise. + */ +function edd_update_customer_address( $customer_address_id = 0, $data = array() ) { + $customer_addresses = new EDD\Database\Queries\Customer_Address(); + + return $customer_addresses->update_item( $customer_address_id, $data ); +} + +/** + * Get a customer address by a specific field value. + * + * @since 3.0 + * + * @param string $field Database table field. + * @param string $value Value of the row. + * + * @return \EDD\Customers\Customer_Address|false Customer_Address if successful, + * false otherwise. + */ +function edd_get_customer_address_by( $field = '', $value = '' ) { + $customer_addresses = new EDD\Database\Queries\Customer_Address(); + + // Return customer address + return $customer_addresses->get_item_by( $field, $value ); +} + +/** + * Query for customer addresses. + * + * @see \EDD\Database\Queries\Customer_Address::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer_Address` for + * accepted arguments. + * @return \EDD\Customers\Customer_Address[] Array of `Customer_Address` objects. + */ +function edd_get_customer_addresses( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'number' => 30 + ) ); + + // Instantiate a query object + $customer_addresses = new EDD\Database\Queries\Customer_Address(); + + // Return addresses + return $customer_addresses->query( $r ); +} + +/** + * Count customer addresses. + * + * @see \EDD\Database\Queries\Customer_Address::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer_Address` for + * accepted arguments. + * @return int Number of customer addresses returned based on query arguments passed. + */ +function edd_count_customer_addresses( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'count' => true + ) ); + + // Query for count(s) + $customer_addresses = new EDD\Database\Queries\Customer_Address( $r ); + + // Return count(s) + return absint( $customer_addresses->found_items ); +} + +/** + * Maybe add a customer address. Only unique addresses will be added. Used + * by `edd_build_order()` and `edd_add_manual_order()` to maybe add order + * addresses to the customer addresses table. Also used by the data migrator + * class when migrating orders from 2.9. + * + * @since 3.0 + * + * @param int $customer_id Customer ID. + * @param array $data { + * Array of customer address data. Default empty. + * + * @type string $type Address type. Default `billing`. + * @type string $status Address status, if used or not. Default `active`. + * @type string $address First line of address. Default empty. + * @type string $address2 Second line of address. Default empty. + * @type string $city City. Default empty. + * @type string $region Region. See `edd_get_shop_states()` for + * accepted values. Default empty. + * @type string $postal_code Postal code. Default empty. + * @type string $country Country. See `edd_get_country_list()` for + * accepted values. Default empty. + * @type string $date_created Optional. Automatically calculated on add/edit. + * The date & time the address was inserted. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * @type string $date_modified Optional. Automatically calculated on add/edit. + * The date & time the address was last modified. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * } + * + * @return int|false ID of the insert customer address. False otherwise. + */ +function edd_maybe_add_customer_address( $customer_id = 0, $data = array() ) { + + // Bail if nothing passed. + if ( empty( $customer_id ) || empty( $data ) ) { + return false; + } + + // Set up an array with empty address keys. If all of these are empty in $data, the address should not be added. + $empty_address = array( + 'address' => '', + 'address2' => '', + 'city' => '', + 'region' => '', + 'country' => '', + 'postal_code' => '', + ); + $address_to_check = array_intersect_key( $data, $empty_address ); + $address_to_check = array_filter( $address_to_check ); + if ( empty( $address_to_check ) ) { + return false; + } + $address_to_check['customer_id'] = $customer_id; + $address_to_check['type'] = empty( $data['type'] ) ? 'billing' : $data['type']; + + // Check if this address is already assigned to the customer. + $address_exists = edd_get_customer_addresses( $address_to_check ); + if ( ! empty( $address_exists ) ) { + if ( 'billing' === $address_to_check['type'] ) { + edd_update_customer_address( $address_exists[0], array( 'is_primary' => true ) ); + } + return false; + } + + $data['customer_id'] = $customer_id; + if ( 'billing' === $address_to_check['type'] ) { + $data['is_primary'] = true; + } + + // Add the new address to the customer record. + return edd_add_customer_address( $data ); +} + +/** + * Query for and return array of customer address counts, keyed by status. + * + * @see \EDD\Database\Queries\Customer_Address::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer_Address` for + * accepted arguments. + * @return array Customer address counts keyed by status. + */ +function edd_get_customer_address_counts( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'count' => true, + 'groupby' => 'status' + ) ); + + // Query for count + $counts = new EDD\Database\Queries\Customer_Address( $r ); + + // Format & return + return edd_format_counts( $counts, $r['groupby'] ); +} + +/** Customer Email Addresses *************************************************/ + +/** + * Add a customer email address. + * + * @since 3.0 + * + * @param array $data { + * Array of customer email address data. Default empty. + * + * The `date_created` and `date_modified` parameters do not need to be passed. + * They will be automatically populated if empty. + * + * @type int $customer_id Customer ID. Default `0`. + * @type string $type Email address type. Default `secondary`. + * @type string $status Email address type. Default `active`. + * @type string $email Email address. Default empty. + * @type string $date_created Optional. Automatically calculated on add/edit. + * The date & time the email address was inserted. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * @type string $date_modified Optional. Automatically calculated on add/edit. + * The date & time the email address was last + * modified. Format: YYYY-MM-DD HH:MM:SS. Default empty. + * } + * @return int|false ID of newly created customer email address, false on error. + */ +function edd_add_customer_email_address( $data ) { + + // A customer ID and email must be supplied for every address inserted. + if ( empty( $data['customer_id'] ) || empty( $data['email'] ) ) { + return false; + } + + // Instantiate a query object. + $customer_email_addresses = new EDD\Database\Queries\Customer_Email_Address(); + + // Check if the address already exists for this customer. + $existing_addresses = $customer_email_addresses->query( + array( + 'customer_id' => $data['customer_id'], + 'email' => $data['email'], + ) + ); + if ( ! empty( $existing_addresses ) ) { + return false; + } + + // Add the email address to the customer. + return $customer_email_addresses->add_item( $data ); +} + +/** + * Delete a customer email address. + * + * @since 3.0 + * + * @param int $customer_email_address_id Customer email address ID. + * @return int|false `1` if the customer email address was deleted successfully, + * false on error. + */ +function edd_delete_customer_email_address( $customer_email_address_id ) { + $customer_email_addresses = new EDD\Database\Queries\Customer_Email_Address(); + + return $customer_email_addresses->delete_item( $customer_email_address_id ); +} + +/** + * Update a customer email address. + * + * @since 3.0 + * + * @param int $customer_email_address_id Customer email address ID. + * @param array $data { + * Array of customer email address data. Default empty. + * + * @type int $customer_id Customer ID. Default `0`. + * @type string $type Email address type. Default `secondary`. + * @type string $status Email address type. Default `active`. + * @type string $email Email address. Default empty. + * @type string $date_created Optional. Automatically calculated on add/edit. + * The date & time the email address was inserted. + * Format: YYYY-MM-DD HH:MM:SS. Default empty. + * @type string $date_modified Optional. Automatically calculated on add/edit. + * The date & time the email address was last + * modified. Format: YYYY-MM-DD HH:MM:SS. Default empty. + * } + * + * @return int|false Number of rows updated if successful, false otherwise. + */ +function edd_update_customer_email_address( $customer_email_address_id, $data = array() ) { + $customer_email_addresses = new EDD\Database\Queries\Customer_Email_Address(); + + return $customer_email_addresses->update_item( $customer_email_address_id, $data ); +} + +/** + * Get a customer email address by ID. + * + * @since 3.0 + * + * @param int $customer_email_address_id Customer email address ID. + * @return \EDD\Customers\Customer_Email_Address|false Customer_Email_Address if + * successful, false otherwise. + */ +function edd_get_customer_email_address( $customer_email_address_id ) { + $customer_email_addresses = new EDD\Database\Queries\Customer_Email_Address(); + + // Return customer email address + return $customer_email_addresses->get_item( $customer_email_address_id ); +} + +/** + * Get a customer email address by a specific field value. + * + * @since 3.0 + * + * @param string $field Database table field. + * @param string $value Value of the row. + * + * @return \EDD\Customers\Customer_Email_Address|false Customer_Email_Address if + * successful, false otherwise. + */ +function edd_get_customer_email_address_by( $field = '', $value = '' ) { + $customer_email_addresses = new EDD\Database\Queries\Customer_Email_Address(); + + // Return customer email address + return $customer_email_addresses->get_item_by( $field, $value ); +} + +/** + * Query for customer email addresses. + * + * @see \EDD\Database\Queries\Customer_Email_Address::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer_Email_Address` + * for accepted arguments. + * @return \EDD\Customers\Customer_Email_Address[] Array of `Customer_Email_Address` objects. + */ +function edd_get_customer_email_addresses( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'number' => 30 + ) ); + + // Instantiate a query object + $customer_email_addresses = new EDD\Database\Queries\Customer_Email_Address(); + + // Return customer email addresses + return $customer_email_addresses->query( $r ); +} + +/** + * Count customer addresses. + * + * @see \EDD\Database\Queries\Customer_Email_Address::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer_Email_Address` + * for accepted arguments. + * @return int Number of customer email addresses returned based on query arguments passed. + */ +function edd_count_customer_email_addresses( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'count' => true + ) ); + + // Query for count(s) + $customer_email_addresses = new EDD\Database\Queries\Customer_Email_Address( $r ); + + // Return count(s) + return absint( $customer_email_addresses->found_items ); +} + +/** + * Query for and return array of customer email counts, keyed by status. + * + * @see \EDD\Database\Queries\Customer_Email_Address::__construct() + * + * @since 3.0 + * + * @param array $args Arguments. See `EDD\Database\Queries\Customer_Email_Address` + * for accepted arguments. + * @return array Customer email addresses keyed by status. + */ +function edd_get_customer_email_address_counts( $args = array() ) { + + // Parse args + $r = wp_parse_args( $args, array( + 'count' => true, + 'groupby' => 'status' + ) ); + + // Query for count + $counts = new EDD\Database\Queries\Customer_Email_Address( $r ); + + // Format & return + return edd_format_counts( $counts, $r['groupby'] ); +} diff --git a/includes/customers/class-customer-address.php b/includes/customers/class-customer-address.php new file mode 100644 index 00000000000..6768d07048e --- /dev/null +++ b/includes/customers/class-customer-address.php @@ -0,0 +1,145 @@ + $customer_id, + 'email' => $data['email'], + 'type' => 'primary', + ) + ); +} +add_action( 'edd_customer_added', 'edd_process_customer_added', 10, 2 ); + +/** + * Is intended to be used when updating a customer directly with edd_add_customer function. + * + * Because the edd_update_customer function only directly interacts with the customers DB table, we may + * need to do some additional actions like managing email addresses. + * + * @since 3.0.4 + * + * @param int $customer_id The customer ID being updated. + * @param array $data The data passed in to update the customer with. + * @param EDD_Customer $prev_customer_obj The customer object, prior to these updates. + */ +function edd_process_customer_updated( $customer_id, $data, $prev_customer_obj ) { + $customer = edd_get_customer( $customer_id ); + $email_updated = false; + + // Process a User ID change. + if ( intval( $customer->user_id ) !== intval( $prev_customer_obj->user_id ) ) { + // Attach the User Email to the customer as well. + $user = new WP_User( $customer->user_id ); + if ( $user instanceof WP_User ) { + + // Only update this if it doesn't match already. + if ( $customer->email !== $user->user_email ) { + $customers = new EDD\Database\Queries\Customer(); + $customers->update_item( $customer_id, array( 'email' => $user->user_email ) ); + } + + // Our transition hook for the type will handle demoting any other email addresses. + edd_add_customer_email_address( + array( + 'customer_id' => $customer->id, + 'email' => $user->user_email, + 'type' => 'primary', + ) + ); + + $email_updated = true; + } + + // Remove the old user email from this account. + $previous_user = new WP_User( $prev_customer_obj->user_id ); + if ( $previous_user instanceof WP_User ) { + $existing_email_addresses = edd_get_customer_email_addresses( + array( + 'customer_id' => $customer->id, + 'email' => $previous_user->user_email, + ) + ); + + if ( ! empty( $existing_email_addresses ) ) { + // Should only be one, but let's foreach to be safe. + foreach ( $existing_email_addresses as $existing_address ) { + edd_delete_customer_email_address( $existing_address->id ); + } + } + } + + // Update some payment meta if we need to. + $order_ids = edd_get_orders( array( 'customer_id' => $customer->id, 'number' => 9999999 ) ); + + foreach ( $order_ids as $order_id ) { + edd_update_order( $order_id, array( 'user_id' => $customer->user_id ) ); + } + } + + // If the email address changed, set the new one as primary. + if ( false === $email_updated && $prev_customer_obj->email !== $customer->email ) { + + // Our transition hook for the type will handle demoting any other email addresses. + edd_add_customer_email_address( + array( + 'customer_id' => $customer->id, + 'email' => $customer->email, + 'type' => 'primary', + ) + ); + } +} +add_action( 'edd_customer_updated', 'edd_process_customer_updated', 10, 3 ); + +/** + * When a new primary email address is added to the database, any other primary email addresses should be demoted. + * + * @param string $old_value The previous value of `type`. + * @param string $new_value The new value of `type`. + * @param int $item_id The address ID in the edd_customer_email_addresses table. + * @return void + */ +function edd_demote_customer_primary_email_addresses( $old_value, $new_value, $item_id ) { + if ( ! $new_value ) { + return; + } + + // If we're not setting the `type` to `primary` we do not need to make any adjustments. + if ( 'primary' !== $new_value ) { + return; + } + + $email_address = edd_get_customer_email_address( $item_id ); + $previous_primary_email_addresses = edd_get_customer_email_addresses( + array( + 'id__not_in' => array( $item_id ), + 'fields' => 'ids', + 'customer_id' => $email_address->customer_id, + 'type' => 'primary', + ) + ); + + if ( empty( $previous_primary_email_addresses ) ) { + return; + } + + foreach ( $previous_primary_email_addresses as $previous ) { + edd_update_customer_email_address( $previous, array( 'type' => 'secondary' ) ); + } +} +add_action( 'edd_transition_customer_email_address_type', 'edd_demote_customer_primary_email_addresses', 10, 3 ); + +/** + * When a new primary address is added to the database, any other primary addresses should be demoted. + * + * @param string $old_value The previous value of `is_primary`. + * @param string $new_value The new value of `is_primary`. + * @param int $item_id The address ID in the edd_customer_addresses table. + * @return void + */ +function edd_demote_customer_primary_addresses( $old_value, $new_value, $item_id ) { + if ( ! $new_value ) { + return; + } + $address = edd_fetch_customer_address( $item_id ); + $previous_primary_addresses = edd_get_customer_addresses( + array( + 'id__not_in' => array( $item_id ), + 'fields' => 'ids', + 'customer_id' => $address->customer_id, + 'is_primary' => true, + ) + ); + if ( empty( $previous_primary_addresses ) ) { + return; + } + foreach ( $previous_primary_addresses as $previous ) { + edd_update_customer_address( $previous, array( 'is_primary' => false ) ); + } +} +add_action( 'edd_transition_customer_address_is_primary', 'edd_demote_customer_primary_addresses', 10, 3 ); + +/** + * Updates the email address of a customer record when the email on a user is updated. + * + * @since 2.4.0 + * + * @param int $user_id User ID. + * @param WP_User $old_user_data Object containing user's data prior to update. + * + * @return void + */ +function edd_update_customer_email_on_user_update( $user_id, $old_user_data ) { + $user = get_userdata( $user_id ); + + // Bail if the email address didn't actually change just now. + if ( empty( $user ) || $user->user_email === $old_user_data->user_email ) { + return; + } + + $customer = edd_get_customer_by( 'user_id', $user_id ); + + if ( empty( $customer ) || $user->user_email === $customer->email ) { + return; + } + + // Bail if we have another customer with this email address already. + if ( edd_get_customer_by( 'email', $user->user_email ) ) { + return; + } + + $success = edd_update_customer( $customer->id, array( 'email' => $user->user_email ) ); + + if ( ! $success ) { + return; + } + + /** + * Triggers after the customer has been successfully updated. + * + * @param WP_User $user + * @param EDD_Customer $customer + */ + do_action( 'edd_update_customer_email_on_user_update', $user, $customer ); +} +add_action( 'profile_update', 'edd_update_customer_email_on_user_update', 10, 2 ); diff --git a/includes/database/README.md b/includes/database/README.md new file mode 100644 index 00000000000..749da0cae04 --- /dev/null +++ b/includes/database/README.md @@ -0,0 +1,384 @@ +# Custom Tables # + +### The schema for each custom table is described below + +## The Adjustments Table: +This table stores things that can be used to adjust an order. This includes things like discount codes and tax rates. Note that these things do not represent an adjustment that was used for a specific order. To see a record of those, look at the order_adjustments table. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| parent | This column does not currently serve any purpose, but might be used in the future. | +| name | The name of the adjustment. For discount codes, this is the name of the discount. For tax rates, this is the name of the country for which the tax rate applies. All tax rates in a country share the same value in this column. For example, all tax rates in Canada will use "CA" here. | +| code | For discount codes, this is the value which users can enter upon checkout. For tax rates, this column is blank. | +| status | A string which indicates the status of the adjustment. For tax rates this will be "active" or "inactive". For discount codes, this will be "active" even if the discount has expired, and this is intentional. | +| type | The type of adjustment this is. For discounts this is "discount". For tax rates this is "tax_rate". | +| scope | A value which defines how the adjustment can be used. For discount codes the value is always "global". For tax rates, the values can be "country", or "region". | +| amount_type | The type of amount this is, either "percent" or "flat", with flat being a monetary value. | +| amount | The amount of the adjustment, stored as a decimal and representing either a percent or flat amount. | +| description | For tax rates, this is the "state" or "province". For example, for Ontario (the province in Canada), this value will be "ON". For discount codes this column is not currently used. | +| max_uses | An integer indicating the maximum number of times this adjustment can be used. | +| use_count | An integer indicating the number of times this adjustment has been used. | +| once_per_customer | A boolean value in the form of 1 (for true) and 0 (for false). If 1, this adjustment should only be used once per customer. If 0, there is no limit to the number of times it can be used. | +| min_charge_amount | For discount codes, this is the minimum amount required in the cart prior to this adjustment being applied. For tax rates this is typically not applicable. | +| start_date | For discount codes, this is the date when a discount code will begin to work. This is not applicable to tax rates. | +| end_date | For discount codes, this is the date when a discount code will cease to work. This is not applicable to tax rates. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Adjustment Meta Table: +This table stores various and custom/extra information about an adjustment. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_adjustment_id | The id of the adjustment to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Customer Addresses Table: +This table stores the addresses of customers. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| customer_id | The id of the customer to which this address belongs. This id corresponds to the id column in the Customers table. | +| name | The name of the person connected to this physical address. | +| type | The type of address this row represents. Typical values are "billing" and "shipping". | +| status | This currently does not serve any purpose, but might in the future. | +| address | The first line of a physical address. | +| address2 | The second line of a physical address. | +| city | The city of a physical address. | +| region | A 2 letter representation of the region/state/province. For example, in the US, this is the "State". In Canada, this is the "Province". | +| postal_code | The postal code for a physical address. It accepts any string. | +| country | The 2 letter representation of a country for a physical address. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Customer Email Addresses Table: +This table stores the email addresses of customers. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| customer_id | The id of the customer to which this email address belongs. This id corresponds to the id column in the Customers table. | +| type | A string representing the priority (or importance) of this email address. Typical values are "primary" and "secondary". | +| status | This does not serve any purpose at this time, but might in the future. | +| email | An email address, which is connected to a customer. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Customer Meta Table: +This table stores various and custom/extra information about a customer. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_customer_id | The id of the customer to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Customers Table: +This table stores customers. Customers are people who have made a purchase in your store. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. This is also the id of the customer. | +| user_id | The id of the WordPress user which is linked to this customer. The same real-world person owns both, the WP user, and the EDD customer. | +| email | The primary email address of this customer. This value will typically match whatever the "primary" email is in the customer emails table in the "type" column. | +| name | This is the customer's name, and includes their first and last name together in a single string. | +| status | Currently, this stores the word "active" for all customers, until such time as functionality for "inactive" customers gets added to EDD. | +| purchase_value | This is the total amount of money this customer has paid. | +| purchase_count | This is the total number of purchases this customer has initiated. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Log Meta Table: +This table stores various and custom/extra information about a log. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_log_id | The id of the log to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Logs Table: +This table stores general-purpose logs, which are typically records of events happening, like a payment being completed or refunded. Note that logs are intended to be "created by a machine" as opposed to "created by a human". If you are writing code that automatically logs something, make it a log in this table. If you are writing code that creates a UI which allows a human being to write a note, store it in the notes table. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| object_id | The id of the thing to which this log relates. For example, the id of the "order" or the "discount code". | +| object_type | This describes the type of thing this log is for. For example, "order" indicates this log is for an order. | +| user_id | This is the ID of the WordPress user who created this log. | +| type | This column indicates the type of log this is. For example, the word "refund" would indicate that this log is about a refund taking place. | +| title | This is the title of the log. Typically this is a short sentence describing what the log is about. | +| content | This is a longer description of the log. Typically this will be a sentence or paragraph describing the event which took place. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Logs API Requests Table: +Every time a request is made to the EDD REST API, that request is logged in this table. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| user_id | This stores the ID of the WordPress user who created this log. | +| api_key | This stores the api key used to make this API request. | +| token | This stores the token used to make this API request. | +| version | This stores the version of the API for which this call was made. | +| request | This stores what the URL variables were when the request was made. | +| error | Errors that took place during the call. Defaults to be empty. | +| ip | The IP address of the machine which made the request. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Log API Request Meta Table: +This table stores various and custom/extra information about an API Request Log. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_logs_api_request_id | The id of the api request log to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Logs File Downloads Table: +Every time a deliverable file is downloaded via EDD, it is logged in this table. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| product_id | The ID of the EDD product whose file was downloaded. | +| file_id | The id of the file being downloaded. This ID comes from the files attached to an EDD product. | +| order_id | The ID of the order which is enabling this download to take place. | +| price_id | The variable price ID which was purchased, and which enabled this download to take place. 0 if the product is not variably-priced. | +| customer_id | The ID of the customer who downloaded this file. | +| ip | The IP address of the machine which made the request to download the file. | +| user_agent | The name/user-agent of the browser which was used to download the file. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Log File Download Meta Table: +This table stores various and custom/extra information about a file download log. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_logs_file_download_id | The id of the file download log to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Note Meta Table: +This table stores various and custom/extra information about a note. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_note_id | The id of the note to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Notes Table: +This table is for storing notes created by human beings, as opposed to notes/logs/data created automatically by code or automatic code events happening. Note that logs are intended to be "created by a machine" as opposed to "created by a human". If you are writing code that automatically logs something, make it a log in the logs table. If you are writing code that creates a UI which allows a human being to write a note, store it in the notes table here. + +This table's data is intended to be mutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| object_id | The id of the thing to which this note relates. For example, the id of the "order" for which this note was created. | +| object_type | This describes the type of thing this note is for. For example, "order" indicates this note is for/about an order. | +| user_id | This is the ID of the WordPress user who created this note. | +| content | This is the main/unique content of the note, the note itself. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Order Addresses Table: +When a user completes a purchase/order and enters their address on the checkout page, that address is stored in a new row here. This allows the address attached to the order to remain what it was at the time of purchase, regardless of whether the customer changes their address in the future. This is because the address attached to an order should remain unchanged forever. These addresses should be considered immutable. Even if a customer has 2 orders and uses the exact same address for each order, a new row will be created here, unique to that order, despite possibly being identical to a previous row. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| order_id | The id of the order to which this address is attached. | +| name | The name of the person attached to this physical address. | +| type | The type of address this row represents. Typical values are "billing" and "shipping". | +| address | The first line of a physical address. | +| address2 | The second line of a physical address. | +| city | The city of a physical address. | +| region | A 2 letter representation of the region/state/province. For example, in the US, this is the "State". In Canada, this is the "Province". | +| postal_code | The postal code for a physical address. It accepts any string. | +| country | The 2 letter representation of a country for a physical address. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Order Adjustment Meta Table: + This table stores various and custom/extra information about an order adjustment. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_order_adjustment_id | The id of the adjustment to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Order Adjustments Table: +This table stores things that adjusted the total amount of a specific order, or the amount of an item within an order. This includes things like discount codes and tax rates. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| parent | The ID of another order adjustment which is considered to be the parent of this adjustment. This is used for adjustments attached to refunds. The parent references the ID of the original order adjustment that was refunded. | +| object_id | The ID of the row that this row adjusted the amount/cost of. This is typically an order (in the orders table) or an order_item (in the order_items table). The type of object is indicated in the object_type column. | +| object_type | This typically indicates the EDD custom table that the object_id value can be found within, and to which row within that table this row relates. For example, the orders table (indicated by the word "order") or the order_items table (indicated by the word "order_item"). | +| type_id | This value indicates the row ID in the adjustments table from which this order adjustment originated. For example, if this value is "25", go to the adjustments table and look at the row with the ID "25" to see the corresponding adjustment. | +| type | A string which indicates the type of adjustment this is. Typically this is something like "fee", "tax_rate", or "discount". | +| type_key | The fees API allows for customizing the array key value for a given fee. This can be a string or numeric. This "fee ID" is stored as the type_key, as it represents the fee's key in the 2.x array. | +| description | A description of the order adjustment. | +| subtotal | If the amount type for this row is a percentage, the value in this column is intentionally unused. Otherwise, it stores the monetary amount of this adjustment before tax. For example, if you have a $10 shipping fee with a 10% tax rate, $10 is stored in this column. | +| tax | If the object_type for this row is a percentage, the value in this column is intentionally unused. Otherwise, it stores the monetary amount of the tax on this adjustment. For example, if you have a $10 shipping fee, the tax on the $10 is stored in this column. | +| total | Like the subtotal and tax columns, this column stores a monetary amount sometimes, and at others stores a percentage rate. To determine the type of amount being stored, percentage vs flat amount, trace the row back to the adjustments table using the type_id value from this table, and check the amount_type column's value from the adjustments table. For example, if you have a $10 shipping fee with a 10% tax rate, $11 is stored in this column. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Order Item Meta Table: +This table stores various and custom/extra information about an order item. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_order_item_id | The id of the order item to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Order Items Table: +This table stores items (or "products", also known as "downloads" in EDD) that were part of an order. It also stores various data about the items, like the tax that was on the item,. + +One way to think about this is that a "for-sale thing" is called a "product" when in an un-purchased state, and called an "item" when in a purchased state. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| parent | The ID of another order item which is considered to be the parent of this item. This is used for order items attached to refunds. The parent references the ID of the original order item that was refunded. | +| order_id | The ID of the order to which this item belongs. | +| product_id | The ID of the product which was purchased. | +| product_name | This is what the name of the product was at the time of this purchase. | +| price_id | This is the ID of the variable price which was purchased. | +| price_name | This is what the name of the variable price was at the time of this purchase. | +| cart_index | This is the position at which this item was sitting in the cart when this order took place, starting at 0 for the first position. | +| type | This indicates the type of product that this item is. In its current form, all things sold in EDD have the type of "download", and thus does not currently have any functional relevance. This is here to enable possible future changes only. | +| status | This indicates the status of this item in regards to purchase completion. Typical values include (but are not limited to) "completed", and "refunded". When set to "inherit", it will inherit the status of the order to which it belongs. When set to anything other than "inherit" it will override the status of the order, but only for this item. | +| quantity | A single item in the cart can have a quantity. This indicates that quantity. Through this column's data, a single order_item can actually represent multiple items, and the values in the subtotal and total columns reflect that quantity. | +| amount | This is what the unadjusted price of this item was at the time of purchase. It does not include tax, discounts, fees, or any other price adjusters. | +| subtotal | This is the cost of the line item in the cart including quantity, but not including tax, discounts, fees, or any other price adjusters. | +| discount | This column stores the portion of the discount from the total that applied directly to this item. | +| tax | This column stores the portion of tax from the total that applied directly to this item. | +| total | This contains the total cost of this item, including quantity, taxes, item-specific discounts (but not cart-wide discounts). *Note that this amount does not include any fees in the cart that are specific to this item. For example, a shipping fee that exists because of this item is not included in the total found in this column. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Order Transactions Table: +Where a transaction represents an actual exchange of money, this table stores all of the transactions that were part of an order. Some orders will contain multiple transactions. For example, many payment gateways (for example: Stripe and PayPal) do not allow the purchasing of multiple recurring-enabled items in a single transaction. This is because 1 item could be monthly, and another could yearly. Each item will create a different transaction on the customer's credit card, and will show up separately on the customer's credit card statement. This table helps you to keep track of which transactions were part of which order. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. | +| object_id | The ID of the row to which this transaction belongs. Typically this will be the ID of an order in the orders table. | +| object_type | The table that the object_id value can be found within, and to which row within that table this row relates. For example, the orders table is indicated by the word "order", and is typically the value that will be found here. | +| transaction_id | The ID of the transaction, which originates from the payment gateway. | +| gateway | The name of the payment gateway where this transaction took place. | +| status | The status of this transaction. For example, if complete, the status will be set to "complete". | +| total | The total amount of this transaction. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | + +## The Order Meta Table: +This table stores various and custom/extra information about an order. + +This table's data is intended to be immutable. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| meta_id | The unique id of the row, which auto increments. | +| edd_order_id | The id of the order item to which this row relates. | +| meta_key | The reference key (like a variable name) of the data in question. | +| meta_value | The value. This can be anything needed as its purpose is for anything extra. | + +## The Orders Table: +This table stores orders (called "payments" prior to EDD 3.0). It also stores various data about the order, like the customer ID, the email entered by the customer at checkout, the IP address of the machine where checkout was completed, and more. See table below for full breakdown of each column. + +This table's data is intended to be immutable. However, some column data is also intended to be mutable. The user_id customer_id, and email column values will change if an order is re-assigned to a new customer. + +| Table Column | Table Column's Description | +| ------------- | ------------- | +| id | The unique id of the row, which auto increments. This also serves as the id of the order itself. | +| parent | The ID of another order which is considered to be the parent of this order. This is used in scenarios like refund orders, which are automatically generated when a refund takes place. Refund orders use this column to refer to the original order where the item being refunded was originally purchased. Another scenario where this is used is for renewal payments done through the EDD Recurring Payments extension. Each renewal payment will use this column to indicate which order was the one where the customer originally initiated the subscription. | +| order_number | This column serves several different purposes:

    1. By default, it will be blank for every order (except "refund" orders).

    2. If the order in question is a "refund" order, this will contain a string in this format: "ORIGINAL_ORDER_NUMBER-R-THE_NUMBER_OF_REFUNDS_IN_THAT_ORDER". So if it is the 2nd refund from order #1, it will be "1-R-2".

    3. If you have "Sequential Order Numbers" enabled in your EDD settings, this column will be populated by the value determined by your settings for that.

    4. If the order in question is a refund for a "Sequentially Ordered" order, the format is the same as for "Non-Sequentially Ordered" orders, but it is important to note that the ORIGINAL_ORDER_NUMBER value will be the value from the "id" column of the original order, not the "order_number" column.

    5. Extensions may modify the way this column works. For example, the "Advanced Sequential Order Numbers" extension for EDD will put its own value in this column, overriding the values from EDD core. | +| status | This column has 2 purposes:

    1) It identifies the financial/accounting status of the order.
    a) If the transaction(s) for the order have completed successfully, this value here will be "complete".
    b) If the transaction(s) for the order have not yet completed successfully, this value here will be "pending".
    c) If the transaction(s) for the order have not completed successfully and it has been 7 days, this value here will be "abandoned".
    d) If the transaction(s) for the order failed at the payment gateway (for example, insufficient funds), this will be set to "failed".
    e) If this order has been partially refunded, the status of the order currently remains set to "complete".
    f) If all of the items in this order have been refunded, this value will be "refunded".

    2) It identifies if the order is in the trash. If the order has been put in the trash, the financial status is no longer stored here, but gets moved to the order_meta table with the key "pre_trash_status". The value in this column will then be "trash". | +| type | The type of order this is. Typical values are "sale" or "refund". | +| user_id | The ID of the user currently attached to this order. Note that this column is mutable and will change if the user attached to the EDD customer changes, or if the customer attached to an order changes. | +| customer_id | The ID of the customer currently attached to this order. Note that this column is mutable and will change if the customer attached to the EDD order changes, or if the user attached to a customer changes. | +| email | The email address currently attached to this order. Note that this column is mutable and will change if the customer attached to the EDD order changes, or if the customer's email is updated. | +| ip | The IP address of the machine on which this order was completed. | +| gateway | A string representing the payment gateway which was used to complete the payments on this order. | +| mode | This stores whether the order was done in test mode or live mode. | +| currency | The 3 letter currency code which this order used/will-use. | +| payment_key | A unique key representing this payment. This key is generated by combining a few different values about this order, like the email address, the date, an optional auth key which can be defined as a constant, and a unique id generated by the uniqid function in PHP. See class-edd-payment.php for the full breakdown of how this is generated. | +| subtotal | This is the amount of the items in the cart added together. It does not include any taxes or discounts. Note: Fees are considered to be both line items and adjustments. In relation to the orders table, fees are treated as line items, and are thus included in the subtotal. But note that they are the only "adjustments" that are included in the subtotal, as other adjustments are not included in the subtotal. | +| discount | This is the total amount of discount(s) that were applied to the order. | +| tax | This is the total amount of the tax that was applied to the order. | +| total | This is the total amount of the order. | +| date_created | The date this row was created. | +| date_modified | The date this row was last modified. | +| uuid | A unique identifying string representing this row. | diff --git a/includes/database/engine/class-base.php b/includes/database/engine/class-base.php new file mode 100644 index 00000000000..cc837bc711a --- /dev/null +++ b/includes/database/engine/class-base.php @@ -0,0 +1,337 @@ +{$key}; + } + + // Return null if not exists + return null; + } + + /** + * Converts the given object to an array. + * + * @since 1.0.0 + * + * @return array Array version of the given object. + */ + public function to_array() { + return get_object_vars( $this ); + } + + /** Protected *************************************************************/ + + /** + * Maybe append the prefix to string. + * + * @since 1.0.0 + * + * @param string $string + * @param string $sep + * @return string + */ + protected function apply_prefix( $string = '', $sep = '_' ) { + return ! empty( $this->prefix ) + ? "{$this->prefix}{$sep}{$string}" + : $string; + } + + /** + * Return the first letters of a string of words with a separator. + * + * Used primarily to guess at table aliases when none is manually set. + * + * Applies the following formatting to a string: + * - Trim whitespace + * - No accents + * - No trailing underscores + * + * @since 1.0.0 + * + * @param string $string + * @param string $sep + * @return string + */ + protected function first_letters( $string = '', $sep = '_' ) { + + // Set empty default return value + $retval = ''; + + // Bail if empty or not a string + if ( empty( $string ) || ! is_string( $string ) ) { + return $retval; + } + + // Trim spaces off the ends + $unspace = trim( $string ); + $accents = remove_accents( $unspace ); + $lower = strtolower( $accents ); + $parts = explode( $sep, $lower ); + + // Loop through parts and concatenate the first letters together + foreach ( $parts as $part ) { + $retval .= substr( $part, 0, 1 ); + } + + // Return the result + return $retval; + } + + /** + * Sanitize a table name string. + * + * Used to make sure that a table name value meets MySQL expectations. + * + * Applies the following formatting to a string: + * - Trim whitespace + * - No accents + * - No special characters + * - No hyphens + * - No double underscores + * - No trailing underscores + * + * @since 1.0.0 + * + * @param string $name The name of the database table + * + * @return string Sanitized database table name + */ + protected function sanitize_table_name( $name = '' ) { + + // Bail if empty or not a string + if ( empty( $name ) || ! is_string( $name ) ) { + return false; + } + + // Trim spaces off the ends + $unspace = trim( $name ); + + // Only non-accented table names (avoid truncation) + $accents = remove_accents( $unspace ); + + // Only lowercase characters, hyphens, and dashes (avoid index corruption) + $lower = sanitize_key( $accents ); + + // Replace hyphens with single underscores + $under = str_replace( '-', '_', $lower ); + + // Single underscores only + $single = str_replace( '__', '_', $under ); + + // Remove trailing underscores + $clean = trim( $single, '_' ); + + // Bail if table name was garbaged + if ( empty( $clean ) ) { + return false; + } + + // Return the cleaned table name + return $clean; + } + + /** + * Set class variables from arguments. + * + * @since 1.0.0 + * @param array $args + */ + protected function set_vars( $args = array() ) { + + // Bail if empty or not an array + if ( empty( $args ) ) { + return; + } + + // Cast to an array + if ( ! is_array( $args ) ) { + $args = (array) $args; + } + + // Set all properties + foreach ( $args as $key => $value ) { + $this->{$key} = $value; + } + } + + /** + * Return the global database interface. + * + * See: https://core.trac.wordpress.org/ticket/31556 + * + * @since 1.0.0 + * + * @return \wpdb Database interface, or False if not set + */ + protected function get_db() { + + // Default database return value (might change) + $retval = false; + + // Look for a commonly used global database interface + if ( isset( $GLOBALS[ $this->db_global ] ) ) { + $retval = $GLOBALS[ $this->db_global ]; + } + + /* + * Developer note: + * + * It should be impossible for a database table to be interacted with + * before the primary database interface it is setup. + * + * However, because applications are complicated, it is unsafe to assume + * anything, so this silently returns false instead of halting everything. + * + * If you are here because this method is returning false for you, that + * means the database table is being invoked too early in the lifecycle + * of the application. + * + * In WordPress, that means before the $wpdb global is created; in other + * environments, you will need to adjust accordingly. + */ + + // Return the database interface + return $retval; + } + + /** + * Check if an operation succeeded. + * + * @since 1.0.0 + * + * @param mixed $result + * @return bool + */ + protected function is_success( $result = false ) { + + // Bail if no row exists + if ( empty( $result ) ) { + $retval = false; + + // Bail if an error occurred + } elseif ( is_wp_error( $result ) ) { + $this->last_error = $result; + $retval = false; + + // No errors + } else { + $retval = true; + } + + // Return the result + return (bool) $retval; + } +} diff --git a/includes/database/engine/class-column.php b/includes/database/engine/class-column.php new file mode 100644 index 00000000000..b72c1972fda --- /dev/null +++ b/includes/database/engine/class-column.php @@ -0,0 +1,965 @@ +parse_args( $args ); + + // Maybe set variables from arguments + if ( ! empty( $r ) ) { + $this->set_vars( $r ); + } + } + + /** Argument Handlers *****************************************************/ + + /** + * Parse column arguments + * + * @since 1.0.0 + * @param array $args Default empty array. + * @return array + */ + private function parse_args( $args = array() ) { + + // Parse arguments + $r = wp_parse_args( $args, array( + + // Table + 'name' => '', + 'type' => '', + 'length' => '', + 'unsigned' => false, + 'zerofill' => false, + 'binary' => false, + 'allow_null' => false, + 'default' => '', + 'extra' => '', + 'encoding' => $this->get_db()->charset, + 'collation' => $this->get_db()->collate, + 'comment' => '', + + // Query + 'pattern' => false, + 'searchable' => false, + 'sortable' => false, + 'date_query' => false, + 'transition' => false, + 'in' => true, + 'not_in' => true, + + // Special + 'primary' => false, + 'created' => false, + 'modified' => false, + 'uuid' => false, + + // Cache + 'cache_key' => false, + + // Validation + 'validate' => '', + + // Capabilities + 'caps' => array(), + + // Backwards Compatibility + 'aliases' => array(), + + // Column Relationships + 'relationships' => array() + ) ); + + // Force some arguments for special column types + $r = $this->special_args( $r ); + + // Set the args before they are sanitized + $this->set_vars( $r ); + + // Return array + return $this->validate_args( $r ); + } + + /** + * Validate arguments after they are parsed. + * + * @since 1.0.0 + * @param array $args Default empty array. + * @return array + */ + private function validate_args( $args = array() ) { + + // Sanitization callbacks + $callbacks = array( + 'name' => 'sanitize_key', + 'type' => 'strtoupper', + 'length' => 'intval', + 'unsigned' => 'wp_validate_boolean', + 'zerofill' => 'wp_validate_boolean', + 'binary' => 'wp_validate_boolean', + 'allow_null' => 'wp_validate_boolean', + 'default' => array( $this, 'sanitize_default' ), + 'extra' => 'wp_kses_data', + 'encoding' => 'wp_kses_data', + 'collation' => 'wp_kses_data', + 'comment' => 'wp_kses_data', + + 'primary' => 'wp_validate_boolean', + 'created' => 'wp_validate_boolean', + 'modified' => 'wp_validate_boolean', + 'uuid' => 'wp_validate_boolean', + + 'searchable' => 'wp_validate_boolean', + 'sortable' => 'wp_validate_boolean', + 'date_query' => 'wp_validate_boolean', + 'transition' => 'wp_validate_boolean', + 'in' => 'wp_validate_boolean', + 'not_in' => 'wp_validate_boolean', + 'cache_key' => 'wp_validate_boolean', + + 'pattern' => array( $this, 'sanitize_pattern' ), + 'validate' => array( $this, 'sanitize_validation' ), + 'caps' => array( $this, 'sanitize_capabilities' ), + 'aliases' => array( $this, 'sanitize_aliases' ), + 'relationships' => array( $this, 'sanitize_relationships' ) + ); + + // Default args array + $r = array(); + + // Loop through and try to execute callbacks + foreach ( $args as $key => $value ) { + + // Callback is callable + if ( isset( $callbacks[ $key ] ) && is_callable( $callbacks[ $key ] ) ) { + $r[ $key ] = call_user_func( $callbacks[ $key ], $value ); + + // Callback is malformed so just let it through to avoid breakage + } else { + $r[ $key ] = $value; + } + } + + // Return sanitized arguments + return $r; + } + + /** + * Force column arguments for special column types + * + * @since 1.0.0 + * @param array $args Default empty array. + * @return array + */ + private function special_args( $args = array() ) { + + // Primary key columns are always used as cache keys + if ( ! empty( $args['primary'] ) ) { + $args['cache_key'] = true; + + // All UUID columns need to follow a very specific pattern + } elseif ( ! empty( $args['uuid'] ) ) { + $args['name'] = 'uuid'; + $args['type'] = 'varchar'; + $args['length'] = '100'; + $args['in'] = false; + $args['not_in'] = false; + $args['searchable'] = false; + $args['sortable'] = false; + } + + // Return args + return (array) $args; + } + + /** Public Helpers ********************************************************/ + + /** + * Return if a column type is numeric or not. + * + * @since 1.0.0 + * @return bool + */ + public function is_numeric() { + return $this->is_type( array( + 'tinyint', + 'int', + 'mediumint', + 'bigint' + ) ); + } + + /** Private Helpers *******************************************************/ + + /** + * Return if this column is of a certain type. + * + * @since 1.0.0 + * @param mixed $type Default empty string. The type to check. Also accepts an array. + * @return bool True if of type, False if not + */ + private function is_type( $type = '' ) { + + // If string, cast to array + if ( is_string( $type ) ) { + $type = (array) $type; + } + + // Make them lowercase + $types = array_map( 'strtolower', $type ); + + // Return if match or not + return (bool) in_array( strtolower( $this->type ), $types, true ); + } + + /** Private Sanitizers ****************************************************/ + + /** + * Sanitize capabilities array + * + * @since 1.0.0 + * @param array $caps Default empty array. + * @return array + */ + private function sanitize_capabilities( $caps = array() ) { + return wp_parse_args( $caps, array( + 'select' => 'exist', + 'insert' => 'exist', + 'update' => 'exist', + 'delete' => 'exist' + ) ); + } + + /** + * Sanitize aliases array using `sanitize_key()` + * + * @since 1.0.0 + * @param array $aliases Default empty array. + * @return array + */ + private function sanitize_aliases( $aliases = array() ) { + return array_map( 'sanitize_key', $aliases ); + } + + /** + * Sanitize relationships array + * + * @todo + * @since 1.0.0 + * @param array $relationships Default empty array. + * @return array + */ + private function sanitize_relationships( $relationships = array() ) { + return array_filter( $relationships ); + } + + /** + * Sanitize the default value + * + * @since 1.0.0 + * @param string $default + * @return string|null + */ + private function sanitize_default( $default = '' ) { + + // Null + if ( ( true === $this->allow_null ) && is_null( $default ) ) { + return null; + + // String + } elseif ( is_string( $default ) ) { + return wp_kses_data( $default ); + + // Integer + } elseif ( $this->is_numeric( $default ) ) { + return (int) $default; + } + + // @todo datetime, decimal, and other column types + + // Unknown, so return the default's default + return ''; + } + + /** + * Sanitize the pattern + * + * @since 1.0.0 + * @param mixed $pattern + * @return string + */ + private function sanitize_pattern( $pattern = false ) { + + // Allowed patterns + $allowed_patterns = array( '%s', '%d', '%f' ); + + // Return pattern if allowed + if ( in_array( $pattern, $allowed_patterns, true ) ) { + return $pattern; + } + + // Fallback to digit or string + return $this->is_numeric() + ? '%d' + : '%s'; + } + + /** + * Sanitize the validation callback + * + * @since 1.0.0 + * @param string $callback Default empty string. A callable PHP function name or method + * @return string The most appropriate callback function for the value + */ + private function sanitize_validation( $callback = '' ) { + + // Return callback if it's callable + if ( is_callable( $callback ) ) { + return $callback; + } + + // UUID special column + if ( true === $this->uuid ) { + $callback = array( $this, 'validate_uuid' ); + + // Datetime fallback + } elseif ( $this->is_type( 'datetime' ) ) { + $callback = array( $this, 'validate_datetime' ); + + // Decimal fallback + } elseif ( $this->is_type( 'decimal' ) ) { + $callback = array( $this, 'validate_decimal' ); + + // Intval fallback + } elseif ( $this->is_numeric() ) { + $callback = 'intval'; + } + + // Return the callback + return $callback; + } + + /** Public Validators *****************************************************/ + + /** + * Fallback to validate a datetime value if no other is set. + * + * This assumes NO_ZERO_DATES is off or overridden. + * + * If MySQL drops support for zero dates, this method will need to be + * updated to support different default values based on the environment. + * + * @since 1.0.0 + * @param string $value Default ''. A datetime value that needs validating + * @return string A valid datetime value + */ + public function validate_datetime( $value = '' ) { + + // Handle "empty" values + if ( empty( $value ) || ( '0000-00-00 00:00:00' === $value ) ) { + $value = ! empty( $this->default ) + ? $this->default + : ''; + + // Convert to MySQL datetime format via date() && strtotime + } elseif ( function_exists( 'date' ) ) { + $value = date( 'Y-m-d H:i:s', strtotime( $value ) ); + } + + // Return the validated value + return $value; + } + + /** + * Validate a decimal + * + * (Recommended decimal column length is '18,9'.) + * + * This is used to validate a mixed value before it is saved into a decimal + * column in a database table. + * + * Uses number_format() which does rounding to the last decimal if your + * value is longer than specified. + * + * @since 1.0.0 + * @param mixed $value Default empty string. The decimal value to validate + * @param int $decimals Default 9. The number of decimal points to accept + * @return float + */ + public function validate_decimal( $value = 0, $decimals = 9 ) { + + // Protect against non-numeric values + if ( ! is_numeric( $value ) ) { + $value = 0; + } + + // Protect against non-numeric decimals + if ( ! is_numeric( $decimals ) ) { + $decimals = 9; + } + + // Is the value negative? + $negative_exponent = ( $value < 0 ) + ? -1 + : 1; + + // Only numbers and period + $value = preg_replace( '/[^0-9\.]/', '', (string) $value ); + + // Format to number of decimals, and cast as float + $formatted = number_format( $value, $decimals, '.', '' ); + + // Adjust for negative values + $retval = $formatted * $negative_exponent; + + // Return + return $retval; + } + + /** + * Validate a UUID. + * + * This uses the v4 algorithm to generate a UUID that is used to uniquely + * and universally identify a given database row without any direct + * connection or correlation to the data in that row. + * + * From http://php.net/manual/en/function.uniqid.php#94959 + * + * @since 1.0.0 + * @param string $value The UUID value (empty on insert, string on update) + * @return string Generated UUID. + */ + public function validate_uuid( $value = '' ) { + + // Default URN UUID prefix + $prefix = 'urn:uuid:'; + + // Bail if not empty and correctly prefixed + // (UUIDs should _never_ change once they are set) + if ( ! empty( $value ) && ( 0 === strpos( $value, $prefix ) ) ) { + return $value; + } + + // Put the pieces together + $value = sprintf( "{$prefix}%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + + // 32 bits for "time_low" + mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), + + // 16 bits for "time_mid" + mt_rand( 0, 0xffff ), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand( 0, 0x0fff ) | 0x4000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand( 0, 0x3fff ) | 0x8000, + + // 48 bits for "node" + mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) + ); + + // Return the new UUID + return $value; + } + + /** Table Helpers *********************************************************/ + + /** + * Return a string representation of what this column's properties look like + * in a MySQL. + * + * @todo + * @since 1.0.0 + * @return string + */ + public function get_create_string() { + + // Default return val + $retval = ''; + + // Bail if no name + if ( ! empty( $this->name ) ) { + $retval .= $this->name; + } + + // Type + if ( ! empty( $this->type ) ) { + $retval .= " {$this->type}"; + } + + // Length + if ( ! empty( $this->length ) ) { + $retval .= '(' . $this->length . ')'; + } + + // Unsigned + if ( ! empty( $this->unsigned ) ) { + $retval .= " unsigned"; + } + + // Zerofill + if ( ! empty( $this->zerofill ) ) { + // TBD + } + + // Binary + if ( ! empty( $this->binary ) ) { + // TBD + } + + // Allow null + if ( ! empty( $this->allow_null ) ) { + $retval .= " NOT NULL "; + } + + // Default + if ( ! empty( $this->default ) ) { + $retval .= " default '{$this->default}'"; + + // A literal false means no default value + } elseif ( false !== $this->default ) { + + // Numeric + if ( $this->is_numeric() ) { + $retval .= " default '0'"; + } elseif ( $this->is_type( 'datetime' ) ) { + $retval .= " default '0000-00-00 00:00:00'"; + } else { + $retval .= " default ''"; + } + } + + // Extra + if ( ! empty( $this->extra ) ) { + $retval .= " {$this->extra}"; + } + + // Encoding + if ( ! empty( $this->encoding ) ) { + + } else { + + } + + // Collation + if ( ! empty( $this->collation ) ) { + + } else { + + } + + // Return the create string + return $retval; + } +} diff --git a/includes/database/engine/class-compare.php b/includes/database/engine/class-compare.php new file mode 100644 index 00000000000..b82ca271c93 --- /dev/null +++ b/includes/database/engine/class-compare.php @@ -0,0 +1,157 @@ + array(), + 'join' => array(), + ); + + if ( isset( $clause['compare'] ) ) { + $clause['compare'] = strtoupper( $clause['compare'] ); + } else { + $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; + } + + if ( ! in_array( + $clause['compare'], array( + '=', + '!=', + '>', + '>=', + '<', + '<=', + 'LIKE', + 'NOT LIKE', + 'IN', + 'NOT IN', + 'BETWEEN', + 'NOT BETWEEN', + 'EXISTS', + 'NOT EXISTS', + 'REGEXP', + 'NOT REGEXP', + 'RLIKE', + ), true + ) ) { + $clause['compare'] = '='; + } + + if ( isset( $clause['compare_key'] ) && 'LIKE' === strtoupper( $clause['compare_key'] ) ) { + $clause['compare_key'] = strtoupper( $clause['compare_key'] ); + } else { + $clause['compare_key'] = '='; + } + + $compare = $clause['compare']; + $compare_key = $clause['compare_key']; + + // Build the WHERE clause. + + // Column name and value. + if ( array_key_exists( 'key', $clause ) && array_key_exists( 'value', $clause ) ) { + $column = sanitize_key( $clause['key'] ); + $value = $clause['value']; + + if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) { + if ( ! is_array( $value ) ) { + $value = preg_split( '/[,\s]+/', $value ); + } + } else { + $value = trim( $value ); + } + + switch ( $compare ) { + case 'IN': + case 'NOT IN': + $compare_string = '(' . substr( str_repeat( ',%s', count( $value ) ), 1 ) . ')'; + $where = $wpdb->prepare( $compare_string, $value ); + break; + + case 'BETWEEN': + case 'NOT BETWEEN': + $value = array_slice( $value, 0, 2 ); + $where = $wpdb->prepare( '%s AND %s', $value ); + break; + + case 'LIKE': + case 'NOT LIKE': + $value = '%' . $wpdb->esc_like( $value ) . '%'; + $where = $wpdb->prepare( '%s', $value ); + break; + + // EXISTS with a value is interpreted as '='. + case 'EXISTS': + $compare = '='; + $where = $wpdb->prepare( '%s', $value ); + break; + + // 'value' is ignored for NOT EXISTS. + case 'NOT EXISTS': + $where = ''; + break; + + default: + $where = $wpdb->prepare( '%s', $value ); + break; + + } + + if ( $where ) { + $sql_chunks['where'][] = "{$column} {$compare} {$where}"; + } + } + + /* + * Multiple WHERE clauses (for meta_key and meta_value) should + * be joined in parentheses. + */ + if ( 1 < count( $sql_chunks['where'] ) ) { + $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); + } + + return $sql_chunks; + } +} diff --git a/includes/database/engine/class-date.php b/includes/database/engine/class-date.php new file mode 100644 index 00000000000..f40dc6566d3 --- /dev/null +++ b/includes/database/engine/class-date.php @@ -0,0 +1,1242 @@ +', + '>=', + '<', + '<=', + 'IN', + 'NOT IN', + 'BETWEEN', + 'NOT BETWEEN' + ); + + /** + * Supported multi-value comparison types + * + * @since 1.1.0 + * @var array + */ + public $multi_value_keys = array( + 'IN', + 'NOT IN', + 'BETWEEN', + 'NOT BETWEEN' + ); + + /** + * Supported relation types + * + * @since 1.1.0 + * @var array + */ + public $relation_keys = array( + 'OR', + 'AND' + ); + + /** + * Constructor. + * + * Time-related parameters that normally require integer values ('year', 'month', 'week', 'dayofyear', 'day', + * 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second') accept arrays of integers for some values of + * 'compare'. When 'compare' is 'IN' or 'NOT IN', arrays are accepted; when 'compare' is 'BETWEEN' or 'NOT + * BETWEEN', arrays of two valid values are required. See individual argument descriptions for accepted values. + * + * @since 1.0.0 + * + * @param array $date_query { + * Array of date query clauses. + * + * @type array { + * @type string $column Optional. The column to query against. If undefined, inherits the value of + * 'date_created'. Accepts 'date_created', 'date_created_gmt', + * 'post_modified','post_modified_gmt', 'comment_date', 'comment_date_gmt'. + * Default 'date_created'. + * @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=', '<', '<=', + * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default '='. + * @type string $relation Optional. The boolean relationship between the date queries. Accepts 'OR' or 'AND'. + * Default 'OR'. + * @type array { + * Optional. An array of first-order clause parameters, or another fully-formed date query. + * + * @type string|array $before { + * Optional. Date to retrieve posts before. Accepts `strtotime()`-compatible string, + * or array of 'year', 'month', 'day' values. + * + * @type string $year The four-digit year. Default empty. Accepts any four-digit year. + * @type string $month Optional when passing array.The month of the year. + * Default (string:empty)|(array:1). Accepts numbers 1-12. + * @type string $day Optional when passing array.The day of the month. + * Default (string:empty)|(array:1). Accepts numbers 1-31. + * } + * @type string|array $after { + * Optional. Date to retrieve posts after. Accepts `strtotime()`-compatible string, + * or array of 'year', 'month', 'day' values. + * + * @type string $year The four-digit year. Accepts any four-digit year. Default empty. + * @type string $month Optional when passing array. The month of the year. Accepts numbers 1-12. + * Default (string:empty)|(array:12). + * @type string $day Optional when passing array.The day of the month. Accepts numbers 1-31. + * Default (string:empty)|(array:last day of month). + * } + * @type string $column Optional. Used to add a clause comparing a column other than the + * column specified in the top-level `$column` parameter. Accepts + * 'date_created', 'date_created_gmt', 'post_modified', 'post_modified_gmt', + * 'comment_date', 'comment_date_gmt'. Default is the value of + * top-level `$column`. + * @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=', + * '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. 'IN', + * 'NOT IN', 'BETWEEN', and 'NOT BETWEEN'. Comparisons support + * arrays in some time-related parameters. Default '='. + * @type bool $inclusive Optional. Include results from dates specified in 'before' or + * 'after'. Default false. + * @type int|array $year Optional. The four-digit year number. Accepts any four-digit year + * or an array of years if `$compare` supports it. Default empty. + * @type int|array $month Optional. The two-digit month number. Accepts numbers 1-12 or an + * array of valid numbers if `$compare` supports it. Default empty. + * @type int|array $week Optional. The week number of the year. Accepts numbers 0-53 or an + * array of valid numbers if `$compare` supports it. Default empty. + * @type int|array $dayofyear Optional. The day number of the year. Accepts numbers 1-366 or an + * array of valid numbers if `$compare` supports it. + * @type int|array $day Optional. The day of the month. Accepts numbers 1-31 or an array + * of valid numbers if `$compare` supports it. Default empty. + * @type int|array $dayofweek Optional. The day number of the week. Accepts numbers 1-7 (1 is + * Sunday) or an array of valid numbers if `$compare` supports it. + * Default empty. + * @type int|array $dayofweek_iso Optional. The day number of the week (ISO). Accepts numbers 1-7 + * (1 is Monday) or an array of valid numbers if `$compare` supports it. + * Default empty. + * @type int|array $hour Optional. The hour of the day. Accepts numbers 0-23 or an array + * of valid numbers if `$compare` supports it. Default empty. + * @type int|array $minute Optional. The minute of the hour. Accepts numbers 0-60 or an array + * of valid numbers if `$compare` supports it. Default empty. + * @type int|array $second Optional. The second of the minute. Accepts numbers 0-60 or an + * array of valid numbers if `$compare` supports it. Default empty. + * } + * } + * } + */ + public function __construct( $date_query = array() ) { + + // Bail if not an array. + if ( ! is_array( $date_query ) ) { + return; + } + + // Support for passing time-based keys in the top level of the array. + if ( ! isset( $date_query[0] ) && ! empty( $date_query ) ) { + $date_query = array( $date_query ); + } + + // Bail if empty. + if ( empty( $date_query ) ) { + return; + } + + // Set column, compare, relation, and queries. + $this->column = $this->get_column( $date_query ); + $this->compare = $this->get_compare( $date_query ); + $this->relation = $this->get_relation( $date_query ); + $this->queries = $this->sanitize_query( $date_query ); + } + + /** + * Recursive-friendly query sanitizer. + * + * Ensures that each query-level clause has a 'relation' key, and that + * each first-order clause contains all the necessary keys from + * `$defaults`. + * + * @since 1.0.0 + * + * @param array $queries + * @param array $parent_query + * + * @return array Sanitized queries. + */ + public function sanitize_query( $queries = array(), $parent_query = array() ) { + + // Default return value. + $retval = array(); + + // Setup defaults. + $defaults = array( + 'column' => $this->get_column(), + 'compare' => $this->get_compare(), + 'relation' => $this->get_relation() + ); + + // Numeric keys should always have array values. + foreach ( $queries as $qkey => $qvalue ) { + if ( is_numeric( $qkey ) && ! is_array( $qvalue ) ) { + unset( $queries[ $qkey ] ); + } + } + + // Each query should have a value for each default key. + // Inherit from the parent when possible. + foreach ( $defaults as $dkey => $dvalue ) { + + // Skip if already set. + if ( isset( $queries[ $dkey ] ) ) { + continue; + } + + // Set the query. + if ( isset( $parent_query[ $dkey ] ) ) { + $queries[ $dkey ] = $parent_query[ $dkey ]; + } else { + $queries[ $dkey ] = $dvalue; + } + } + + // Validate the dates passed in the query. + if ( $this->is_first_order_clause( $queries ) ) { + $this->validate_date_values( $queries ); + } + + // Add queries to return array. + foreach ( $queries as $key => $q ) { + + // This is a first-order query. Trust the values and sanitize when building SQL. + if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) { + $retval[ $key ] = $q; + + // Any array without a time key is another query, so we recurse. + } else { + $retval[] = $this->sanitize_query( $q, $queries ); + } + } + + // Return sanitized queries. + return $retval; + } + + /** + * Determine whether this is a first-order clause. + * + * Checks to see if the current clause has any time-related keys. + * If so, it's first-order. + * + * @since 1.0.0 + * + * @param array $query Query clause. + * + * @return bool True if this is a first-order clause. + */ + protected function is_first_order_clause( $query = array() ) { + $time_keys = array_intersect( $this->time_keys, array_keys( $query ) ); + + return ! empty( $time_keys ); + } + + /** + * Determines and validates what comparison operator to use. + * + * @since 1.0.0 + * + * @param array $query A date query or a date subquery. + * + * @return string The comparison operator. + */ + public function get_column( $query = array() ) { + + // Use column if passed + $retval = ! empty( $query['column'] ) + ? esc_sql( $this->validate_column( $query['column'] ) ) + : $this->column; + + return $retval; + } + + /** + * Determines and validates what comparison operator to use. + * + * @since 1.0.0 + * + * @param array $query A date query or a date subquery. + * + * @return string The comparison operator. + */ + public function get_compare( $query = array() ) { + + // Compare must be in the allowed array + $retval = ! empty( $query['compare'] ) && in_array( $query['compare'], $this->comparison_keys, true ) + ? strtoupper( $query['compare'] ) + : $this->compare; + + return $retval; + } + + /** + * Determines and validates what relation to use. + * + * @since 1.0.0 + * + * @param array $query A date query or a date subquery. + * @return string The relation operator. + */ + public function get_relation( $query = array() ) { + + // Relation must be in the allowed array + $retval = ! empty( $query['relation'] ) && in_array( $query['relation'], $this->relation_keys, true ) + ? strtoupper( $query['relation'] ) + : $this->relation; + + return $retval; + } + + /** + * Validates the given date_query values. + * + * Note that date queries with invalid date ranges are allowed to + * continue (though of course no items will be found for impossible dates). + * This method only generates debug notices for these cases. + * + * @since 1.0.0 + * + * @param array $date_query The date_query array. + * + * @return bool True if all values in the query are valid, false if one or more fail. + */ + public function validate_date_values( $date_query = array() ) { + + // Bail if empty. + if ( empty( $date_query ) ) { + return false; + } + + $valid = true; + + /* + * Validate 'before' and 'after' up front, then let the + * validation routine continue to be sure that all invalid + * values generate errors too. + */ + if ( array_key_exists( 'before', $date_query ) && is_array( $date_query['before'] ) ) { + $valid = $this->validate_date_values( $date_query['before'] ); + } + + if ( array_key_exists( 'after', $date_query ) && is_array( $date_query['after'] ) ) { + $valid = $this->validate_date_values( $date_query['after'] ); + } + + // Values are passthroughs. + if ( array_key_exists( 'value', $date_query ) ) { + $valid = true; + } + + // Array containing all min-max checks. + $min_max_checks = array(); + + // Days per year. + if ( array_key_exists( 'year', $date_query ) ) { + /* + * If a year exists in the date query, we can use it to get the days. + * If multiple years are provided (as in a BETWEEN), use the first one. + */ + if ( is_array( $date_query['year'] ) ) { + $_year = reset( $date_query['year'] ); + } else { + $_year = $date_query['year']; + } + + $max_days_of_year = date( 'z', mktime( 0, 0, 0, 12, 31, $_year ) ) + 1; + + // Otherwise we use the max of 366 (leap-year) + } else { + $max_days_of_year = 366; + } + + // Days of year. + $min_max_checks['dayofyear'] = array( + 'min' => 1, + 'max' => $max_days_of_year, + ); + + // Days per week. + $min_max_checks['dayofweek'] = array( + 'min' => 1, + 'max' => 7, + ); + + // Days per week. + $min_max_checks['dayofweek_iso'] = array( + 'min' => 1, + 'max' => 7, + ); + + // Months per year. + $min_max_checks['month'] = array( + 'min' => 1, + 'max' => 12, + ); + + // Weeks per year. + if ( isset( $_year ) ) { + /* + * If we have a specific year, use it to calculate number of weeks. + * Note: the number of weeks in a year is the date in which Dec 28 appears. + */ + $week_count = date( 'W', mktime( 0, 0, 0, 12, 28, $_year ) ); + + // Otherwise set the week-count to a maximum of 53. + } else { + $week_count = 53; + } + + // Weeks per year. + $min_max_checks['week'] = array( + 'min' => 1, + 'max' => $week_count, + ); + + // Days per month. + $min_max_checks['day'] = array( + 'min' => 1, + 'max' => 31, + ); + + // Hours per day. + $min_max_checks['hour'] = array( + 'min' => 0, + 'max' => 23, + ); + + // Minutes per hour. + $min_max_checks['minute'] = array( + 'min' => 0, + 'max' => 59, + ); + + // Seconds per minute. + $min_max_checks['second'] = array( + 'min' => 0, + 'max' => 59, + ); + + // Loop through min/max checks. + foreach ( $min_max_checks as $key => $check ) { + + // Skip if not in query. + if ( ! array_key_exists( $key, $date_query ) ) { + continue; + } + + // Check for invalid values. + foreach ( (array) $date_query[ $key ] as $_value ) { + $is_between = ( $_value >= $check['min'] ) && ( $_value <= $check['max'] ); + + if ( ! is_numeric( $_value ) || empty( $is_between ) ) { + $valid = false; + } + } + } + + // Bail if invalid query. + if ( false === $valid ) { + return $valid; + } + + // Check what kinds of dates are being queried for. + $day_exists = array_key_exists( 'day', $date_query ) && is_numeric( $date_query['day'] ); + $month_exists = array_key_exists( 'month', $date_query ) && is_numeric( $date_query['month'] ); + $year_exists = array_key_exists( 'year', $date_query ) && is_numeric( $date_query['year'] ); + + // Checking at least day & month. + if ( ! empty( $day_exists ) && ! empty( $month_exists ) ) { + + // Check for year query, or fallback to 2012 (for flexibility). + $year = ! empty( $year_exists ) + ? $date_query['year'] + : '2012'; + + // Parse the date to check. + $to_check = sprintf( '%s-%s-%s', $year, $date_query['month'], $date_query['day'] ); + + // Check the date. + if ( ! $this->checkdate( $date_query['month'], $date_query['day'], $year, $to_check ) ) { + $valid = false; + } + } + + // Return if valid or not + return $valid; + } + + /** + * Validates a column name parameter. + * + * @since 1.0.0 + * + * @param string $column The user-supplied column name. + * + * @return string A validated column name value. + */ + public function validate_column( $column = '' ) { + return preg_replace( '/[^a-zA-Z0-9_$\.]/', '', $column ); + } + + /** + * Generate WHERE clause to be appended to a main query. + * + * @since 1.0.0 + * + * @return string MySQL WHERE clauses. + */ + public function get_sql() { + $sql = $this->get_sql_clauses(); + + /** + * Filters the date query clauses. + * + * @since 1.0.0 + * + * @param string $sql Clauses of the date query. + * @param Date $this The Date query instance. + */ + return apply_filters( 'get_date_sql', $sql, $this ); + } + + /** + * Generate SQL clauses to be appended to a main query. + * + * Called by the public Date::get_sql(), this method is abstracted + * out to maintain parity with the other Query classes. + * + * @since 1.0.0 + * + * @return array { + * Array containing JOIN and WHERE SQL clauses to append to the main query. + * + * @type string $join SQL fragment to append to the main JOIN clause. + * @type string $where SQL fragment to append to the main WHERE clause. + * } + */ + protected function get_sql_clauses() { + $sql = $this->get_sql_for_query( $this->queries ); + + if ( ! empty( $sql['where'] ) ) { + $sql['where'] = ' AND ' . $sql['where']; + } + + return apply_filters( 'get_date_sql_clauses', $sql, $this ); + } + + /** + * Generate SQL clauses for a single query array. + * + * If nested subqueries are found, this method recurses the tree to + * produce the properly nested SQL. + * + * @since 1.0.0 + * + * @param array $query Query to parse. + * @param int $depth Optional. Number of tree levels deep we currently are. + * Used to calculate indentation. Default 0. + * @return array { + * Array containing JOIN and WHERE SQL clauses to append to a single query array. + * + * @type string $join SQL fragment to append to the main JOIN clause. + * @type string $where SQL fragment to append to the main WHERE clause. + * } + */ + protected function get_sql_for_query( $query = array(), $depth = 0 ) { + $sql_chunks = array( + 'join' => array(), + 'where' => array(), + ); + + $sql = array( + 'join' => '', + 'where' => '', + ); + + $indent = ''; + for ( $i = 0; $i < $depth; $i++ ) { + $indent .= ' '; + } + + foreach ( $query as $key => $clause ) { + + if ( 'relation' === $key ) { + $relation = $query['relation']; + + } elseif ( is_array( $clause ) ) { + + // This is a first-order clause. + if ( $this->is_first_order_clause( $clause ) ) { + + // Get clauses & where count + $clause_sql = $this->get_sql_for_clause( $clause, $query ); + $where_count = count( $clause_sql['where'] ); + + if ( ! $where_count ) { + $sql_chunks['where'][] = ''; + + } elseif ( 1 === $where_count ) { + $sql_chunks['where'][] = $clause_sql['where'][0]; + + } else { + $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; + } + + $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); + + // This is a subquery, so we recurse. + } else { + $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); + + $sql_chunks['where'][] = $clause_sql['where']; + $sql_chunks['join'][] = $clause_sql['join']; + } + } + } + + // Filter to remove empties. + $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); + $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); + + if ( empty( $relation ) ) { + $relation = 'AND'; + } + + // Filter duplicate JOIN clauses and combine into a single string. + if ( ! empty( $sql_chunks['join'] ) ) { + $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); + } + + // Generate a single WHERE clause with proper brackets and indentation. + if ( ! empty( $sql_chunks['where'] ) ) { + $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; + } + + // Filter and return + return apply_filters( 'get_date_sql_for_query', $sql, $query, $depth, $this ); + } + + /** + * Turns a first-order date query into SQL for a WHERE clause. + * + * @since 1.0.0 + * + * @param array $query Date query clause. + * @param array $parent_query Parent query of the current date query. + * + * @return array { + * Array containing JOIN and WHERE SQL clauses to append to the main query. + * + * @type string $join SQL fragment to append to the main JOIN clause. + * @type string $where SQL fragment to append to the main WHERE clause. + * } + */ + protected function get_sql_for_clause( $query = array(), $parent_query = array() ) { + + // The sub-parts of a $where part. + $where_parts = array(); + + $column = $this->get_column( $query ); + $compare = $this->get_compare( $query ); + $inclusive = ! empty( $query['inclusive'] ); + + // Assign greater-than and less-than values. + $lt = '<'; + $gt = '>'; + + if ( $inclusive ) { + $lt .= '='; + $gt .= '='; + } + + // Range queries. + if ( ! empty( $query['after'] ) ) { + $where_parts[] = $this->get_db()->prepare( "{$column} {$gt} %s", $this->build_mysql_datetime( $query['after'], ! $inclusive ) ); + } + + if ( ! empty( $query['before'] ) ) { + $where_parts[] = $this->get_db()->prepare( "{$column} {$lt} %s", $this->build_mysql_datetime( $query['before'], $inclusive ) ); + } + + // Specific value queries. + if ( isset( $query['year'] ) && $value = $this->build_numeric_value( $compare, $query['year'] ) ) { + $where_parts[] = "YEAR( {$column} ) {$compare} {$value}"; + } + + if ( isset( $query['month'] ) && $value = $this->build_numeric_value( $compare, $query['month'] ) ) { + $where_parts[] = "MONTH( {$column} ) {$compare} {$value}"; + } elseif ( isset( $query['monthnum'] ) && $value = $this->build_numeric_value( $compare, $query['monthnum'] ) ) { + $where_parts[] = "MONTH( {$column} ) {$compare} {$value}"; + } + + if ( isset( $query['week'] ) && false !== ( $value = $this->build_numeric_value( $compare, $query['week'] ) ) ) { + $where_parts[] = _wp_mysql_week( $column ) . " {$compare} {$value}"; + } elseif ( isset( $query['w'] ) && false !== ( $value = $this->build_numeric_value( $compare, $query['w'] ) ) ) { + $where_parts[] = _wp_mysql_week( $column ) . " {$compare} {$value}"; + } + + if ( isset( $query['dayofyear'] ) && $value = $this->build_numeric_value( $compare, $query['dayofyear'] ) ) { + $where_parts[] = "DAYOFYEAR( {$column} ) {$compare} {$value}"; + } + + if ( isset( $query['day'] ) && $value = $this->build_numeric_value( $compare, $query['day'] ) ) { + $where_parts[] = "DAYOFMONTH( {$column} ) {$compare} {$value}"; + } + + if ( isset( $query['dayofweek'] ) && $value = $this->build_numeric_value( $compare, $query['dayofweek'] ) ) { + $where_parts[] = "DAYOFWEEK( {$column} ) {$compare} {$value}"; + } + + if ( isset( $query['dayofweek_iso'] ) && $value = $this->build_numeric_value( $compare, $query['dayofweek_iso'] ) ) { + $where_parts[] = "WEEKDAY( {$column} ) + 1 {$compare} {$value}"; + } + + // Straight value compare + if ( isset( $query['value'] ) ) { + $value = $this->build_value( $compare, $query['value'] ); + $where_parts[] = "{$column} {$compare} $value"; + } + + // Hour/Minute/Second + if ( isset( $query['hour'] ) || isset( $query['minute'] ) || isset( $query['second'] ) ) { + + // Avoid notices. + foreach ( array( 'hour', 'minute', 'second' ) as $unit ) { + if ( ! isset( $query[ $unit ] ) ) { + $query[ $unit ] = null; + } + } + + $time_query = $this->build_time_query( $column, $compare, $query['hour'], $query['minute'], $query['second'] ); + + if ( ! empty( $time_query ) ) { + $where_parts[] = $time_query; + } + } + + /* + * Return an array of 'join' and 'where' for compatibility + * with other query classes. + */ + return array( + 'where' => $where_parts, + 'join' => array(), + ); + } + + /** + * Builds and validates a value string based on the comparison operator. + * + * @since 1.0.0 + * + * @param string $compare The compare operator to use + * @param string|array $value The value + * + * @return string|false|int The value to be used in SQL or false on error. + */ + public function build_numeric_value( $compare = '=', $value = null ) { + + // Bail if null value + if ( is_null( $value ) ) { + return false; + } + + switch ( $compare ) { + case 'IN': + case 'NOT IN': + $value = (array) $value; + + // Remove non-numeric values. + $value = array_filter( $value, 'is_numeric' ); + + if ( empty( $value ) ) { + return false; + } + + return '(' . implode( ',', array_map( 'intval', $value ) ) . ')'; + + case 'BETWEEN': + case 'NOT BETWEEN': + if ( ! is_array( $value ) || 2 != count( $value ) ) { + $value = array( $value, $value ); + } else { + $value = array_values( $value ); + } + + // If either value is non-numeric, bail. + foreach ( $value as $v ) { + if ( ! is_numeric( $v ) ) { + return false; + } + } + + $value = array_map( 'intval', $value ); + + return $value[0] . ' AND ' . $value[1]; + + default: + if ( ! is_numeric( $value ) ) { + return false; + } + + return (int) $value; + } + } + + /** + * Builds and validates a value string based on the comparison operator. + * + * @since 1.0.0 + * + * @param string $compare The compare operator to use + * @param string|array $value The value + * + * @return string|false|int The value to be used in SQL or false on error. + */ + public function build_value( $compare = '=', $value = null ) { + + if ( in_array( $compare, $this->multi_value_keys, true ) ) { + if ( ! is_array( $value ) ) { + $value = preg_split( '/[,\s]+/', $value ); + } + } else { + $value = trim( $value ); + } + + switch ( $compare ) { + case 'IN': + case 'NOT IN': + $compare_string = '(' . substr( str_repeat( ',%s', count( $value ) ), 1 ) . ')'; + $where = $this->get_db()->prepare( $compare_string, $value ); + break; + + case 'BETWEEN': + case 'NOT BETWEEN': + $value = array_slice( $value, 0, 2 ); + $where = $this->get_db()->prepare( '%s AND %s', $value ); + break; + + case 'LIKE': + case 'NOT LIKE': + $value = '%' . $this->get_db()->esc_like( $value ) . '%'; + $where = $this->get_db()->prepare( '%s', $value ); + break; + + // EXISTS with a value is interpreted as '='. + case 'EXISTS': + $compare = '='; + $where = $this->get_db()->prepare( '%s', $value ); + break; + + // 'value' is ignored for NOT EXISTS. + case 'NOT EXISTS': + $where = ''; + break; + + default: + $where = $this->get_db()->prepare( '%s', $value ); + break; + } + + return $where; + } + + /** + * Builds a MySQL format date/time based on some query parameters. + * + * You can pass an array of values (year, month, etc.) with missing parameter values being defaulted to + * either the maximum or minimum values (controlled by the $default_to parameter). Alternatively you can + * pass a string that will be run through strtotime(). + * + * @since 1.0.0 + * + * @param string|array $datetime An array of parameters or a strtotime() string + * @param bool $default_to_max Whether to round up incomplete dates. Supported by values + * of $datetime that are arrays, or string values that are a + * subset of MySQL date format ('Y', 'Y-m', 'Y-m-d', 'Y-m-d H:i'). + * Default: false. + * + * @return string|false A MySQL format date/time or false on failure + */ + public function build_mysql_datetime( $datetime = '', $default_to_max = false ) { + + // Get current time + $now = time(); + + // Datetime is string + if ( is_string( $datetime ) ) { + + // Define matches so linters don't complain + $matches = array(); + + /* + * Try to parse some common date formats, so we can detect + * the level of precision and support the 'inclusive' parameter. + */ + + // Y + if ( preg_match( '/^(\d{4})$/', $datetime, $matches ) ) { + $datetime = array( + 'year' => intval( $matches[1] ), + ); + + // Y-m + } elseif ( preg_match( '/^(\d{4})\-(\d{2})$/', $datetime, $matches ) ) { + $datetime = array( + 'year' => intval( $matches[1] ), + 'month' => intval( $matches[2] ), + ); + + // Y-m-d + } elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2})$/', $datetime, $matches ) ) { + $datetime = array( + 'year' => intval( $matches[1] ), + 'month' => intval( $matches[2] ), + 'day' => intval( $matches[3] ), + ); + + // Y-m-d H:i + } elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2}) (\d{2}):(\d{2})$/', $datetime, $matches ) ) { + $datetime = array( + 'year' => intval( $matches[1] ), + 'month' => intval( $matches[2] ), + 'day' => intval( $matches[3] ), + 'hour' => intval( $matches[4] ), + 'minute' => intval( $matches[5] ), + ); + } + + // If no match is found, we don't support default_to_max. + if ( ! is_array( $datetime ) ) { + return gmdate( 'Y-m-d H:i:s', strtotime( $datetime, $now ) ); + } + } + + // Map to ints + $datetime = array_map( 'absint', $datetime ); + + // Year + if ( ! isset( $datetime['year'] ) ) { + $datetime['year'] = date( 'Y', $now ); + } + + // Month + if ( ! isset( $datetime['month'] ) ) { + $datetime['month'] = ! empty( $default_to_max ) + ? 12 + : 1; + } + + // Day + if ( ! isset( $datetime['day'] ) ) { + $datetime['day'] = ! empty( $default_to_max ) + ? (int) date( 't', mktime( 0, 0, 0, $datetime['month'], 1, $datetime['year'] ) ) + : 1; + } + + // Hour + if ( ! isset( $datetime['hour'] ) ) { + $datetime['hour'] = ! empty( $default_to_max ) + ? 23 + : 0; + } + + // Minute + if ( ! isset( $datetime['minute'] ) ) { + $datetime['minute'] = ! empty( $default_to_max ) + ? 59 + : 0; + } + + // Second + if ( ! isset( $datetime['second'] ) ) { + $datetime['second'] = ! empty( $default_to_max ) + ? 59 + : 0; + } + + // Combine and return + return sprintf( + '%04d-%02d-%02d %02d:%02d:%02d', + $datetime['year'], + $datetime['month'], + $datetime['day'], + $datetime['hour'], + $datetime['minute'], + $datetime['second'] + ); + } + + /** + * Return a MySQL expression for selecting the week number based on the + * day that the week starts. + * + * Uses the WordPress site option, if set. + * + * @since 1.0.0 + * + * @param string $column Database column. + * @param int $start_of_week Day that week starts on. 0 = Sunday. + * + * @return string SQL clause. + */ + public function build_mysql_week( $column = '', $start_of_week = 0 ) { + + // Start of week option + $start_of_week = (int) get_option( 'start_of_week', $start_of_week ); + + // When does the week start? + switch ( $start_of_week ) { + + // Monday + case 1: + $retval = "WEEK( {$column}, 1 )"; + break; + + // Tuesday - Saturday + case 2: + case 3: + case 4: + case 5: + case 6: + $retval = "WEEK( DATE_SUB( {$column}, INTERVAL {$start_of_week} DAY ), 0 )"; + break; + + // Sunday + case 0: + default: + $retval = "WEEK( {$column}, 0 )"; + break; + } + + // Return SQL + return $retval; + } + + /** + * Builds a query string for comparing time values (hour, minute, second). + * + * If just hour, minute, or second is set than a normal comparison will be done. + * However if multiple values are passed, a pseudo-decimal time will be created + * in order to be able to accurately compare against. + * + * @since 1.0.0 + * + * @param string $column The column to query against. Needs to be pre-validated! + * @param string $compare The comparison operator. Needs to be pre-validated! + * @param int|null $hour Optional. An hour value (0-23). + * @param int|null $minute Optional. A minute value (0-59). + * @param int|null $second Optional. A second value (0-59). + * + * @return string|false A query part or false on failure. + */ + public function build_time_query( $column, $compare, $hour = null, $minute = null, $second = null ) { + + // Have to have at least one + if ( ! isset( $hour ) && ! isset( $minute ) && ! isset( $second ) ) { + return false; + } + + // Complex combined queries aren't supported for multi-value queries + if ( in_array( $compare, $this->multi_value_keys, true ) ) { + $retval = array(); + + // Hour + if ( isset( $hour ) && false !== ( $value = $this->build_numeric_value( $compare, $hour ) ) ) { + $retval[] = "HOUR( {$column} ) {$compare} {$value}"; + } + + // Minute + if ( isset( $minute ) && false !== ( $value = $this->build_numeric_value( $compare, $minute ) ) ) { + $retval[] = "MINUTE( {$column} ) {$compare} {$value}"; + } + + // Second + if ( isset( $second ) && false !== ( $value = $this->build_numeric_value( $compare, $second ) ) ) { + $retval[] = "SECOND( {$column} ) {$compare} {$value}"; + } + + return implode( ' AND ', $retval ); + } + + // Cases where just one unit is set + + // Hour + if ( isset( $hour ) && ! isset( $minute ) && ! isset( $second ) && false !== ( $value = $this->build_numeric_value( $compare, $hour ) ) ) { + return "HOUR( {$column} ) {$compare} {$value}"; + + // Minute + } elseif ( ! isset( $hour ) && isset( $minute ) && ! isset( $second ) && false !== ( $value = $this->build_numeric_value( $compare, $minute ) ) ) { + return "MINUTE( {$column} ) {$compare} {$value}"; + + // Second + } elseif ( ! isset( $hour ) && ! isset( $minute ) && isset( $second ) && false !== ( $value = $this->build_numeric_value( $compare, $second ) ) ) { + return "SECOND( {$column} ) {$compare} {$value}"; + } + + // Single units were already handled. Since hour & second isn't allowed, + // minute must to be set. + if ( ! isset( $minute ) ) { + return false; + } + + // Defaults + $format = $time = ''; + + // Hour + if ( null !== $hour ) { + $format .= '%H.'; + $time .= sprintf( '%02d', $hour ) . '.'; + } else { + $format .= '0.'; + $time .= '0.'; + } + + // Minute + $format .= '%i'; + $time .= sprintf( '%02d', $minute ); + + // Second + if ( isset( $second ) ) { + $format .= '%s'; + $time .= sprintf( '%02d', $second ); + } + + // Build the SQL + $query = "DATE_FORMAT( {$column}, %s ) {$compare} %f"; + + // Return the prepared SQL + return $this->get_db()->prepare( $query, $format, $time ); + } + + /** + * Test if the supplied date is valid for the Gregorian calendar. + * + * @since 1.0.0 + * + * @link https://www.php.net/manual/en/function.checkdate.php + * + * @param int $month Month number. + * @param int $day Day number. + * @param int $year Year number. + * @param string $source_date The date to filter. + * + * @return bool True if valid date, false if not valid date. + */ + public function checkdate( $month = 0, $day = 0, $year = 0, $source_date = '' ) { + + // Check the date + $retval = checkdate( $month, $day, $year ); + + /** + * Filters whether the given date is valid for the Gregorian calendar. + * + * @since 1.0.0 + * + * @param bool $checkdate Whether the given date is valid. + * @param string $source_date Date to check. + */ + return (bool) apply_filters( 'wp_checkdate', $retval, $source_date ); + } +} diff --git a/includes/database/engine/class-meta.php b/includes/database/engine/class-meta.php new file mode 100644 index 00000000000..ae9c4165bc6 --- /dev/null +++ b/includes/database/engine/class-meta.php @@ -0,0 +1,29 @@ + '', + 'from' => '', + 'where' => array(), + 'groupby' => '', + 'orderby' => '', + 'limits' => '' + ); + + /** + * Request clauses. + * + * @since 1.0.0 + * @var array + */ + protected $request_clauses = array( + 'select' => '', + 'from' => '', + 'where' => '', + 'groupby' => '', + 'orderby' => '', + 'limits' => '' + ); + + /** + * Meta query container. + * + * @since 1.0.0 + * @var object|Queries\Meta + */ + protected $meta_query = false; + + /** + * Date query container. + * + * @since 1.0.0 + * @var object|Queries\Date + */ + protected $date_query = false; + + /** + * Compare query container. + * + * @since 1.0.0 + * @var object|Queries\Compare + */ + protected $compare_query = false; + + /** Query Variables *******************************************************/ + + /** + * Parsed query vars set by the application, possibly filtered and changed. + * + * This is specifically marked as public, to allow byref actions to change + * them from outside the class methods proper and inside filter functions. + * + * @since 1.0.0 + * @var array + */ + public $query_vars = array(); + + /** + * Original query vars set by the application. + * + * These are the original query variables before any filters are applied, + * and are the results of merging $query_var_defaults with $query_vars. + * + * @since 1.0.0 + * @var array + */ + protected $query_var_originals = array(); + + /** + * Default values for query vars. + * + * These are computed at runtime based on the registered columns for the + * database table this query relates to. + * + * @since 1.0.0 + * @var array + */ + protected $query_var_defaults = array(); + + /** + * This private variable temporarily holds onto a random string used as the + * default query var value. This is used internally when performing + * comparisons, and allows for querying by falsy values. + * + * @since 1.1.0 + * @var string + */ + protected $query_var_default_value = ''; + + /** Results ***************************************************************/ + + /** + * List of items located by the query. + * + * @since 1.0.0 + * @var array + */ + public $items = array(); + + /** + * The amount of found items for the current query. + * + * @since 1.0.0 + * @var int + */ + protected $found_items = 0; + + /** + * The number of pages. + * + * @since 1.0.0 + * @var int + */ + protected $max_num_pages = 0; + + /** + * SQL for database query. + * + * @since 1.0.0 + * @var string + */ + protected $request = ''; + + /** Methods ***************************************************************/ + + /** + * Sets up the item query, based on the query vars passed. + * + * @since 1.0.0 + * + * @param string|array $query { + * Optional. Array or query string of item query parameters. + * Default empty. + * + * @type string $fields Site fields to return. Accepts 'ids' (returns an array of item IDs) + * or empty (returns an array of complete item objects). Default empty. + * To do a date query against a field, append the field name with _query + * @type bool $count Whether to return a item count (true) or array of item objects. + * Default false. + * @type int $number Limit number of items to retrieve. Use 0 for no limit. + * Default 100. + * @type int $offset Number of items to offset the query. Used to build LIMIT clause. + * Default 0. + * @type bool $no_found_rows Whether to disable the `SQL_CALC_FOUND_ROWS` query. + * Default true. + * @type string|array $orderby Accepts false, an empty array, or 'none' to disable `ORDER BY` clause. + * Default 'id'. + * @type string $item How to item retrieved items. Accepts 'ASC', 'DESC'. + * Default 'DESC'. + * @type string $search Search term(s) to retrieve matching items for. + * Default empty. + * @type array $search_columns Array of column names to be searched. + * Default empty array. + * @type bool $update_item_cache Whether to prime the cache for found items. + * Default false. + * @type bool $update_meta_cache Whether to prime the meta cache for found items. + * Default false. + * } + */ + public function __construct( $query = array() ) { + + // Setup + $this->set_alias(); + $this->set_prefix(); + $this->set_columns(); + $this->set_item_shape(); + $this->set_query_var_defaults(); + + // Maybe execute a query if arguments were passed + if ( ! empty( $query ) ) { + $this->query( $query ); + } + } + + /** + * Queries the database and retrieves items or counts. + * + * This method is public to allow subclasses to perform JIT manipulation + * of the parameters passed into it. + * + * @since 1.0.0 + * + * @param string|array $query Array or URL query string of parameters. + * @return array|int List of items, or number of items when 'count' is passed as a query var. + */ + public function query( $query = array() ) { + $this->parse_query( $query ); + + return $this->get_items(); + } + + /** Private Setters *******************************************************/ + + /** + * Set the time when items were last changed. + * + * We set this locally to avoid inconsistencies between method calls. + * + * @since 1.0.0 + */ + protected function set_last_changed() { + $this->last_changed = microtime(); + } + + /** + * Set up the table alias if not already set in the class. + * + * This happens before prefixes are applied. + * + * @since 1.0.0 + */ + protected function set_alias() { + if ( empty( $this->table_alias ) ) { + $this->table_alias = $this->first_letters( $this->table_name ); + } + } + + /** + * Prefix table names, cache groups, and other things. + * + * This is to avoid conflicts with other plugins or themes that might be + * doing their own things. + * + * @since 1.0.0 + */ + protected function set_prefix() { + $this->table_name = $this->apply_prefix( $this->table_name ); + $this->table_alias = $this->apply_prefix( $this->table_alias ); + $this->cache_group = $this->apply_prefix( $this->cache_group, '-' ); + } + + /** + * Set columns objects + * + * @since 1.0.0 + */ + protected function set_columns() { + + // Bail if no table schema + if ( ! class_exists( $this->table_schema ) ) { + return; + } + + // Invoke a new table schema class + $schema = new $this->table_schema; + + // Maybe get the column objects + if ( ! empty( $schema->columns ) ) { + $this->columns = $schema->columns; + } + } + + /** + * Set the default item shape if none exists + * + * @since 1.0.0 + */ + protected function set_item_shape() { + if ( empty( $this->item_shape ) || ! class_exists( $this->item_shape ) ) { + $this->item_shape = __NAMESPACE__ . '\\Row'; + } + } + + /** + * Set default query vars based on columns + * + * @since 1.0.0 + */ + protected function set_query_var_defaults() { + + // Default query variable value + $this->query_var_default_value = function_exists( 'random_bytes' ) + ? $this->apply_prefix( bin2hex( random_bytes( 18 ) ) ) + : $this->apply_prefix( uniqid( '_', true ) ); + + // Default query variables + $this->query_var_defaults = array( + 'fields' => '', + 'number' => 100, + 'offset' => '', + 'orderby' => 'id', + 'order' => 'DESC', + 'groupby' => '', + 'search' => '', + 'search_columns' => array(), + 'count' => false, + 'meta_query' => null, // See Queries\Meta + 'date_query' => null, // See Queries\Date + 'compare_query' => null, // See Queries\Compare + 'no_found_rows' => true, + + // Caching + 'update_item_cache' => true, + 'update_meta_cache' => true + ); + + // Bail if no columns + if ( empty( $this->columns ) ) { + return; + } + + // Direct column names + $names = wp_list_pluck( $this->columns, 'name' ); + foreach ( $names as $name ) { + $this->query_var_defaults[ $name ] = $this->query_var_default_value; + } + + // Possible ins + $possible_ins = $this->get_columns( array( 'in' => true ), 'and', 'name' ); + foreach ( $possible_ins as $in ) { + $key = "{$in}__in"; + $this->query_var_defaults[ $key ] = false; + } + + // Possible not ins + $possible_not_ins = $this->get_columns( array( 'not_in' => true ), 'and', 'name' ); + foreach ( $possible_not_ins as $in ) { + $key = "{$in}__not_in"; + $this->query_var_defaults[ $key ] = false; + } + + // Possible dates + $possible_dates = $this->get_columns( array( 'date_query' => true ), 'and', 'name' ); + foreach ( $possible_dates as $date ) { + $key = "{$date}_query"; + $this->query_var_defaults[ $key ] = false; + } + } + + /** + * Set the request clauses + * + * @since 1.0.0 + * + * @param array $clauses + */ + protected function set_request_clauses( $clauses = array() ) { + + // Found rows + $found_rows = empty( $this->query_vars['no_found_rows'] ) + ? 'SQL_CALC_FOUND_ROWS' + : ''; + + // Fields + $fields = ! empty( $clauses['fields'] ) + ? $clauses['fields'] + : ''; + + // Join + $join = ! empty( $clauses['join'] ) + ? $clauses['join'] + : ''; + + // Where + $where = ! empty( $clauses['where'] ) + ? "WHERE {$clauses['where']}" + : ''; + + // Group by + $groupby = ! empty( $clauses['groupby'] ) + ? "GROUP BY {$clauses['groupby']}" + : ''; + + // Order by + $orderby = ! empty( $clauses['orderby'] ) + ? "ORDER BY {$clauses['orderby']}" + : ''; + + // Limits + $limits = ! empty( $clauses['limits'] ) + ? $clauses['limits'] + : ''; + + // Select & From + $table = $this->get_table_name(); + $select = "SELECT {$found_rows} {$fields}"; + $from = "FROM {$table} {$this->table_alias} {$join}"; + + // Put query into clauses array + $this->request_clauses['select'] = $select; + $this->request_clauses['from'] = $from; + $this->request_clauses['where'] = $where; + $this->request_clauses['groupby'] = $groupby; + $this->request_clauses['orderby'] = $orderby; + $this->request_clauses['limits'] = $limits; + } + + /** + * Set the request + * + * @since 1.0.0 + */ + protected function set_request() { + $filtered = array_filter( $this->request_clauses ); + $clauses = array_map( 'trim', $filtered ); + $this->request = implode( ' ', $clauses ); + } + + /** + * Set items by mapping them through the single item callback. + * + * @since 1.0.0 + * @param array $item_ids + */ + protected function set_items( $item_ids = array() ) { + + // Bail if counting, to avoid shaping items + if ( ! empty( $this->query_vars['count'] ) ) { + $this->items = $item_ids; + return; + } + + // Cast to integers + $item_ids = array_map( 'intval', $item_ids ); + + // Prime item caches + $this->prime_item_caches( $item_ids ); + + // Shape the items + $this->items = $this->shape_items( $item_ids ); + } + + /** + * Populates found_items and max_num_pages properties for the current query + * if the limit clause was used. + * + * @since 1.0.0 + * + * @param array $item_ids Optional array of item IDs + */ + protected function set_found_items( $item_ids = array() ) { + + // Items were not found + if ( empty( $item_ids ) ) { + return; + } + + // Default to number of item IDs + $this->found_items = count( (array) $item_ids ); + + // Count query + if ( ! empty( $this->query_vars['count'] ) ) { + + // Not grouped + if ( is_numeric( $item_ids ) && empty( $this->query_vars['groupby'] ) ) { + $this->found_items = intval( $item_ids ); + } + + // Not a count query + } elseif ( is_array( $item_ids ) && ( ! empty( $this->query_vars['number'] ) && empty( $this->query_vars['no_found_rows'] ) ) ) { + + /** + * Filters the query used to retrieve found item count. + * + * @since 1.0.0 + * + * @param string $found_items_query SQL query. Default 'SELECT FOUND_ROWS()'. + * @param object $item_query The object instance. + */ + $found_items_query = (string) apply_filters_ref_array( $this->apply_prefix( "found_{$this->item_name_plural}_query" ), array( 'SELECT FOUND_ROWS()', &$this ) ); + + // Maybe query for found items + if ( ! empty( $found_items_query ) ) { + $this->found_items = (int) $this->get_db()->get_var( $found_items_query ); + } + } + } + + /** Public Setters ********************************************************/ + + /** + * Set a query var, to both defaults and request arrays. + * + * This method is used to expose the private query_vars array to hooks, + * allowing them to manipulate query vars just-in-time. + * + * @since 1.0.0 + * + * @param string $key + * @param string $value + */ + public function set_query_var( $key = '', $value = '' ) { + $this->query_var_defaults[ $key ] = $value; + $this->query_vars[ $key ] = $value; + } + + /** + * Check whether a query variable strictly equals the unique default + * starting value. + * + * @since 1.1.0 + * @param string $key + * @return bool + */ + public function is_query_var_default( $key = '' ) { + return (bool) ( $this->query_vars[ $key ] === $this->query_var_default_value ); + } + + /** Private Getters *******************************************************/ + + /** + * Pass-through method to return a new Meta object. + * + * @since 1.0.0 + * + * @param array $args See Queries\Meta + * + * @return Queries\Meta + */ + private function get_meta_query( $args = array() ) { + return new Queries\Meta( $args ); + } + + /** + * Pass-through method to return a new Compare object. + * + * @since 1.0.0 + * + * @param array $args See Queries\Compare + * + * @return Queries\Compare + */ + private function get_compare_query( $args = array() ) { + return new Queries\Compare( $args ); + } + + /** + * Pass-through method to return a new Queries\Date object. + * + * @since 1.0.0 + * + * @param array $args See Queries\Date + * + * @return Queries\Date + */ + private function get_date_query( $args = array() ) { + return new Queries\Date( $args ); + } + + /** + * Return the current time as a UTC timestamp + * + * This is used by add_item() and update_item() + * + * @since 1.0.0 + * + * @return string + */ + protected function get_current_time() { + return gmdate( "Y-m-d\TH:i:s\Z" ); + } + + /** + * Return the literal table name (with prefix) from the database interface. + * + * @since 1.0.0 + * + * @return string + */ + protected function get_table_name() { + return $this->get_db()->{$this->table_name}; + } + + /** + * Return array of column names + * + * @since 1.0.0 + * + * @return array + */ + protected function get_column_names() { + return array_flip( $this->get_columns( array(), 'and', 'name' ) ); + } + + /** + * Return the primary database column name + * + * @since 1.0.0 + * + * @return string Default "id", Primary column name if not empty + */ + protected function get_primary_column_name() { + return $this->get_column_field( array( 'primary' => true ), 'name', 'id' ); + } + + /** + * Get a column from an array of arguments + * + * @since 1.0.0 + * + * @return mixed Column object, or false + */ + protected function get_column_field( $args = array(), $field = '', $default = false ) { + + // Get column + $column = $this->get_column_by( $args ); + + // Return field, or default + return isset( $column->{$field} ) + ? $column->{$field} + : $default; + } + + /** + * Get a column from an array of arguments + * + * @since 1.0.0 + * + * @return mixed Column object, or false + */ + protected function get_column_by( $args = array() ) { + + // Filter columns + $filter = $this->get_columns( $args ); + + // Return column or false + return ! empty( $filter ) + ? reset( $filter ) + : false; + } + + /** + * Get columns from an array of arguments + * + * @since 1.0.0 + */ + protected function get_columns( $args = array(), $operator = 'and', $field = false ) { + + // Filter columns + $filter = wp_filter_object_list( $this->columns, $args, $operator, $field ); + + // Return column or false + return ! empty( $filter ) + ? array_values( $filter ) + : array(); + } + + /** + * Get a single database row by any column and value, skipping cache. + * + * @since 1.0.0 + * + * @param string $column_name Name of database column + * @param string $column_value Value to query for + * @return object|false False if empty/error, Object if successful + */ + protected function get_item_raw( $column_name = '', $column_value = '' ) { + + // Bail if no name or value + if ( empty( $column_name ) || empty( $column_value ) ) { + return false; + } + + // Bail if values aren't query'able + if ( ! is_string( $column_name ) || ! is_scalar( $column_value ) ) { + return false; + } + + // Query database for row + $pattern = $this->get_column_field( array( 'name' => $column_name ), 'pattern', '%s' ); + $table = $this->get_table_name(); + $select = $this->get_db()->prepare( "SELECT * FROM {$table} WHERE {$column_name} = {$pattern}", $column_value ); + $result = $this->get_db()->get_row( $select ); + + // Bail on failure + if ( ! $this->is_success( $result ) ) { + return false; + } + + // Return row + return $result; + } + + /** + * Retrieves a list of items matching the query vars. + * + * @since 1.0.0 + * + * @return array|int List of items, or number of items when 'count' is passed as a query var. + */ + protected function get_items() { + + /** + * Fires before object items are retrieved. + * + * @since 1.0.0 + * + * @param Query &$this Current instance of Query, passed by reference. + */ + do_action_ref_array( $this->apply_prefix( "pre_get_{$this->item_name_plural}" ), array( &$this ) ); + + // Never limit, never update item/meta caches when counting + if ( ! empty( $this->query_vars['count'] ) ) { + $this->query_vars['number'] = false; + $this->query_vars['no_found_rows'] = true; + $this->query_vars['update_item_cache'] = false; + $this->query_vars['update_meta_cache'] = false; + } + + // Check the cache + $cache_key = $this->get_cache_key(); + $cache_value = $this->cache_get( $cache_key, $this->cache_group ); + + // No cache value + if ( false === $cache_value ) { + $item_ids = $this->get_item_ids(); + + // Set the number of found items + $this->set_found_items( $item_ids ); + + // Format the cached value + $cache_value = array( + 'item_ids' => $item_ids, + 'found_items' => intval( $this->found_items ), + ); + + // Add value to the cache + $this->cache_add( $cache_key, $cache_value, $this->cache_group ); + + // Value exists in cache + } else { + $item_ids = $cache_value['item_ids']; + $this->found_items = intval( $cache_value['found_items'] ); + } + + // Pagination + if ( ! empty( $this->found_items ) && ! empty( $this->query_vars['number'] ) ) { + $this->max_num_pages = ceil( $this->found_items / $this->query_vars['number'] ); + } + + // Cast to int if not grouping counts + if ( ! empty( $this->query_vars['count'] ) && empty( $this->query_vars['groupby'] ) ) { + $item_ids = intval( $item_ids ); + } + + // Set items from IDs + $this->set_items( $item_ids ); + + // Return array of items + return $this->items; + } + + /** + * Used internally to get a list of item IDs matching the query vars. + * + * @since 1.0.0 + * + * @return int|array A single count of item IDs if a count query. An array of item IDs if a full query. + */ + protected function get_item_ids() { + + // Setup primary column, and parse the where clause + $this->parse_where(); + + // Order & Order By + $order = $this->parse_order( $this->query_vars['order'] ); + $orderby = $this->get_order_by( $order ); + + // Limit & Offset + $limit = absint( $this->query_vars['number'] ); + $offset = absint( $this->query_vars['offset'] ); + + // Limits + if ( ! empty( $limit ) ) { + $limits = ! empty( $offset ) + ? "LIMIT {$offset}, {$limit}" + : "LIMIT {$limit}"; + } else { + $limits = ''; + } + + // Where & Join + $where = implode( ' AND ', $this->query_clauses['where'] ); + $join = implode( ', ', $this->query_clauses['join'] ); + + // Group by + $groupby = $this->parse_groupby( $this->query_vars['groupby'] ); + + // Fields + $fields = $this->parse_fields( $this->query_vars['fields'] ); + + // Setup the query array (compact() is too opaque here) + $query = array( + 'fields' => $fields, + 'join' => $join, + 'where' => $where, + 'orderby' => $orderby, + 'limits' => $limits, + 'groupby' => $groupby + ); + + /** + * Filters the item query clauses. + * + * @since 1.0.0 + * + * @param array $pieces A compacted array of item query clauses. + * @param Query &$this Current instance passed by reference. + */ + $clauses = (array) apply_filters_ref_array( $this->apply_prefix( "{$this->item_name_plural}_query_clauses" ), array( $query, &$this ) ); + + // Setup request + $this->set_request_clauses( $clauses ); + $this->set_request(); + + // Return count + if ( ! empty( $this->query_vars['count'] ) ) { + + // Get vars or results + $retval = empty( $this->query_vars['groupby'] ) + ? $this->get_db()->get_var( $this->request ) + : $this->get_db()->get_results( $this->request, ARRAY_A ); + + // Return vars or results + return $retval; + } + + // Get IDs + $item_ids = $this->get_db()->get_col( $this->request ); + + // Return parsed IDs + return wp_parse_id_list( $item_ids ); + } + + /** + * Get the ORDERBY clause. + * + * @since 1.0.0 + * + * @param string $order + * @return string + */ + protected function get_order_by( $order = '' ) { + + // Default orderby primary column + $orderby = "{$this->parse_orderby()} {$order}"; + + // Disable ORDER BY if counting, or: 'none', an empty array, or false. + if ( ! empty( $this->query_vars['count'] ) || in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) { + $orderby = ''; + + // Ordering by something, so figure it out + } elseif ( ! empty( $this->query_vars['orderby'] ) ) { + + // Array of keys, or comma separated + $ordersby = is_array( $this->query_vars['orderby'] ) + ? $this->query_vars['orderby'] + : preg_split( '/[,\s]/', $this->query_vars['orderby'] ); + + $orderby_array = array(); + $possible_ins = $this->get_columns( array( 'in' => true ), 'and', 'name' ); + $sortables = $this->get_columns( array( 'sortable' => true ), 'and', 'name' ); + + // Loop through possible order by's + foreach ( $ordersby as $_key => $_value ) { + + // Skip if empty + if ( empty( $_value ) ) { + continue; + } + + // Key is numeric + if ( is_int( $_key ) ) { + $_orderby = $_value; + $_item = $order; + + // Key is string + } else { + $_orderby = $_key; + $_item = $_value; + } + + // Skip if not sortable + if ( ! in_array( $_value, $sortables, true ) ) { + continue; + } + + // Parse orderby + $parsed = $this->parse_orderby( $_orderby ); + + // Skip if empty + if ( empty( $parsed ) ) { + continue; + } + + // Set if __in + if ( in_array( $_orderby, $possible_ins, true ) ) { + $orderby_array[] = "{$parsed} {$order}"; + continue; + } + + // Append parsed orderby to array + $orderby_array[] = $parsed . ' ' . $this->parse_order( $_item ); + } + + // Only set if valid orderby + if ( ! empty( $orderby_array ) ) { + $orderby = implode( ', ', $orderby_array ); + } + } + + // Return parsed orderby + return $orderby; + } + + /** + * Used internally to generate an SQL string for searching across multiple columns. + * + * @since 1.0.0 + * + * @param string $string Search string. + * @param array $columns Columns to search. + * @return string Search SQL. + */ + protected function get_search_sql( $string = '', $columns = array() ) { + + // Array or String + $like = ( false !== strpos( $string, '*' ) ) + ? '%' . implode( '%', array_map( array( $this->get_db(), 'esc_like' ), explode( '*', $string ) ) ) . '%' + : '%' . $this->get_db()->esc_like( $string ) . '%'; + + // Default array + $searches = array(); + + // Build search SQL + foreach ( $columns as $column ) { + $searches[] = $this->get_db()->prepare( "{$column} LIKE %s", $like ); + } + + // Return the clause + return '(' . implode( ' OR ', $searches ) . ')'; + } + + /** Private Parsers *******************************************************/ + + /** + * Parses arguments passed to the item query with default query parameters. + * + * @since 1.0.0 + * + * @see Query::__construct() + * + * @param string|array $query Array or string of Query arguments. + */ + private function parse_query( $query = array() ) { + + // Setup the query_vars_original var + $this->query_var_originals = wp_parse_args( $query ); + + // Setup the query_vars parsed var + $this->query_vars = wp_parse_args( + $this->query_var_originals, + $this->query_var_defaults + ); + + /** + * Fires after the item query vars have been parsed. + * + * @since 1.0.0 + * + * @param Query &$this The Query instance (passed by reference). + */ + do_action_ref_array( $this->apply_prefix( "parse_{$this->item_name_plural}_query" ), array( &$this ) ); + } + + /** + * Parse the where clauses for all known columns + * + * @since 1.0.0 + */ + private function parse_where() { + + // Defaults + $where = $join = $searchable = $date_query = array(); + + // Loop through columns + foreach ( $this->columns as $column ) { + + // Maybe add name to searchable array + if ( true === $column->searchable ) { + $searchable[] = $column->name; + } + + // Literal column comparison + if ( ! $this->is_query_var_default( $column->name ) ) { + + // Array (unprepared) + if ( is_array( $this->query_vars[ $column->name ] ) ) { + $where_id = "'" . implode( "', '", $this->get_db()->_escape( $this->query_vars[ $column->name ] ) ) . "'"; + $statement = "{$this->table_alias}.{$column->name} IN ({$where_id})"; + + // Add to where array + $where[ $column->name ] = $statement; + + // Numeric/String/Float (prepared) + } else { + $pattern = $this->get_column_field( array( 'name' => $column->name ), 'pattern', '%s' ); + $where_id = $this->query_vars[ $column->name ]; + $statement = "{$this->table_alias}.{$column->name} = {$pattern}"; + + // Add to where array + $where[ $column->name ] = $this->get_db()->prepare( $statement, $where_id ); + } + } + + // __in + if ( true === $column->in ) { + $where_id = "{$column->name}__in"; + + // Parse item for an IN clause. + if ( isset( $this->query_vars[ $where_id ] ) && is_array( $this->query_vars[ $where_id ] ) ) { + + // Convert single item arrays to literal column comparisons + if ( 1 === count( $this->query_vars[ $where_id ] ) ) { + $column_value = reset( $this->query_vars[ $where_id ] ); + $statement = "{$this->table_alias}.{$column->name} = %s"; + + $where[ $column->name ] = $this->get_db()->prepare( $statement, $column_value ); + + // Implode + } else { + $where[ $where_id ] = "{$this->table_alias}.{$column->name} IN ( '" . implode( "', '", $this->get_db()->_escape( $this->query_vars[ $where_id ] ) ) . "' )"; + } + } + } + + // __not_in + if ( true === $column->not_in ) { + $where_id = "{$column->name}__not_in"; + + // Parse item for a NOT IN clause. + if ( isset( $this->query_vars[ $where_id ] ) && is_array( $this->query_vars[ $where_id ] ) ) { + + // Convert single item arrays to literal column comparisons + if ( 1 === count( $this->query_vars[ $where_id ] ) ) { + $column_value = reset( $this->query_vars[ $where_id ] ); + $statement = "{$this->table_alias}.{$column->name} != %s"; + + $where[ $column->name ] = $this->get_db()->prepare( $statement, $column_value ); + + // Implode + } else { + $where[ $where_id ] = "{$this->table_alias}.{$column->name} NOT IN ( '" . implode( "', '", $this->get_db()->_escape( $this->query_vars[ $where_id ] ) ) . "' )"; + } + } + } + + // date_query + if ( true === $column->date_query ) { + $where_id = "{$column->name}_query"; + $column_date = $this->query_vars[ $where_id ]; + + // Parse item + if ( ! empty( $column_date ) ) { + + // Default arguments + $defaults = array( + 'column' => "{$this->table_alias}.{$column->name}", + 'before' => $column_date, + 'inclusive' => true + ); + + // Default date query + if ( is_string( $column_date ) ) { + $date_query[] = $defaults; + + // Array query var + } elseif ( is_array( $column_date ) ) { + + // Auto-fill column if empty + if ( empty( $column_date['column'] ) ) { + $column_date['column'] = $defaults['column']; + } + + // Add clause to date query + $date_query[] = $column_date; + } + } + } + } + + // Maybe search if columns are searchable. + if ( ! empty( $searchable ) && strlen( $this->query_vars['search'] ) ) { + $search_columns = array(); + + // Intersect against known searchable columns + if ( ! empty( $this->query_vars['search_columns'] ) ) { + $search_columns = array_intersect( + $this->query_vars['search_columns'], + $searchable + ); + } + + // Default to all searchable columns + if ( empty( $search_columns ) ) { + $search_columns = $searchable; + } + + /** + * Filters the columns to search in a Query search. + * + * @since 1.0.0 + * + * @param array $search_columns Array of column names to be searched. + * @param string $search Text being searched. + * @param object $this The current Query instance. + */ + $search_columns = (array) apply_filters( $this->apply_prefix( "{$this->item_name_plural}_search_columns" ), $search_columns, $this->query_vars['search'], $this ); + + // Add search query clause + $where['search'] = $this->get_search_sql( $this->query_vars['search'], $search_columns ); + } + + // Get the primary column & table + $primary = $this->get_primary_column_name(); + $table = $this->get_meta_type(); + $and = '/^\s*AND\s*/'; + + // Maybe perform a meta query. + $meta_query = $this->query_vars['meta_query']; + if ( ! empty( $meta_query ) && is_array( $meta_query ) ) { + $this->meta_query = $this->get_meta_query( $meta_query ); + $clauses = $this->meta_query->get_sql( $table, $this->table_alias, $primary, $this ); + + // Not all objects have meta, so make sure this one exists + if ( false !== $clauses ) { + + // Set join + if ( ! empty( $clauses['join'] ) ) { + $join['meta_query'] = $clauses['join']; + } + + // Remove " AND " from meta_query query where clause + if ( ! empty( $clauses['where'] ) ) { + $where['meta_query'] = preg_replace( $and, '', $clauses['where'] ); + } + } + } + + // Maybe perform a compare query. + $compare_query = $this->query_vars['compare_query']; + if ( ! empty( $compare_query ) && is_array( $compare_query ) ) { + $this->compare_query = $this->get_compare_query( $compare_query ); + $clauses = $this->compare_query->get_sql( $table, $this->table_alias, $primary, $this ); + + // Not all objects can compare, so make sure this one exists + if ( false !== $clauses ) { + + // Set join + if ( ! empty( $clauses['join'] ) ) { + $join['compare_query'] = $clauses['join']; + } + + // Remove " AND " from query where clause. + $where['compare_query'] = preg_replace( $and, '', $clauses['where'] ); + } + } + + // Only do a date query with an array + $date_query = ! empty( $date_query ) + ? $date_query + : $this->query_vars['date_query']; + + // Maybe perform a date query + if ( ! empty( $date_query ) && is_array( $date_query ) ) { + $this->date_query = $this->get_date_query( $date_query ); + $clauses = $this->date_query->get_sql( $this->table_name, $this->table_alias, $primary, $this ); + + // Not all objects are dates, so make sure this one exists + if ( false !== $clauses ) { + + // Set join + if ( ! empty( $clauses['join'] ) ) { + $join['date_query'] = $clauses['join']; + } + + // Remove " AND " from query where clause. + $where['date_query'] = preg_replace( $and, '', $clauses['where'] ); + } + } + + // Set where and join clauses + $this->query_clauses['where'] = $where; + $this->query_clauses['join'] = $join; + } + + /** + * Parse which fields to query for + * + * @since 1.0.0 + * + * @param string $fields + * @param bool $alias + * @return string + */ + private function parse_fields( $fields = '', $alias = true ) { + + // Default return value + $primary = $this->get_primary_column_name(); + $retval = ( true === $alias ) + ? "{$this->table_alias}.{$primary}" + : $primary; + + // No fields + if ( empty( $fields ) && ! empty( $this->query_vars['count'] ) ) { + + // Possible fields to group by + $groupby_names = $this->parse_groupby( $this->query_vars['groupby'], $alias ); + $groupby_names = ! empty( $groupby_names ) + ? "{$groupby_names}" + : ''; + + // Group by or total count + $retval = ! empty( $groupby_names ) + ? "{$groupby_names}, COUNT(*) as count" + : 'COUNT(*)'; + } + + // Return fields (or COUNT) + return $retval; + } + + /** + * Parses and sanitizes the 'groupby' keys passed into the item query + * + * @since 1.0.0 + * + * @param string $groupby + * @param bool $alias + * @return string + */ + private function parse_groupby( $groupby = '', $alias = true ) { + + // Bail if empty + if ( empty( $groupby ) ) { + return ''; + } + + // Sanitize groupby columns + $groupby = (array) array_map( 'sanitize_key', (array) $groupby ); + + // Re'flip column names back around + $columns = array_flip( $this->get_column_names() ); + + // Get the intersection of allowed column names to groupby columns + $intersect = array_intersect( $columns, $groupby ); + + // Bail if invalid column + if ( empty( $intersect ) ) { + return ''; + } + + // Default return value + $retval = array(); + + // Maybe prepend table alias to key + foreach ( $intersect as $key ) { + $retval[] = ( true === $alias ) + ? "{$this->table_alias}.{$key}" + : $key; + } + + // Separate sanitized columns + return implode( ',', array_values( $retval ) ); + } + + /** + * Parses and sanitizes 'orderby' keys passed to the item query. + * + * @since 1.0.0 + * + * @param string $orderby Field for the items to be ordered by. + * @return string|false Value to used in the ORDER clause. False otherwise. + */ + private function parse_orderby( $orderby = 'id' ) { + + // Default value + $parsed = "{$this->table_alias}.{$this->get_primary_column_name()}"; + + // __in + if ( false !== strstr( $orderby, '__in' ) ) { + $column_name = str_replace( '__in', '', $orderby ); + $column = $this->get_column_by( array( 'name' => $column_name ) ); + $item_in = $column->is_numeric() + ? implode( ',', array_map( 'absint', $this->query_vars[ $orderby ] ) ) + : implode( ',', $this->query_vars[ $orderby ] ); + + $parsed = "FIELD( {$this->table_alias}.{$column->name}, {$item_in} )"; + + // Specific column + } else { + + // Orderby is a literal, sortable column name + $sortables = $this->get_columns( array( 'sortable' => true ), 'and', 'name' ); + if ( in_array( $orderby, $sortables, true ) ) { + $parsed = "{$this->table_alias}.{$orderby}"; + } + } + + // Return parsed value + return $parsed; + } + + /** + * Parses an 'order' query variable and cast it to 'ASC' or 'DESC' as necessary. + * + * @since 1.0.0 + * + * @param string $order The 'order' query variable. + * @return string The sanitized 'order' query variable. + */ + private function parse_order( $order = '' ) { + + // Bail if malformed + if ( empty( $order ) || ! is_string( $order ) ) { + return 'DESC'; + } + + // Ascending or Descending + return ( 'ASC' === strtoupper( $order ) ) + ? 'ASC' + : 'DESC'; + } + + /** Private Shapers *******************************************************/ + + /** + * Shape items into their most relevant objects. + * + * This will try to use item_shape, but will fallback to a private + * method for querying and caching items. + * + * If using the `fields` parameter, results will have unique shapes based on + * exactly what was requested. + * + * @since 1.0.0 + * + * @param array $items + * @return array + */ + private function shape_items( $items = array() ) { + + // Force to stdClass if querying for fields + if ( ! empty( $this->query_vars['fields'] ) ) { + $this->item_shape = 'stdClass'; + } + + // Default return value + $retval = array(); + + // Use foreach because it's faster than array_map() + if ( ! empty( $items ) ) { + foreach ( $items as $item ) { + $retval[] = $this->get_item( $item ); + } + } + + /** + * Filters the object query results. + * + * Looks like `edd_get_customers` + * + * @since 1.0.0 + * + * @param array $retval An array of items. + * @param object &$this Current instance of Query, passed by reference. + */ + $retval = (array) apply_filters_ref_array( $this->apply_prefix( "the_{$this->item_name_plural}" ), array( $retval, &$this ) ); + + // Return filtered results + return ! empty( $this->query_vars['fields'] ) + ? $this->get_item_fields( $retval ) + : $retval; + } + + /** + * Get specific item fields based on query_vars['fields']. + * + * @since 1.0.0 + * + * @param array $items + * @return array + */ + private function get_item_fields( $items = array() ) { + + // Get the primary column + $primary = $this->get_primary_column_name(); + $fields = $this->query_vars['fields']; + + // Strings need to be single columns + if ( is_string( $fields ) ) { + $field = sanitize_key( $fields ); + $items = ( 'ids' === $fields ) + ? wp_list_pluck( $items, $primary ) + : wp_list_pluck( $items, $field, $primary ); + + // Arrays could be anything + } elseif ( is_array( $fields ) ) { + $new_items = array(); + $fields = array_flip( $fields ); + + // Loop through items and pluck out the fields + foreach ( $items as $item_id => $item ) { + $new_items[ $item_id ] = (object) array_intersect_key( (array) $item, $fields ); + } + + // Set the items and unset the new items + $items = $new_items; + unset( $new_items ); + } + + // Return the item, possibly reduced + return $items; + } + + /** + * Shape an item ID from an object, array, or numeric value + * + * @since 1.0.0 + * + * @param mixed $item + * @return int + */ + private function shape_item_id( $item = 0 ) { + + // Default return value + $retval = 0; + + // Get the primary column name + $primary = $this->get_primary_column_name(); + + // Numeric item ID + if ( is_numeric( $item ) ) { + $retval = $item; + + // Object item + } elseif ( is_object( $item ) && isset( $item->{$primary} ) ) { + $retval = $item->{$primary}; + + // Array item + } elseif ( is_array( $item ) && isset( $item[ $primary ] ) ) { + $retval = $item[ $primary ]; + } + + // Return the item ID + return absint( $retval ); + } + + /** Queries ***************************************************************/ + + /** + * Get a single database row by the primary column ID, possibly from cache. + * + * Accepts an integer, object, or array, and attempts to get the ID from it, + * then attempts to retrieve that item fresh from the database or cache. + * + * @since 1.0.0 + * + * @param int|array|object $item_id The ID of the item + * @return object|false False if empty/error, Object if successful + */ + public function get_item( $item_id = 0 ) { + + // Shape the item ID + $item_id = $this->shape_item_id( $item_id ); + + // Bail if no item to get by + if ( empty( $item_id ) ) { + return false; + } + + // Get the primary column name + $column_name = $this->get_primary_column_name(); + + // Get item by ID + return $this->get_item_by( $column_name, $item_id ); + } + + /** + * Get a single database row by any column and value, possibly from cache. + * + * Take care to only use this method on columns with unique values, + * preferably with a cache group for that column. See: get_item(). + * + * @since 1.0.0 + * + * @param string $column_name Name of database column + * @param int|string $column_value Value to query for + * @return object|false False if empty/error, Object if successful + */ + public function get_item_by( $column_name = '', $column_value = '' ) { + + // Default return value + $retval = false; + + // Bail if no key or value + if ( empty( $column_name ) || empty( $column_value ) ) { + return $retval; + } + + // Bail if name is not a string + if ( ! is_string( $column_name ) ) { + return $retval; + } + + // Bail if value is not scalar (null values also not allowed) + if ( ! is_scalar( $column_value ) ) { + return $retval; + } + + // Get column names + $columns = $this->get_column_names(); + + // Bail if column does not exist + if ( ! isset( $columns[ $column_name ] ) ) { + return $retval; + } + + // Cache groups + $groups = $this->get_cache_groups(); + + // Check cache + if ( ! empty( $groups[ $column_name ] ) ) { + $retval = $this->cache_get( $column_value, $groups[ $column_name ] ); + } + + // Item not cached + if ( false === $retval ) { + + // Try to get item directly from DB + $retval = $this->get_item_raw( $column_name, $column_value ); + + // Bail on failure + if ( ! $this->is_success( $retval ) ) { + return false; + } + + // Cache + $this->update_item_cache( $retval ); + } + + // Reduce the item + $retval = $this->reduce_item( 'select', $retval ); + + // Return result + return $this->shape_item( $retval ); + } + + /** + * Add an item to the database + * + * @since 1.0.0 + * + * @param array $data + * @return false|int Returns the item ID on success; false on failure. + */ + public function add_item( $data = array() ) { + + // Get primary column + $primary = $this->get_primary_column_name(); + + // If data includes primary column, check if item already exists + if ( ! empty( $data[ $primary ] ) ) { + + // Shape the primary item ID + $item_id = $this->shape_item_id( $data[ $primary ] ); + + // Get item by ID (from database, not cache) + $item = $this->get_item_raw( $primary, $item_id ); + + // Bail if item already exists + if ( ! empty( $item ) ) { + return false; + } + + // Set data primary ID to newly shaped ID + $data[ $primary ] = $item_id; + } + + // Get default values for item (from columns) + $item = $this->default_item(); + + // Unset the primary key if not part of data array (auto-incremented) + if ( empty( $data[ $primary ] ) ) { + unset( $item[ $primary ] ); + } + + // Cut out non-keys for meta + $columns = $this->get_column_names(); + $data = array_merge( $item, $data ); + $meta = array_diff_key( $data, $columns ); + $save = array_intersect_key( $data, $columns ); + + // Get the current time (maybe used by created/modified) + $time = $this->get_current_time(); + + // If date-created exists, but is empty or default, use the current time + $created = $this->get_column_by( array( 'created' => true ) ); + if ( ! empty( $created ) && ( empty( $save[ $created->name ] ) || ( $save[ $created->name ] === $created->default ) ) ) { + $save[ $created->name ] = $time; + } + + // If date-modified exists, but is empty or default, use the current time + $modified = $this->get_column_by( array( 'modified' => true ) ); + if ( ! empty( $modified ) && ( empty( $save[ $modified->name ] ) || ( $save[ $modified->name ] === $modified->default ) ) ) { + $save[ $modified->name ] = $time; + } + + // Try to add + $table = $this->get_table_name(); + $reduce = $this->reduce_item( 'insert', $save ); + $save = $this->validate_item( $reduce ); + $result = ! empty( $save ) + ? $this->get_db()->insert( $table, $save ) + : false; + + // Bail on failure + if ( ! $this->is_success( $result ) ) { + return false; + } + + // Get the new item ID + $item_id = $this->get_db()->insert_id; + + // Maybe save meta keys + if ( ! empty( $meta ) ) { + $this->save_extra_item_meta( $item_id, $meta ); + } + + // Use get item to prime caches + $this->update_item_cache( $item_id ); + + // Transition item data + $this->transition_item( $save, $item_id ); + + /** + * Adds a hook for a successfully added item. + * Custom for EDD. + * + * @since 3.1.1.4 + * @param int $item_id The item id. + * @param array $data The array of data for the update. + */ + do_action( $this->apply_prefix( "{$this->item_name}_added" ), $item_id, $data ); + + // Return result + return $item_id; + } + + /** + * Update an item in the database + * + * @since 1.0.0 + * + * @param int $item_id + * @param array $data + * @return bool + */ + public function update_item( $item_id = 0, $data = array() ) { + + // Bail if no item ID + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) ) { + return false; + } + + // Get primary column + $primary = $this->get_primary_column_name(); + + // Get item to update (from database, not cache) + $item_raw = $this->get_item_raw( $primary, $item_id ); + + // Bail if item does not exist to update + if ( empty( $item_raw ) ) { + return false; + } + + // Cast as an array for easier manipulation + $item = (array) $item_raw; + + // Unset the primary key from data to parse + unset( $data[ $primary ] ); + + // Splice new data into item, and cut out non-keys for meta + $columns = $this->get_column_names(); + $data = array_merge( $item, $data ); + $meta = array_diff_key( $data, $columns ); + $save = array_intersect_key( $data, $columns ); + + // Maybe save meta keys + if ( ! empty( $meta ) ) { + $this->save_extra_item_meta( $item_id, $meta ); + } + + // Bail if no change + if ( (array) $save === (array) $item ) { + return true; + } + + // Unset the primary key from data to save + unset( $save[ $primary ] ); + + // If date-modified is empty, use the current time + $modified = $this->get_column_by( array( 'modified' => true ) ); + if ( ! empty( $modified ) ) { + $save[ $modified->name ] = $this->get_current_time(); + } + + // Try to update + $where = array( $primary => $item_id ); + $table = $this->get_table_name(); + $reduce = $this->reduce_item( 'update', $save ); + $save = $this->validate_item( $reduce ); + $result = ! empty( $save ) + ? $this->get_db()->update( $table, $save, $where ) + : false; + + // Bail on failure + if ( ! $this->is_success( $result ) ) { + return false; + } + + // Use get item to prime caches + $this->update_item_cache( $item_id ); + + // Transition item data + $this->transition_item( $save, $item ); + + /** + * Adds a hook for a successfully updated item. + * Custom for EDD. + * + * @since 3.1.1.4 + * @param int $item_id The item id. + * @param array $data The array of data for the update. + * @param object $item_raw The original item. + */ + do_action( $this->apply_prefix( "{$this->item_name}_updated" ), $item_id, $data, $item_raw ); + + // Return result + return $result; + } + + /** + * Delete an item from the database + * + * @since 1.0.0 + * + * @param int $item_id + * @return bool + */ + public function delete_item( $item_id = 0 ) { + + // Bail if no item ID + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) ) { + return false; + } + + // Get vars + $primary = $this->get_primary_column_name(); + + // Get item (before it's deleted) + $item = $this->get_item_raw( $primary, $item_id ); + + // Bail if item does not exist to delete + if ( empty( $item ) ) { + return false; + } + + // Attempt to reduce this item + $item = $this->reduce_item( 'delete', $item ); + + // Bail if item was reduced to nothing + if ( empty( $item ) ) { + return false; + } + + // Try to delete + $table = $this->get_table_name(); + $where = array( $primary => $item_id ); + $result = $this->get_db()->delete( $table, $where ); + + // Bail on failure + if ( ! $this->is_success( $result ) ) { + return false; + } + + // Clean caches on successful delete + $this->delete_all_item_meta( $item_id ); + $this->clean_item_cache( $item ); + + /** + * Adds a hook for a successfully deleted item. + * Custom for EDD. + * + * @since 3.1.1.4 + * @param int $item_id The item id. + */ + do_action( $this->apply_prefix( "{$this->item_name}_deleted" ), $item_id ); + + // Return result + return $result; + } + + /** + * Filter an item before it is inserted of updated in the database. + * + * This method is public to allow subclasses to perform JIT manipulation + * of the parameters passed into it. + * + * @since 1.0.0 + * + * @param array $item + * @return array + */ + public function filter_item( $item = array() ) { + return (array) apply_filters_ref_array( $this->apply_prefix( "filter_{$this->item_name}_item" ), array( $item, &$this ) ); + } + + /** + * Shape an item from the database into the type of object it always wanted + * to be when it grew up. + * + * @since 1.0.0 + * + * @param mixed ID of item, or row from database + * @return mixed False on error, Object of single-object class type on success + */ + private function shape_item( $item = 0 ) { + + // Get the item from an ID + if ( is_numeric( $item ) ) { + $item = $this->get_item( $item ); + } + + // Return the item if it's already shaped + if ( $item instanceof $this->item_shape ) { + return $item; + } + + // Shape the item as needed + $item = ! empty( $this->item_shape ) + ? new $this->item_shape( $item ) + : (object) $item; + + // Return the item object + return $item; + } + + /** + * Validate an item before it is updated in or added to the database. + * + * @since 1.0.0 + * + * @param array $item + * @return array|false False on error, Array of validated values on success + */ + private function validate_item( $item = array() ) { + + // Bail if item is empty or not an array + if ( empty( $item ) || ! is_array( $item ) ) { + return $item; + } + + // Loop through item attributes + foreach ( $item as $key => $value ) { + + // Strip slashes from all strings + if ( is_string( $value ) ) { + $value = stripslashes( $value ); + } + + // Get column + $column = $this->get_column_by( array( 'name' => $key ) ); + + // Null value is special for all item keys + if ( is_null( $value ) ) { + + // Bail if null is not allowed + if ( false === $column->allow_null ) { + return false; + } + + // Attempt to validate + } elseif ( ! empty( $column->validate ) && is_callable( $column->validate ) ) { + $validated = call_user_func( $column->validate, $value ); + + // Bail if error + if ( is_wp_error( $validated ) ) { + return false; + } + + // Update the value + $item[ $key ] = $validated; + + /** + * Fallback to using the raw value. + * + * Note: This may change at a later date, so do not rely on this. + * Please always validate all data. + */ + } else { + $item[ $key ] = $value; + } + } + + // Return the validated item + return $this->filter_item( $item ); + } + + /** + * Reduce an item down to the keys and values the current user has the + * appropriate capabilities to select|insert|update|delete. + * + * Note that internally, this method works with both arrays and objects of + * any type, and also resets the key values. It looks weird, but is + * currently by design to protect the integrity of the return value. + * + * @since 1.0.0 + * + * @param string $method select|insert|update|delete + * @param array|object $item Object|Array of keys/values to reduce + * + * @return mixed Object|Array without keys the current user does not have caps for + */ + private function reduce_item( $method = 'update', $item = array() ) { + + // Bail if item is empty + if ( empty( $item ) ) { + return $item; + } + + // Loop through item attributes + foreach ( $item as $key => $value ) { + + // Get callback for column + $caps = $this->get_column_field( array( 'name' => $key ), 'caps' ); + + // Unset if not explicitly allowed + if ( empty( $caps[ $method ] ) || ! current_user_can( $caps[ $method ] ) ) { + if ( is_array( $item ) ) { + unset( $item[ $key ] ); + } elseif ( is_object( $item ) ) { + $item->{$key} = null; + } + + // Set if explicitly allowed + } elseif ( is_array( $item ) ) { + $item[ $key ] = $value; + } elseif ( is_object( $item ) ) { + $item->{$key} = $value; + } + } + + // Return the reduced item + return $item; + } + + /** + * Return an item comprised of all default values + * + * This is used by `add_item()` to populate known default values, to ensure + * new item data is always what we expect it to be. + * + * @since 1.0.0 + * + * @return array + */ + private function default_item() { + + // Default return value + $retval = array(); + + // Get column names and defaults + $names = $this->get_columns( array(), 'and', 'name' ); + $defaults = $this->get_columns( array(), 'and', 'default' ); + + // Put together an item using default values + foreach ( $names as $key => $name ) { + $retval[ $name ] = $defaults[ $key ]; + } + + // Return + return $retval; + } + + /** + * Transition an item when adding or updating. + * + * This method takes the data being saved, looks for any columns that are + * known to transition between values, and fires actions on them. + * + * @since 1.0.0 + * + * @param array $item + * @return array + */ + private function transition_item( $new_data = array(), $old_data = array() ) { + + // Look for transition columns + $columns = $this->get_columns( array( 'transition' => true ), 'and', 'name' ); + + // Bail if no columns to transition + if ( empty( $columns ) ) { + return; + } + + // Get the item ID + $item_id = $this->shape_item_id( $old_data ); + + // Bail if item ID cannot be retrieved + if ( empty( $item_id ) ) { + return; + } + + // If no old value(s), it's new + if ( ! is_array( $old_data ) ) { + $old_data = $new_data; + + // Set all old values to "new" + foreach ( $old_data as $key => $value ) { + $value = 'new'; + $old_data[ $key ] = $value; + } + } + + // Compare + $keys = array_flip( $columns ); + $new = array_intersect_key( $new_data, $keys ); + $old = array_intersect_key( $old_data, $keys ); + + // Get the difference + $diff = array_diff( $new, $old ); + + // Bail if nothing is changing + if ( empty( $diff ) ) { + return; + } + + // Do the actions + foreach ( $diff as $key => $value ) { + $old_value = $old_data[ $key ]; + $new_value = $new_data[ $key ]; + $key_action = $this->apply_prefix( "transition_{$this->item_name}_{$key}" ); + + /** + * Fires after an object value has transitioned. + * + * @since 1.0.0 + * + * @param mixed $old_value The value being transitioned FROM. + * @param mixed $new_value The value being transitioned TO. + * @param int $item_id The ID of the item that is transitioning. + */ + do_action( $key_action, $old_value, $new_value, $item_id ); + } + } + + /** Meta ******************************************************************/ + + /** + * Add meta data to an item + * + * @since 1.0.0 + * + * @param int $item_id + * @param string $meta_key + * @param string $meta_value + * @param string $unique + * @return int|false The meta ID on success, false on failure. + */ + protected function add_item_meta( $item_id = 0, $meta_key = '', $meta_value = '', $unique = false ) { + + // Bail if no meta was returned + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) || empty( $meta_key ) ) { + return false; + } + + // Bail if no meta table exists + if ( false === $this->get_meta_table_name() ) { + return false; + } + + // Return results of get meta data + return add_metadata( $table, $item_id, $meta_key, $meta_value, $unique ); + } + + /** + * Get meta data for an item + * + * @since 1.0.0 + * + * @param int $item_id + * @param string $meta_key + * @param bool $single + * @return mixed Single metadata value, or array of values + */ + protected function get_item_meta( $item_id = 0, $meta_key = '', $single = false ) { + + // Bail if no meta was returned + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) || empty( $meta_key ) ) { + return false; + } + + // Bail if no meta table exists + if ( false === $this->get_meta_table_name() ) { + return false; + } + + // Get meta type + $meta_type = $this->get_meta_type(); + + // Return results of getting meta data + return get_metadata( $meta_type, $item_id, $meta_key, $single ); + } + + /** + * Update meta data for an item + * + * @since 1.0.0 + * + * @param int $item_id + * @param string $meta_key + * @param string $meta_value + * @param string $prev_value + * @return bool True on successful update, false on failure. + */ + protected function update_item_meta( $item_id = 0, $meta_key = '', $meta_value = '', $prev_value = '' ) { + + // Bail if no meta was returned + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) || empty( $meta_key ) ) { + return false; + } + + // Bail if no meta table exists + if ( false === $this->get_meta_table_name() ) { + return false; + } + + // Get meta type + $meta_type = $this->get_meta_type(); + + // Return results of updating meta data + return update_metadata( $meta_type, $item_id, $meta_key, $meta_value, $prev_value ); + } + + /** + * Delete meta data for an item + * + * @since 1.0.0 + * + * @param int $item_id + * @param string $meta_key + * @param string $meta_value + * @param string $delete_all + * @return bool True on successful delete, false on failure. + */ + protected function delete_item_meta( $item_id = 0, $meta_key = '', $meta_value = '', $delete_all = false ) { + + // Bail if no meta was returned + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) || empty( $meta_key ) ) { + return false; + } + + // Bail if no meta table exists + if ( false === $this->get_meta_table_name() ) { + return false; + } + + // Get meta type + $meta_type = $this->get_meta_type(); + + // Return results of deleting meta data + return delete_metadata( $meta_type, $item_id, $meta_key, $meta_value, $delete_all ); + } + + /** + * Get registered meta data keys + * + * @since 1.0.0 + * + * @param string $object_subtype The sub-type of meta keys + * + * @return array + */ + private function get_registered_meta_keys( $object_subtype = '' ) { + + // Get the object type + $object_type = $this->get_meta_type(); + + // Return the keys + return get_registered_meta_keys( $object_type, $object_subtype ); + } + + /** + * Maybe update meta values on item update/save + * + * @since 1.0.0 + * + * @param array $meta + */ + private function save_extra_item_meta( $item_id = 0, $meta = array() ) { + + // Bail if there is no bulk meta to save + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) || empty( $meta ) ) { + return; + } + + // Bail if no meta table exists + if ( false === $this->get_meta_table_name() ) { + return; + } + + // Only save registered keys + $keys = $this->get_registered_meta_keys(); + $meta = array_intersect_key( $meta, $keys ); + + // Bail if no registered meta keys + if ( empty( $meta ) ) { + return; + } + + // Save or delete meta data + foreach ( $meta as $key => $value ) { + ! empty( $value ) + ? $this->update_item_meta( $item_id, $key, $value ) + : $this->delete_item_meta( $item_id, $key ); + } + } + + /** + * Delete all meta data for an item + * + * @since 1.0.0 + * + * @param int $item_id + */ + private function delete_all_item_meta( $item_id = 0 ) { + + // Bail if no meta was returned + $item_id = $this->shape_item_id( $item_id ); + if ( empty( $item_id ) ) { + return; + } + + // Get the meta table name + $table = $this->get_meta_table_name(); + + // Bail if no meta table exists + if ( empty( $table ) ) { + return; + } + + // Guess the item ID column for the meta table + $primary_id = $this->get_primary_column_name(); + $item_id_column = $this->apply_prefix( "{$this->item_name}_{$primary_id}" ); + + // Get meta IDs + $query = "SELECT meta_id FROM {$table} WHERE {$item_id_column} = %d"; + $prepared = $this->get_db()->prepare( $query, $item_id ); + $meta_ids = $this->get_db()->get_col( $prepared ); + + // Bail if no meta IDs to delete + if ( empty( $meta_ids ) ) { + return; + } + + // Get the meta type + $meta_type = $this->get_meta_type(); + + // Delete all meta data for this item ID + foreach ( $meta_ids as $mid ) { + delete_metadata_by_mid( $meta_type, $mid ); + } + } + + /** + * Get the meta table for this query + * + * Forked from WordPress\_get_meta_table() so it can be more accurately + * predicted in a future iteration and default to returning false. + * + * @since 1.0.0 + * + * @return mixed Table name if exists, False if not + */ + private function get_meta_table_name() { + + // Get the meta-type + $type = $this->get_meta_type(); + + // Append "meta" to end of meta-type + $table_name = "{$type}meta"; + + // Variable'ize the database interface, to use inside empty() + $db = $this->get_db(); + + // If not empty, return table name + if ( ! empty( $db->{$table_name} ) ) { + return $db->{$table_name}; + } + + // Default return false + return false; + } + + /** + * Get the meta type for this query + * + * This method exists to reduce some duplication for now. Future iterations + * will likely use Column::relationships to + * + * @since 1.1.0 + * + * @return string + */ + private function get_meta_type() { + return $this->apply_prefix( $this->item_name ); + } + + /** Cache *****************************************************************/ + + /** + * Get cache key from query_vars and query_var_defaults. + * + * @since 1.0.0 + * + * @return string + */ + private function get_cache_key( $group = '' ) { + + // Slice query vars + $slice = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ); + + // Unset `fields` so it does not effect the cache key + unset( $slice['fields'] ); + + // Setup key & last_changed + $key = md5( serialize( $slice ) ); + $last_changed = $this->get_last_changed_cache( $group ); + + // Concatenate and return cache key + return "get_{$this->item_name_plural}:{$key}:{$last_changed}"; + } + + /** + * Get the cache group, or fallback to the primary one. + * + * @since 1.0.0 + * + * @param string $group + * @return string + */ + private function get_cache_group( $group = '' ) { + + // Get the primary column + $primary = $this->get_primary_column_name(); + + // Default return value + $retval = $this->cache_group; + + // Only allow non-primary groups + if ( ! empty( $group ) && ( $group !== $primary ) ) { + $retval = $group; + } + + // Return the group + return $retval; + } + + /** + * Get array of which database columns have uniquely cached groups + * + * @since 1.0.0 + * + * @return array + */ + private function get_cache_groups() { + + // Return value + $cache_groups = array(); + + // Get cache groups + $groups = $this->get_columns( array( 'cache_key' => true ), 'and', 'name' ); + + if ( ! empty( $groups ) ) { + + // Get the primary column + $primary = $this->get_primary_column_name(); + + // Setup return values + foreach ( $groups as $name ) { + if ( $primary !== $name ) { + $cache_groups[ $name ] = "{$this->cache_group}-by-{$name}"; + } else { + $cache_groups[ $name ] = $this->cache_group; + } + } + } + + // Return cache groups array + return $cache_groups; + } + + /** + * Maybe prime item & item-meta caches by querying 1 time for all un-cached + * items. + * + * Accepts a single ID, or an array of IDs. + * + * The reason this accepts only IDs is because it gets called immediately + * after an item is inserted in the database, but before items have been + * "shaped" into proper objects, so object properties may not be set yet. + * + * @since 1.0.0 + * + * @param array $item_ids + * @param bool $force + * + * @return bool False if empty + */ + private function prime_item_caches( $item_ids = array(), $force = false ) { + + // Bail if no items to cache + if ( empty( $item_ids ) ) { + return false; + } + + // Accepts single values, so cast to array + $item_ids = (array) $item_ids; + + // Update item caches + if ( ! empty( $force ) || ! empty( $this->query_vars['update_item_cache'] ) ) { + + // Look for non-cached IDs + $ids = $this->get_non_cached_ids( $item_ids, $this->cache_group ); + + // Bail if IDs are cached + if ( empty( $ids ) ) { + return false; + } + + // Query + $table = $this->get_table_name(); + $primary = $this->get_primary_column_name(); + $query = "SELECT * FROM {$table} WHERE {$primary} IN (%s)"; + $ids = join( ',', array_map( 'absint', $ids ) ); + $prepare = sprintf( $query, $ids ); + $results = $this->get_db()->get_results( $prepare ); + + // Update item caches + $this->update_item_cache( $results ); + } + + // Update meta data caches + if ( ! empty( $this->query_vars['update_meta_cache'] ) ) { + $singular = rtrim( $this->table_name, 's' ); // sic + update_meta_cache( $singular, $item_ids ); + } + } + + /** + * Update the cache for an item. Does not update item-meta cache. + * + * Accepts a single object, or an array of objects. + * + * The reason this does not accept ID's is because this gets called + * after an item is already updated in the database, so we want to avoid + * querying for it again. It's just safer this way. + * + * @since 1.0.0 + * + * @param array $items + */ + private function update_item_cache( $items = array() ) { + + // Maybe query for single item + if ( is_numeric( $items ) ) { + $primary = $this->get_primary_column_name(); + $items = $this->get_item_raw( $primary, $items ); + } + + // Bail if no items to cache + if ( empty( $items ) ) { + return false; + } + + // Make sure items are an array (without casting objects to arrays) + if ( ! is_array( $items ) ) { + $items = array( $items ); + } + + // Get cache groups + $groups = $this->get_cache_groups(); + + // Loop through all items and cache them + foreach ( $items as $item ) { + + // Skip if item is not an object + if ( ! is_object( $item ) ) { + continue; + } + + // Loop through groups and set cache + if ( ! empty( $groups ) ) { + foreach ( $groups as $key => $group ) { + $this->cache_set( $item->{$key}, $item, $group ); + } + } + } + + // Update last changed + $this->update_last_changed_cache(); + } + + /** + * Clean the cache for an item. Does not clean item-meta. + * + * Accepts a single object, or an array of objects. + * + * The reason this does not accept ID's is because this gets called + * after an item is already deleted from the database, so it cannot be + * queried and may not exist in the cache. It's just safer this way. + * + * @since 1.0.0 + * + * @param mixed $items Single object item, or Array of object items + * + * @return bool + */ + private function clean_item_cache( $items = array() ) { + + // Bail if no items to clean + if ( empty( $items ) ) { + return false; + } + + // Make sure items are an array + if ( ! is_array( $items ) ) { + $items = array( $items ); + } + + // Get all cache groups + $groups = $this->get_cache_groups(); + + // Loop through all items and clean them + foreach ( $items as $item ) { + + // Skip if item is not an object + if ( ! is_object( $item ) ) { + continue; + } + + // Loop through groups and delete cache + if ( ! empty( $groups ) ) { + foreach ( $groups as $key => $group ) { + $this->cache_delete( $item->{$key}, $group ); + } + } + } + + // Update last changed + $this->update_last_changed_cache(); + } + + /** + * Update the last_changed key for the cache group + * + * @since 1.0.0 + */ + private function update_last_changed_cache( $group = '' ) { + + // Fallback to microtime + if ( empty( $this->last_changed ) ) { + $this->set_last_changed(); + } + + // Set the last changed time for this cache group + $this->cache_set( 'last_changed', $this->last_changed, $group ); + + // Return the last changed time + return $this->last_changed; + } + + /** + * Get the last_changed key for a cache group + * + * @since 1.0.0 + * + * @param string $group Cache group. Defaults to $this->cache_group + * + * @return int The last time a cache group was changed + */ + private function get_last_changed_cache( $group = '' ) { + + // Get the last changed cache value + $last_changed = $this->cache_get( 'last_changed', $group ); + + // Maybe update the last changed value + if ( false === $last_changed ) { + $last_changed = $this->update_last_changed_cache( $group ); + } + + // Return the last changed value for the cache group + return $last_changed; + } + + /** + * Get array of non-cached item IDs. + * + * @since 1.0.0 + * + * @param array $item_ids Array of item IDs + * @param string $group Cache group. Defaults to $this->cache_group + * + * @return array + */ + private function get_non_cached_ids( $item_ids = array(), $group = '' ) { + $retval = array(); + + // Bail if no item IDs + if ( empty( $item_ids ) ) { + return $retval; + } + + // Loop through item IDs + foreach ( $item_ids as $id ) { + $id = $this->shape_item_id( $id ); + + if ( false === $this->cache_get( $id, $group ) ) { + $retval[] = $id; + } + } + + // Return array of IDs + return $retval; + } + + /** + * Add a cache value for a key and group. + * + * @since 1.0.0 + * + * @param string $key Cache key. + * @param mixed $value Cache value. + * @param string $group Cache group. Defaults to $this->cache_group + * @param int $expire Expiration. + */ + private function cache_add( $key = '', $value = '', $group = '', $expire = 0 ) { + + // Bail if cache invalidation is suspended + if ( wp_suspend_cache_addition() ) { + return; + } + + // Bail if no cache key + if ( empty( $key ) ) { + return; + } + + // Get the cache group + $group = $this->get_cache_group( $group ); + + // Add to the cache + wp_cache_add( $key, $value, $group, $expire ); + } + + /** + * Get a cache value for a key and group. + * + * @since 1.0.0 + * + * @param string $key Cache key. + * @param string $group Cache group. Defaults to $this->cache_group + * @param bool $force + */ + private function cache_get( $key = '', $group = '', $force = false ) { + + // Bail if no cache key + if ( empty( $key ) ) { + return; + } + + // Get the cache group + $group = $this->get_cache_group( $group ); + + // Get from the cache + return wp_cache_get( $key, $group, $force ); + } + + /** + * Set a cache value for a key and group. + * + * @since 1.0.0 + * + * @param string $key Cache key. + * @param mixed $value Cache value. + * @param string $group Cache group. Defaults to $this->cache_group + * @param int $expire Expiration. + */ + private function cache_set( $key = '', $value = '', $group = '', $expire = 0 ) { + + // Bail if cache invalidation is suspended + if ( wp_suspend_cache_addition() ) { + return; + } + + // Bail if no cache key + if ( empty( $key ) ) { + return; + } + + // Get the cache group + $group = $this->get_cache_group( $group ); + + // Update the cache + wp_cache_set( $key, $value, $group, $expire ); + } + + /** + * Delete a cache key for a group. + * + * @since 1.0.0 + * + * @global bool $_wp_suspend_cache_invalidation + * + * @param string $key Cache key. + * @param string $group Cache group. Defaults to $this->cache_group + */ + private function cache_delete( $key = '', $group = '' ) { + global $_wp_suspend_cache_invalidation; + + // Bail if cache invalidation is suspended + if ( ! empty( $_wp_suspend_cache_invalidation ) ) { + return; + } + + // Bail if no cache key + if ( empty( $key ) ) { + return; + } + + // Get the cache group + $group = $this->get_cache_group( $group ); + + // Delete the cache + wp_cache_delete( $key, $group ); + } + + /** + * Fetch raw results directly from the database. + * + * @since 1.0.0 + * + * @param array $cols Columns for `SELECT`. + * @param array $where_cols Where clauses. Each key-value pair in the array + * represents a column and a comparison. + * @param int $limit Optional. LIMIT value. Default 25. + * @param null $offset Optional. OFFSET value. Default null. + * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants. + * Default OBJECT. + * With one of the first three, return an array of + * rows indexed from 0 by SQL result row number. + * Each row is an associative array (column => value, ...), + * a numerically indexed array (0 => value, ...), + * or an object. ( ->column = value ), respectively. + * With OBJECT_K, return an associative array of + * row objects keyed by the value of each row's + * first column's value. + * + * @return array|object|null Database query results. + */ + public function get_results( $cols = array(), $where_cols = array(), $limit = 25, $offset = null, $output = OBJECT ) { + + // Bail if no columns have been passed. + if ( empty( $cols ) ) { + return null; + } + + // Fetch all the columns for the table being queried. + $column_names = $this->get_column_names(); + + // Ensure valid column names have been passed for the `SELECT` clause. + foreach ( $cols as $index => $column ) { + if ( ! array_key_exists( $column, $column_names ) ) { + unset( $cols[ $index ] ); + } + } + + // Setup base SQL query. + $query = "SELECT "; + $query .= implode( ',', $cols ); + $query .= " FROM {$this->get_table_name()} {$this->table_alias} "; + $query .= " WHERE 1=1 "; + + // Ensure valid columns have been passed for the `WHERE` clause. + if ( ! empty( $where_cols ) ) { + + // Get keys from where columns + $columns = array_keys( $where_cols ); + + // Loop through columns and unset any invalid names + foreach ( $columns as $index => $column ) { + if ( ! array_key_exists( $column, $column_names ) ) { + unset( $where_cols[ $index ] ); + } + } + + // Parse WHERE clauses. + foreach ( $where_cols as $column => $compare ) { + + // Basic WHERE clause. + if ( ! is_array( $compare ) ) { + $pattern = $this->get_column_field( array( 'name' => $column ), 'pattern', '%s' ); + $statement = " AND {$this->table_alias}.{$column} = {$pattern} "; + $query .= $this->get_db()->prepare( $statement, $compare ); + + // More complex WHERE clause. + } else { + $value = isset( $compare['value'] ) + ? $compare['value'] + : false; + + // Skip if a value was not provided. + if ( false === $value ) { + continue; + } + + // Default compare clause to equals. + $compare_clause = isset( $compare['compare_query'] ) + ? trim( strtoupper( $compare['compare_query'] ) ) + : '='; + + // Array (unprepared) + if ( is_array( $compare['value'] ) ) { + + // Default to IN if clause not specified. + if ( ! in_array( $compare_clause, array( 'IN', 'NOT IN', 'BETWEEN' ), true ) ) { + $compare_clause = 'IN'; + } + + // Parse & escape for IN and NOT IN. + if ( 'IN' === $compare_clause || 'NOT IN' === $compare_clause ) { + $value = "('" . implode( "','", $this->get_db()->_escape( $compare['value'] ) ) . "')"; + + // Parse & escape for BETWEEN. + } elseif ( is_array( $value ) && 2 === count( $value ) && 'BETWEEN' === $compare_clause ) { + $_this = $this->get_db()->_escape( $value[0] ); + $_that = $this->get_db()->_escape( $value[1] ); + $value = " {$_this} AND {$_that} "; + } + } + + // Add WHERE clause. + $query .= " AND {$this->table_alias}.{$column} {$compare_clause} {$value} "; + } + } + } + + // Maybe set an offset. + if ( ! empty( $offset ) ) { + $values = explode( ',', $offset ); + $values = array_filter( $values, 'intval' ); + $offset = implode( ',', $values ); + $query .= " OFFSET {$offset} "; + } + + // Maybe set a limit. + if ( ! empty( $limit ) && ( $limit > 0 ) ) { + $limit = intval( $limit ); + $query .= " LIMIT {$limit} "; + } + + // Execute query. + $results = $this->get_db()->get_results( $query, $output ); + + // Return results. + return $results; + } +} diff --git a/includes/database/engine/class-row.php b/includes/database/engine/class-row.php new file mode 100644 index 00000000000..853757e7355 --- /dev/null +++ b/includes/database/engine/class-row.php @@ -0,0 +1,65 @@ +init( $item ); + } + } + + /** + * Initialize class properties based on data array. + * + * @since 1.0.0 + * + * @param array $data + */ + private function init( $data = array() ) { + $this->set_vars( $data ); + } + + /** + * Determines whether the current row exists. + * + * @since 1.0.0 + * + * @return bool + */ + public function exists() { + return ! empty( $this->id ); + } +} diff --git a/includes/database/engine/class-schema.php b/includes/database/engine/class-schema.php new file mode 100644 index 00000000000..192289915cf --- /dev/null +++ b/includes/database/engine/class-schema.php @@ -0,0 +1,88 @@ +columns ) || ! is_array( $this->columns ) ) { + return; + } + + // Juggle original columns array + $columns = $this->columns; + $this->columns = array(); + + // Loop through columns and create objects from them + foreach ( $columns as $column ) { + if ( is_array( $column ) ) { + $this->columns[] = new Column( $column ); + } elseif ( $column instanceof Column ) { + $this->columns[] = $column; + } + } + } + + /** + * Return the schema in string form. + * + * @since 1.0.0 + * + * @return string Calls get_create_string() on every column. + */ + protected function to_string() { + + // Default return value + $retval = ''; + + // Bail if no columns to convert + if ( empty( $this->columns ) ) { + return $retval; + } + + // Loop through columns... + foreach ( $this->columns as $column_info ) { + if ( method_exists( $column_info, 'get_create_string' ) ) { + $retval .= '\n' . $column_info->get_create_string() . ', '; + } + } + + // Return the string + return $retval; + } +} diff --git a/includes/database/engine/class-table.php b/includes/database/engine/class-table.php new file mode 100644 index 00000000000..0fc88c20a0f --- /dev/null +++ b/includes/database/engine/class-table.php @@ -0,0 +1,933 @@ + value array of versions => methods. + * + * @since 1.0.0 + * @var array + */ + protected $upgrades = array(); + + /** Methods ***************************************************************/ + + /** + * Hook into queries, admin screens, and more! + * + * @since 1.0.0 + */ + public function __construct() { + + // Setup the database table + $this->setup(); + + // Bail if setup failed + if ( empty( $this->name ) || empty( $this->db_version_key ) ) { + return; + } + + // Add the table to the database interface + $this->set_db_interface(); + + // Set the database schema + $this->set_schema(); + + // Add hooks + $this->add_hooks(); + + // Maybe force upgrade if testing + if ( $this->is_testing() ) { + $this->maybe_upgrade(); + } + } + + /** Abstract **************************************************************/ + + /** + * Setup this database table. + * + * @since 1.0.0 + */ + protected abstract function set_schema(); + + /** Multisite *************************************************************/ + + /** + * Update table version & references. + * + * Hooked to the "switch_blog" action. + * + * @since 1.0.0 + * + * @param int $site_id The site being switched to + */ + public function switch_blog( $site_id = 0 ) { + + // Update DB version based on the current site + if ( ! $this->is_global() ) { + $this->db_version = get_blog_option( $site_id, $this->db_version_key, false ); + } + + // Update interface for switched site + $this->set_db_interface(); + } + + /** Public Helpers ********************************************************/ + + /** + * Maybe upgrade the database table. Handles creation & schema changes. + * + * Hooked to the `admin_init` action. + * + * @since 1.0.0 + */ + public function maybe_upgrade() { + + // Bail if not upgradeable + if ( ! $this->is_upgradeable() ) { + return; + } + + // Bail if upgrade not needed + if ( ! $this->needs_upgrade() ) { + return; + } + + // Upgrade + if ( $this->exists() ) { + $this->upgrade(); + + // Install + } else { + $this->install(); + } + } + + /** + * Return whether this table needs an upgrade. + * + * @since 1.0.0 + * + * @param mixed $version Database version to check if upgrade is needed + * + * @return bool True if table needs upgrading. False if not. + */ + public function needs_upgrade( $version = false ) { + + // Use the current table version if none was passed + if ( empty( $version ) ) { + $version = $this->version; + } + + // Get the current database version + $this->get_db_version(); + + // Is the database table up to date? + $is_current = version_compare( $this->db_version, $version, '>=' ); + + // Return false if current, true if out of date + return ( true === $is_current ) + ? false + : true; + } + + /** + * Return whether this table can be upgraded. + * + * @since 1.0.0 + * + * @return bool True if table can be upgraded. False if not. + */ + public function is_upgradeable() { + + // Bail if global and upgrading global tables is not allowed + if ( $this->is_global() && ! wp_should_upgrade_global_tables() ) { + return false; + } + + // Kinda weird, but assume it is + return true; + } + + /** + * Return the current table version from the database. + * + * This is public method for accessing a private variable so that it cannot + * be externally modified. + * + * @since 1.0.0 + * + * @return string + */ + public function get_version() { + $this->get_db_version(); + + return $this->db_version; + } + + /** + * Install a database table by creating the table and setting the version. + * + * @since 1.0.0 + */ + public function install() { + $created = $this->create(); + + // Set the DB version if create was successful + if ( true === $created ) { + $this->set_db_version(); + } + } + + /** + * Destroy a database table by dropping the table and deleting the version. + * + * @since 1.0.0 + */ + public function uninstall() { + $dropped = $this->drop(); + + // Delete the DB version if drop was successful + if ( true === $dropped ) { + $this->delete_db_version(); + } + } + + /** Public Management *****************************************************/ + + /** + * Check if table already exists. + * + * @since 1.0.0 + * + * @return bool + */ + public function exists() { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Query statement + $query = "SHOW TABLES LIKE %s"; + $like = $db->esc_like( $this->table_name ); + $prepared = $db->prepare( $query, $like ); + $result = $db->get_var( $prepared ); + + // Does the table exist? + return $this->is_success( $result ); + } + + /** + * Create the table. + * + * @since 1.0.0 + * + * @return bool + */ + public function create() { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Query statement + $query = "CREATE TABLE {$this->table_name} ( {$this->schema} ) {$this->charset_collation}"; + $result = $db->query( $query ); + + // Was the table created? + return $this->is_success( $result ); + } + + /** + * Drop the database table. + * + * @since 1.0.0 + * + * @return bool + */ + public function drop() { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Query statement + $query = "DROP TABLE {$this->table_name}"; + $result = $db->query( $query ); + + // Did the table get dropped? + return $this->is_success( $result ); + } + + /** + * Truncate the database table. + * + * @since 1.0.0 + * + * @return bool + */ + public function truncate() { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Query statement + $query = "TRUNCATE TABLE {$this->table_name}"; + $result = $db->query( $query ); + + // Did the table get truncated? + return $this->is_success( $result ); + } + + /** + * Delete all items from the database table. + * + * @since 1.0.0 + * + * @return bool + */ + public function delete_all() { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Query statement + $query = "DELETE FROM {$this->table_name}"; + $deleted = $db->query( $query ); + + // Did the table get emptied? + return $deleted; + } + + /** + * Clone this database table. + * + * Pair with copy(). + * + * @since 1.1.0 + * + * @param string $new_table_name The name of the new table, without prefix + * + * @return bool + */ + public function _clone( $new_table_name = '' ) { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Sanitize the new table name + $table_name = $this->sanitize_table_name( $new_table_name ); + + // Bail if new table name is invalid + if ( empty( $table_name ) ) { + return false; + } + + // Query statement + $table = $this->apply_prefix( $table_name ); + $query = "CREATE TABLE {$table} LIKE {$this->table_name}"; + $result = $db->query( $query ); + + // Did the table get cloned? + return $this->is_success( $result ); + } + + /** + * Copy the contents of this table to a new table. + * + * Pair with clone(). + * + * @since 1.1.0 + * + * @param string $new_table_name The name of the new table, without prefix + * + * @return bool + */ + public function copy( $new_table_name = '' ) { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Sanitize the new table name + $table_name = $this->sanitize_table_name( $new_table_name ); + + // Bail if new table name is invalid + if ( empty( $table_name ) ) { + return false; + } + + // Query statement + $table = $this->apply_prefix( $table_name ); + $query = "INSERT INTO {$table} SELECT * FROM {$this->table_name}"; + $result = $db->query( $query ); + + // Did the table get copied? + return $this->is_success( $result ); + } + + /** + * Count the number of items in the database table. + * + * @since 1.0.0 + * + * @return int + */ + public function count() { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return 0; + } + + // Query statement + $query = "SELECT COUNT(*) FROM {$this->table_name}"; + $count = $db->get_var( $query ); + + // Query success/fail + return intval( $count ); + } + + /** + * Check if column already exists. + * + * @since 1.0.0 + * + * @param string $name Value + * + * @return bool + */ + public function column_exists( $name = '' ) { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + // Query statement + $query = "SHOW COLUMNS FROM {$this->table_name} LIKE %s"; + $like = $db->esc_like( $name ); + $prepared = $db->prepare( $query, $like ); + $result = $db->query( $prepared ); + + // Does the column exist? + return $this->is_success( $result ); + } + + /** + * Check if index already exists. + * + * @since 1.0.0 + * + * @param string $name Value + * @param string $column Column name + * + * @return bool + */ + public function index_exists( $name = '', $column = 'Key_name' ) { + + // Get the database interface + $db = $this->get_db(); + + // Bail if no database interface is available + if ( empty( $db ) ) { + return false; + } + + $column = esc_sql( $column ); + + // Query statement + $query = "SHOW INDEXES FROM {$this->table_name} WHERE {$column} LIKE %s"; + $like = $db->esc_like( $name ); + $prepared = $db->prepare( $query, $like ); + $result = $db->query( $prepared ); + + // Does the index exist? + return $this->is_success( $result ); + } + + /** Upgrades **************************************************************/ + + /** + * Upgrade this database table. + * + * @since 1.0.0 + * + * @return bool + */ + public function upgrade() { + + // Get pending upgrades + $upgrades = $this->get_pending_upgrades(); + + // Bail if no upgrades + if ( empty( $upgrades ) ) { + $this->set_db_version(); + + // Return, without failure + return true; + } + + // Default result + $result = false; + + // Try to do the upgrades + foreach ( $upgrades as $version => $callback ) { + + // Do the upgrade + $result = $this->upgrade_to( $version, $callback ); + + // Bail if an error occurs, to avoid skipping upgrades + if ( ! $this->is_success( $result ) ) { + return false; + } + } + + // Success/fail + return $this->is_success( $result ); + } + + /** + * Return array of upgrades that still need to run. + * + * @since 1.1.0 + * + * @return array Array of upgrade callbacks, keyed by their db version. + */ + public function get_pending_upgrades() { + + // Default return value + $upgrades = array(); + + // Bail if no upgrades, or no database version to compare to + if ( empty( $this->upgrades ) || empty( $this->db_version ) ) { + return $upgrades; + } + + // Loop through all upgrades, and pick out the ones that need doing + foreach ( $this->upgrades as $version => $callback ) { + if ( true === version_compare( $version, $this->db_version, '>' ) ) { + $upgrades[ $version ] = $callback; + } + } + + // Return + return $upgrades; + } + + /** + * Upgrade to a specific database version. + * + * @since 1.0.0 + * + * @param mixed $version Database version to check if upgrade is needed + * @param string $callback Callback function or class method to call + * + * @return bool + */ + public function upgrade_to( $version = '', $callback = '' ) { + + // Bail if no upgrade is needed + if ( ! $this->needs_upgrade( $version ) ) { + return false; + } + + // Allow self-named upgrade callbacks + if ( empty( $callback ) ) { + $callback = $version; + } + + // Is the callback... callable? + $callable = $this->get_callable( $callback ); + + // Bail if no callable upgrade was found + if ( empty( $callable ) ) { + return false; + } + + // Do the upgrade + $result = call_user_func( $callable ); + $success = $this->is_success( $result ); + + // Bail if upgrade failed + if ( true !== $success ) { + return false; + } + + // Set the database version to this successful version + $this->set_db_version( $version ); + + // Return success + return true; + } + + /** Private ***************************************************************/ + + /** + * Setup the necessary table variables. + * + * @since 1.0.0 + */ + private function setup() { + + // Bail if no database interface is available + if ( ! $this->get_db() ) { + return; + } + + // Sanitize the database table name + $this->name = $this->sanitize_table_name( $this->name ); + + // Bail if database table name was garbage + if ( false === $this->name ) { + return; + } + + // Separator + $glue = '_'; + + // Setup the prefixed name + $this->prefixed_name = $this->apply_prefix( $this->name, $glue ); + + // Maybe create database key + if ( empty( $this->db_version_key ) ) { + $this->db_version_key = implode( + $glue, + array( + sanitize_key( $this->db_global ), + $this->prefixed_name, + 'version' + ) + ); + } + } + + /** + * Set this table up in the database interface. + * + * This must be done directly because the database interface does not + * have a common mechanism for manipulating them safely. + * + * @since 1.0.0 + */ + private function set_db_interface() { + + // Get the database once, to avoid duplicate function calls + $db = $this->get_db(); + + // Bail if no database + if ( empty( $db ) ) { + return; + } + + // Set variables for global tables + if ( $this->is_global() ) { + $site_id = 0; + $tables = 'ms_global_tables'; + + // Set variables for per-site tables + } else { + $site_id = null; + $tables = 'tables'; + } + + // Set the table prefix and prefix the table name + $this->table_prefix = $db->get_blog_prefix( $site_id ); + + // Get the prefixed table name + $prefixed_table_name = "{$this->table_prefix}{$this->prefixed_name}"; + + // Set the database interface + $db->{$this->prefixed_name} = $this->table_name = $prefixed_table_name; + + // Create the array if it does not exist + if ( ! isset( $db->{$tables} ) ) { + $db->{$tables} = array(); + } + + // Add the table to the global table array + $db->{$tables}[] = $this->prefixed_name; + + // Charset + if ( ! empty( $db->charset ) ) { + $this->charset_collation = "DEFAULT CHARACTER SET {$db->charset}"; + } + + // Collation + if ( ! empty( $db->collate ) ) { + $this->charset_collation .= " COLLATE {$db->collate}"; + } + } + + /** + * Set the database version for the table. + * + * @since 1.0.0 + * + * @param mixed $version Database version to set when upgrading/creating + */ + private function set_db_version( $version = '' ) { + + // If no version is passed during an upgrade, use the current version + if ( empty( $version ) ) { + $version = $this->version; + } + + // Update the DB version + $this->is_global() + ? update_network_option( get_main_network_id(), $this->db_version_key, $version ) + : update_option( $this->db_version_key, $version ); + + // Set the DB version + $this->db_version = $version; + } + + /** + * Get the table version from the database. + * + * @since 1.0.0 + */ + private function get_db_version() { + $this->db_version = $this->is_global() + ? get_network_option( get_main_network_id(), $this->db_version_key, 1 ) + : get_option( $this->db_version_key, 1 ); + } + + /** + * Delete the table version from the database. + * + * @since 1.0.0 + */ + private function delete_db_version() { + $this->db_version = $this->is_global() + ? delete_network_option( get_main_network_id(), $this->db_version_key ) + : delete_option( $this->db_version_key ); + } + + /** + * Add class hooks to the parent application actions. + * + * @since 1.0.0 + */ + private function add_hooks() { + + // Add table to the global database object + add_action( 'switch_blog', array( $this, 'switch_blog' ) ); + add_action( 'admin_init', array( $this, 'maybe_upgrade' ) ); + } + + /** + * Check if the current request is from some kind of test. + * + * This is primarily used to skip 'admin_init' and force-install tables. + * + * @since 1.0.0 + * + * @return bool + */ + private function is_testing() { + return edd_is_doing_unit_tests(); + } + + /** + * Check if table is global. + * + * @since 1.0.0 + * + * @return bool + */ + private function is_global() { + return ( true === $this->global ); + } + + /** + * Try to get a callable upgrade, with some magic to avoid needing to + * do this dance repeatedly inside subclasses. + * + * @since 1.0.0 + * + * @param string $callback + * + * @return mixed Callable string, or false if not callable + */ + private function get_callable( $callback = '' ) { + + // Default return value + $callable = $callback; + + // Look for global function + if ( ! is_callable( $callable ) ) { + + // Fallback to local class method + $callable = array( $this, $callback ); + if ( ! is_callable( $callable ) ) { + + // Fallback to class method prefixed with "__" + $callable = array( $this, "__{$callback}" ); + if ( ! is_callable( $callable ) ) { + $callable = false; + } + } + } + + // Return callable string, or false if not callable + return $callable; + } +} diff --git a/includes/database/queries/class-adjustment.php b/includes/database/queries/class-adjustment.php new file mode 100644 index 00000000000..b354afab2c8 --- /dev/null +++ b/includes/database/queries/class-adjustment.php @@ -0,0 +1,170 @@ +parse_query_for_emails( $query ); + + return parent::query( $query ); + } + + /** + * If we are querying for an email, this queries the email addresses table first, + * then updates the queries with the matching customer IDs. + * + * @since 3.1.1 + * @param array $query + * @return array + */ + private function parse_query_for_emails( $query ) { + if ( empty( $query['email'] ) && empty( $query['email__in'] ) && empty( $query['email__not_in'] ) ) { + return $query; + } + $operator = 'id__in'; + $args = array( + 'fields' => array( 'customer_id' ), + ); + if ( ! empty( $query['email'] ) ) { + $args['email'] = $query['email']; + unset( $query['email'] ); + } elseif ( ! empty( $query['email__in'] ) ) { + $args['email__in'] = $query['email__in']; + unset( $query['email__in'] ); + } elseif ( ! empty( $query['email__not_in'] ) ) { + /** + * Speical treatment for email__not_in + * + * When we are searching for email__not_in, we want to actually find any email + * addresses in the customer email addresses table that match the email__not_in parameter + * find the customer IDs for these excluded email addresses, and pass them back in as id__not_in + * to the main query, so that we can ensure we're excluding them even if their main email value is + * one of the excluded ones. + */ + $operator = 'id__not_in'; + $args['email__in'] = $query['email__not_in']; + } + + $customer_ids = edd_get_customer_email_addresses( $args ); + $query[ $operator ] = array_map( 'absint', wp_list_pluck( $customer_ids, 'customer_id' ) ); + + return $query; + } +} diff --git a/includes/database/queries/class-log-api-request.php b/includes/database/queries/class-log-api-request.php new file mode 100644 index 00000000000..94832f0d029 --- /dev/null +++ b/includes/database/queries/class-log-api-request.php @@ -0,0 +1,156 @@ + $status ) { + if ( 'publish' === $status ) { + unset( $query['status'][ $key ] ); + } + } + + $query['status'][] = 'complete'; + } elseif ( 'publish' === $query['status'] ) { + $query['status'] = 'complete'; + } + } + + parent::__construct( $query ); + } + + /** + * Set up the filter callback to add the country and region from the order addresses table. + * + * @since 3.0 + * @access public + * + * @param string|array $query See Order::__construct() for accepted arguments. + * + * @see Order::__construct() + */ + public function query( $query = array() ) { + $query_clauses_filters = $this->get_query_clauses_filters( $query ); + foreach ( $query_clauses_filters as $filter ) { + if ( $filter['condition'] ) { + add_filter( 'edd_orders_query_clauses', array( $this, $filter['callback'] ) ); + } + } + + $result = parent::query( $query ); + + foreach ( $query_clauses_filters as $filter ) { + if ( $filter['condition'] ) { + remove_filter( 'edd_orders_query_clauses', array( $this, $filter['callback'] ) ); + } + } + + return $result; + } + + /** + * Filter the query clause to add the country and region from the order addresses table. + * + * @since 3.0 + * @access public + * + * @param string|array $clauses The clauses which will generate the final SQL query. + */ + public function query_by_country( $clauses ) { + + if ( empty( $this->query_vars['country'] ) || 'all' === $this->query_vars['country'] ) { + return $clauses; + } + + global $wpdb; + + $primary_alias = $this->table_alias; + $primary_column = parent::get_primary_column_name(); + + $order_addresses_query = new \EDD\Database\Queries\Order_Address(); + $join_alias = $order_addresses_query->table_alias; + + // Filter by the order address's region (state/province/etc).. + if ( ! empty( $this->query_vars['region'] ) && 'all' !== $this->query_vars['region'] ) { + $location_join = $wpdb->prepare( + " INNER JOIN {$order_addresses_query->table_name} {$join_alias} ON ({$primary_alias}.{$primary_column} = {$join_alias}.order_id AND {$join_alias}.country = %s AND {$join_alias}.region = %s)", + $this->query_vars['country'], + $this->query_vars['region'] + ); + + // Add the region to the query var defaults. + $this->query_var_defaults['region'] = $this->query_vars['region']; + + // Filter only by the country, not by region. + } else { + $location_join = $wpdb->prepare( + " INNER JOIN {$order_addresses_query->table_name} {$join_alias} ON ({$primary_alias}.{$primary_column} = {$join_alias}.order_id AND {$join_alias}.country = %s)", + $this->query_vars['country'] + ); + + // Add the country to the query var defaults. + $this->query_var_defaults['country'] = $this->query_vars['country']; + } + + // Add the customized join to the query. + $clauses['join'] .= ' ' . $location_join; + + return $clauses; + } + + /** + * Filter the query clause to filter by product ID. + * + * @since 3.0 + * @access public + * + * @param string|array $clauses The clauses which will generate the final SQL query. + */ + public function query_by_product( $clauses ) { + if ( + empty( $this->query_vars['product_id'] ) && + ( ! isset( $this->query_vars['product_price_id'] ) || ! is_numeric( $this->query_vars['product_price_id'] ) ) + ) { + return $clauses; + } + + global $wpdb; + + $primary_column = parent::get_primary_column_name(); + $order_items_query = new Order_Item(); + + // Build up our conditions. + $conditions = array(); + foreach ( array( 'product_id' => 'product_id', 'product_price_id' => 'price_id' ) as $query_var => $db_col ) { + if ( isset( $this->query_vars[ $query_var ] ) && is_numeric( $this->query_vars[ $query_var ] ) ) { + $conditions[] = $wpdb->prepare( + "AND {$order_items_query->table_alias}.{$db_col} = %d", + absint( $this->query_vars[ $query_var ] ) + ); + } + } + + $conditions = implode( ' ', $conditions ); + + $clauses['join'] .= " INNER JOIN {$order_items_query->table_name} {$order_items_query->table_alias} ON( + {$this->table_alias}.{$primary_column} = {$order_items_query->table_alias}.order_id + {$conditions} + )"; + + return $clauses; + } + + /** + * Filter the query clause to filter by transaction ID. + * + * @since 3.0.2 + * @param string $clauses + * @return string + */ + public function query_by_txn( $clauses ) { + if ( empty( $this->query_vars['txn'] ) ) { + return $clauses; + } + + global $wpdb; + + $primary_column = parent::get_primary_column_name(); + $order_transaction_query = new Order_Transaction(); + + $clauses['join'] .= $wpdb->prepare( + " INNER JOIN {$order_transaction_query->table_name} {$order_transaction_query->table_alias} + ON( {$this->table_alias}.{$primary_column} = {$order_transaction_query->table_alias}.object_id + AND {$order_transaction_query->table_alias}.transaction_id = %s )", + sanitize_text_field( $this->query_vars['txn'] ) + ); + + return $clauses; + } + + /** + * Filter the query clause to filter by discount ID. + * + * @since 3.0.2 + * @param string $clauses + * @return string + */ + public function query_by_discount_id( $clauses ) { + if ( empty( $this->query_vars['discount_id'] ) ) { + return $clauses; + } + + global $wpdb; + + $primary_column = parent::get_primary_column_name(); + $order_adjustment_query = new Order_Adjustment(); + + $clauses['join'] .= $wpdb->prepare( + " INNER JOIN {$order_adjustment_query->table_name} {$order_adjustment_query->table_alias} + ON( {$this->table_alias}.{$primary_column} = {$order_adjustment_query->table_alias}.object_id + AND {$order_adjustment_query->table_alias}.type_id = %d )", + absint( $this->query_vars['discount_id'] ) + ); + + return $clauses; + } + + /** + * When searching by a numeric order number, we need to override the default where clause + * to return orders matching either the ID or order number. + * + * @since 3.1.1.4 + * @param array $clauses + * @return array + */ + public function query_by_order_search( $clauses ) { + global $wpdb; + $clauses['where'] = $wpdb->prepare( + "{$this->table_alias}.id = %d OR {$this->table_alias}.order_number = %d", + absint( $this->query_vars['id'] ), + absint( $this->query_vars['order_number'] ) + ); + + return $clauses; + } + + /** + * Set the query var defaults for country and region. + * + * @since 3.0 + * @access public + */ + protected function set_query_var_defaults() { + parent::set_query_var_defaults(); + + $this->query_var_defaults['country'] = false; + $this->query_var_defaults['region'] = false; + $this->query_var_defaults['product_id'] = false; + $this->query_var_defaults['product_product_id'] = false; + } + + /** + * Adds an item to the database + * + * @since 3.0 + * + * @param array $data + * @return false|int Returns the item ID on success; false on failure. + */ + public function add_item( $data = array() ) { + // Every order should have a currency assigned. + if ( empty( $data['currency'] ) ) { + $data['currency'] = edd_get_currency(); + } + + // If the payment key isn't already created, generate it. + if ( empty( $data['payment_key'] ) ) { + $email = ! empty( $data['email'] ) ? $data['email'] : ''; + $data['payment_key'] = edd_generate_order_payment_key( $email ); + } + + // Add the IP address if it hasn't been already. + if ( empty( $data['ip'] ) ) { + $data['ip'] = edd_get_ip(); + } + + return parent::add_item( $data ); + } + + /** + * Get the array of possible query clause filters. + * + * @since 3.0.2 + * @param array $query + * @return array + */ + private function get_query_clauses_filters( $query ) { + return array( + array( + 'condition' => ! empty( $query['country'] ), + 'callback' => 'query_by_country', + ), + array( + 'condition' => ! empty( $query['product_id'] ) || ( isset( $query['product_price_id'] ) && is_numeric( $query['product_price_id'] ) ), + 'callback' => 'query_by_product', + ), + array( + 'condition' => ! empty( $query['txn'] ), + 'callback' => 'query_by_txn', + ), + array( + 'condition' => ! empty( $query['discount_id'] ), + 'callback' => 'query_by_discount_id', + ), + array( + 'condition' => ! empty( $query['id'] ) && ! empty( $query['order_number'] ) && $query['id'] === $query['order_number'], + 'callback' => 'query_by_order_search', + ), + ); + } +} diff --git a/includes/database/rows/class-adjustment.php b/includes/database/rows/class-adjustment.php new file mode 100644 index 00000000000..44fc9b1b7b5 --- /dev/null +++ b/includes/database/rows/class-adjustment.php @@ -0,0 +1,33 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true, + ), + + // parent. + array( + 'name' => 'parent', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + 'transition' => true, + ), + + // name. + array( + 'name' => 'name', + 'type' => 'varchar', + 'length' => '200', + 'searchable' => true, + 'sortable' => true, + ), + + // code. + array( + 'name' => 'code', + 'type' => 'varchar', + 'length' => '50', + 'searchable' => true, + 'sortable' => true, + 'cache_key' => true, + ), + + // status. + array( + 'name' => 'status', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'draft', + 'sortable' => true, + 'transition' => true, + ), + + // type + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true, + 'transition' => true, + ), + + // scope. + array( + 'name' => 'scope', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true, + 'transition' => true, + ), + + // amount_type. + array( + 'name' => 'amount_type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true, + 'transition' => true, + ), + + // amount. + array( + 'name' => 'amount', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + ), + + // description. + array( + 'name' => 'description', + 'type' => 'longtext', + 'default' => '', + 'searchable' => true, + ), + + // max_uses. + array( + 'name' => 'max_uses', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + ), + + // use_count. + array( + 'name' => 'use_count', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + ), + + // once_per_customer. + array( + 'name' => 'once_per_customer', + 'type' => 'int', + 'length' => '1', + 'default' => '0', + ), + + // min_charge_amount. + array( + 'name' => 'min_charge_amount', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + ), + + // start_date. + array( + 'name' => 'start_date', + 'type' => 'datetime', + 'default' => null, + 'allow_null' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // end_date. + array( + 'name' => 'end_date', + 'type' => 'datetime', + 'default' => null, + 'allow_null' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // date_created. + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class. + 'created' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // date_modified. + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class. + 'modified' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // uuid. + array( + 'uuid' => true, + ), + ); +} diff --git a/includes/database/schemas/class-customer-addresses.php b/includes/database/schemas/class-customer-addresses.php new file mode 100644 index 00000000000..34ae3707df1 --- /dev/null +++ b/includes/database/schemas/class-customer-addresses.php @@ -0,0 +1,169 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // customer_id + array( + 'name' => 'customer_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0' + ), + + // is_primary + array( + 'name' => 'is_primary', + 'type' => 'tinyint', + 'length' => '1', + 'unsigned' => false, + 'default' => '0', + 'sortable' => true, + 'transition' => true, + ), + + // type + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'billing', + 'sortable' => true, + 'transition' => true + ), + + // status + array( + 'name' => 'status', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'active', + 'sortable' => true, + 'transition' => true + ), + + // name + array( + 'name' => 'name', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // address + array( + 'name' => 'address', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // address2 + array( + 'name' => 'address2', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // city + array( + 'name' => 'city', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // region + array( + 'name' => 'region', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // postal_code + array( + 'name' => 'postal_code', + 'type' => 'varchar', + 'length' => '32', + 'default' => '', + 'searchable' => true, + 'sortable' => true + ), + + // country + array( + 'name' => 'country', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-customer-email-addresses.php b/includes/database/schemas/class-customer-email-addresses.php new file mode 100644 index 00000000000..76747d57fdc --- /dev/null +++ b/includes/database/schemas/class-customer-email-addresses.php @@ -0,0 +1,112 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // customer_id + array( + 'name' => 'customer_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'cache_key' => true + ), + + // type + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'secondary', + 'sortable' => true, + 'transition' => true + ), + + // status + array( + 'name' => 'status', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'active', + 'sortable' => true, + 'transition' => true + ), + + // email + array( + 'name' => 'email', + 'type' => 'varchar', + 'length' => '100', + 'default' => '', + 'cache_key' => true, + 'searchable' => true, + 'sortable' => true + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-customers.php b/includes/database/schemas/class-customers.php new file mode 100644 index 00000000000..a0224819c6b --- /dev/null +++ b/includes/database/schemas/class-customers.php @@ -0,0 +1,128 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // user_id + array( + 'name' => 'user_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'cache_key' => true + ), + + // email + array( + 'name' => 'email', + 'type' => 'varchar', + 'length' => '100', + 'cache_key' => true, + 'searchable' => true, + 'sortable' => true + ), + + // name + array( + 'name' => 'name', + 'type' => 'varchar', + 'length' => '255', + 'searchable' => true, + 'sortable' => true + ), + + // status + array( + 'name' => 'status', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'active', + 'sortable' => true, + 'transition' => true + ), + + // purchase_value + array( + 'name' => 'purchase_value', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true + ), + + // purchase_count + array( + 'name' => 'purchase_count', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-logs-api-requests.php b/includes/database/schemas/class-logs-api-requests.php new file mode 100644 index 00000000000..87284cfbeeb --- /dev/null +++ b/includes/database/schemas/class-logs-api-requests.php @@ -0,0 +1,148 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // user_id + array( + 'name' => 'user_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // api_key + array( + 'name' => 'api_key', + 'type' => 'varchar', + 'length' => '32', + 'default' => 'public', + 'searchable' => true, + 'sortable' => true + ), + + // token + array( + 'name' => 'token', + 'type' => 'varchar', + 'length' => '32', + 'default' => '', + 'searchable' => true, + 'sortable' => true + ), + + // version + array( + 'name' => 'version', + 'type' => 'varchar', + 'length' => '32', + 'default' => '', + 'sortable' => true + ), + + // request + array( + 'name' => 'request', + 'type' => 'longtext', + 'default' => '', + 'searchable' => true, + 'in' => false, + 'not_in' => false + ), + + // error + array( + 'name' => 'error', + 'type' => 'longtext', + 'default' => '', + 'searchable' => true, + 'in' => false, + 'not_in' => false + ), + + // ip + array( + 'name' => 'ip', + 'type' => 'varchar', + 'length' => '60', + 'default' => '', + 'searchable' => true, + 'sortable' => true + ), + + // time + array( + 'name' => 'time', + 'type' => 'varchar', + 'length' => '60', + 'default' => '', + 'sortable' => true + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-logs-file-downloads.php b/includes/database/schemas/class-logs-file-downloads.php new file mode 100644 index 00000000000..38ea17c3ea3 --- /dev/null +++ b/includes/database/schemas/class-logs-file-downloads.php @@ -0,0 +1,139 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // product_id + array( + 'name' => 'product_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // file_id + array( + 'name' => 'file_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // order_id + array( + 'name' => 'order_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // price_id + array( + 'name' => 'price_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0' + ), + + // customer_id + array( + 'name' => 'customer_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // ip + array( + 'name' => 'ip', + 'type' => 'varchar', + 'length' => '60', + 'default' => '', + 'sortable' => true, + 'searchable' => true + ), + + // user_agent + array( + 'name' => 'user_agent', + 'type' => 'varchar', + 'length' => '200', + 'default' => '', + 'sortable' => true, + 'searchable' => true, + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-logs.php b/includes/database/schemas/class-logs.php new file mode 100644 index 00000000000..ba156c4d20b --- /dev/null +++ b/includes/database/schemas/class-logs.php @@ -0,0 +1,134 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // object_id + array( + 'name' => 'object_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + 'cache_key' => true, + ), + + // object_type + array( + 'name' => 'object_type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true, + 'cache_key' => true, + 'allow_null' => true + ), + + // user_id + array( + 'name' => 'user_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + 'cache_key' => true, + ), + + // type + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true + ), + + // title + array( + 'name' => 'title', + 'type' => 'varchar', + 'length' => '200', + 'default' => '', + 'searchable' => true, + 'sortable' => true, + 'in' => false, + 'not_in' => false + ), + + // content + array( + 'name' => 'content', + 'type' => 'longtext', + 'default' => '', + 'searchable' => true, + 'in' => false, + 'not_in' => false + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-notes.php b/includes/database/schemas/class-notes.php new file mode 100644 index 00000000000..c74208baca2 --- /dev/null +++ b/includes/database/schemas/class-notes.php @@ -0,0 +1,109 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // object_id + array( + 'name' => 'object_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // object_type + array( + 'name' => 'object_type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true + ), + + // user_id + array( + 'name' => 'user_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // content + array( + 'name' => 'content', + 'type' => 'longtext', + 'default' => '', + 'searchable' => true, + 'in' => false, + 'not_in' => false + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-order-addresses.php b/includes/database/schemas/class-order-addresses.php new file mode 100644 index 00000000000..0347dda3a1d --- /dev/null +++ b/includes/database/schemas/class-order-addresses.php @@ -0,0 +1,148 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // order_id + array( + 'name' => 'order_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0' + ), + + // type. + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'billing', + 'sortable' => true, + 'transition' => true, + ), + + // name + array( + 'name' => 'name', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // address + array( + 'name' => 'address', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // address2 + array( + 'name' => 'address2', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // city + array( + 'name' => 'city', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // region + array( + 'name' => 'region', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // postal_code + array( + 'name' => 'postal_code', + 'type' => 'varchar', + 'length' => '32', + 'default' => '', + 'searchable' => true, + 'sortable' => true + ), + + // country + array( + 'name' => 'country', + 'type' => 'mediumtext', + 'searchable' => true, + 'sortable' => true + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-order-adjustments.php b/includes/database/schemas/class-order-adjustments.php new file mode 100644 index 00000000000..587c3b62a77 --- /dev/null +++ b/includes/database/schemas/class-order-adjustments.php @@ -0,0 +1,178 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // parent + array( + 'name' => 'parent', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // object_id + array( + 'name' => 'object_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // object_type + array( + 'name' => 'object_type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true + ), + + // type_id + array( + 'name' => 'type_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => null, + 'sortable' => true, + 'allow_null' => true, + ), + + // type + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true, + 'transition' => true + ), + + // type key + array( + 'name' => 'type_key', + 'type' => 'varchar', + 'length' => '255', + 'default' => null, + 'allow_null' => true, + 'sortable' => true, + ), + + // description + array( + 'name' => 'description', + 'type' => 'varchar', + 'length' => '100', + 'default' => '', + 'searchable' => true, + 'sortable' => true + ), + + // subtotal + array( + 'name' => 'subtotal', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // tax + array( + 'name' => 'tax', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // total + array( + 'name' => 'total', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // rate + array( + 'name' => 'rate', + 'type' => 'decimal', + 'length' => '10,5', + 'default' => '1.00000', + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-order-items.php b/includes/database/schemas/class-order-items.php new file mode 100644 index 00000000000..5b34d50efa8 --- /dev/null +++ b/includes/database/schemas/class-order-items.php @@ -0,0 +1,219 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // parent + array( + 'name' => 'parent', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // order_id + array( + 'name' => 'order_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // product_id + array( + 'name' => 'product_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // product_name + array( + 'name' => 'product_name', + 'type' => 'text', + 'default' => '', + 'searchable' => true, + 'in' => false, + 'not_in' => false + ), + + // price_id + array( + 'name' => 'price_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => null, + 'sortable' => true, + 'allow_null' => true, + ), + + // cart_index + array( + 'name' => 'cart_index', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // type + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'download', + 'sortable' => true, + 'transition' => true + ), + + // status + array( + 'name' => 'status', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'pending', + 'sortable' => true, + 'transition' => true + ), + + // quantity + array( + 'name' => 'quantity', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // amount + array( + 'name' => 'amount', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // subtotal + array( + 'name' => 'subtotal', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // discount + array( + 'name' => 'discount', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // tax + array( + 'name' => 'tax', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // total + array( + 'name' => 'total', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount' + ), + + // rate + array( + 'name' => 'rate', + 'type' => 'decimal', + 'length' => '10,5', + 'default' => '1.00000', + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-order-transactions.php b/includes/database/schemas/class-order-transactions.php new file mode 100644 index 00000000000..4119d08716a --- /dev/null +++ b/includes/database/schemas/class-order-transactions.php @@ -0,0 +1,134 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true + ), + + // object_id + array( + 'name' => 'object_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true + ), + + // object_type + array( + 'name' => 'object_type', + 'type' => 'varchar', + 'length' => '20', + 'default' => '', + 'sortable' => true + ), + + // transaction_id + array( + 'name' => 'transaction_id', + 'type' => 'varchar', + 'length' => '256', + 'cache_key' => true, + 'searchable' => true, + 'sortable' => true + ), + + // gateway + array( + 'name' => 'gateway', + 'type' => 'varchar', + 'length' => '20', + 'sortable' => true, + ), + + // status + array( + 'name' => 'status', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'pending', + 'sortable' => true, + 'transition' => true + ), + + // total + array( + 'name' => 'total', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true + ), + + // rate + array( + 'name' => 'rate', + 'type' => 'decimal', + 'length' => '10,5', + 'default' => '1.00000', + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'created' => true, + 'date_query' => true, + 'sortable' => true + ), + + // date_modified + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'modified' => true, + 'date_query' => true, + 'sortable' => true + ), + + // uuid + array( + 'uuid' => true, + ) + ); +} diff --git a/includes/database/schemas/class-orders.php b/includes/database/schemas/class-orders.php new file mode 100644 index 00000000000..e0cfbc239e7 --- /dev/null +++ b/includes/database/schemas/class-orders.php @@ -0,0 +1,256 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true, + ), + + // parent. + array( + 'name' => 'parent', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + ), + + // order_number. + array( + 'name' => 'order_number', + 'type' => 'varchar', + 'length' => '255', + 'searchable' => true, + 'sortable' => true, + ), + + // status. + array( + 'name' => 'status', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'pending', + 'sortable' => true, + 'transition' => true, + ), + + // type. + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '20', + 'default' => 'sale', + 'sortable' => true, + ), + + // user_id. + array( + 'name' => 'user_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + ), + + // customer_id. + array( + 'name' => 'customer_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => '0', + 'sortable' => true, + ), + + // email. + array( + 'name' => 'email', + 'type' => 'varchar', + 'length' => '100', + 'searchable' => true, + 'sortable' => true, + ), + + // ip. + array( + 'name' => 'ip', + 'type' => 'varchar', + 'length' => '60', + 'sortable' => true, + ), + + // gateway. + array( + 'name' => 'gateway', + 'type' => 'varchar', + 'length' => '100', + 'sortable' => true, + 'default' => 'manual', + ), + + // mode. + array( + 'name' => 'mode', + 'type' => 'varchar', + 'length' => '20', + ), + + // currency. + array( + 'name' => 'currency', + 'type' => 'varchar', + 'length' => '20', + 'validate' => 'strtoupper', + ), + + // payment_key. + array( + 'name' => 'payment_key', + 'type' => 'varchar', + 'length' => '64', + 'searchable' => true, + ), + + // tax_rate_id. + array( + 'name' => 'tax_rate_id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'default' => null, + 'allow_null' => true, + 'sortable' => true, + ), + + // subtotal. + array( + 'name' => 'subtotal', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount', + ), + + // discount. + array( + 'name' => 'discount', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount', + ), + + // tax. + array( + 'name' => 'tax', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount', + ), + + // total. + array( + 'name' => 'total', + 'type' => 'decimal', + 'length' => '18,9', + 'default' => '0', + 'sortable' => true, + 'validate' => 'edd_sanitize_amount', + ), + + // rate. + array( + 'name' => 'rate', + 'type' => 'decimal', + 'length' => '10,5', + 'default' => '1.00000', + ), + + // date_created. + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class. + 'created' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // date_modified. + array( + 'name' => 'date_modified', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class. + 'modified' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // date_completed. + array( + 'name' => 'date_completed', + 'type' => 'datetime', + 'default' => null, + 'allow_null' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // date_refundable. + array( + 'name' => 'date_refundable', + 'type' => 'datetime', + 'default' => null, + 'allow_null' => true, + 'date_query' => true, + 'sortable' => true, + ), + + // uuid. + array( + 'uuid' => true, + ), + ); +} diff --git a/includes/database/tables/class-adjustment-meta.php b/includes/database/tables/class-adjustment-meta.php new file mode 100644 index 00000000000..095507af597 --- /dev/null +++ b/includes/database/tables/class-adjustment-meta.php @@ -0,0 +1,68 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_adjustment_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_adjustment_id (edd_adjustment_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-adjustments.php b/includes/database/tables/class-adjustments.php new file mode 100644 index 00000000000..24f05d14a89 --- /dev/null +++ b/includes/database/tables/class-adjustments.php @@ -0,0 +1,206 @@ + 201906031, + '202002121' => 202002121, + '202102161' => 202102161 + ); + + /** + * Setup the database schema. + * + * @access protected + * @since 3.0 + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + parent bigint(20) unsigned NOT NULL default '0', + name varchar(200) NOT NULL default '', + code varchar(50) NOT NULL default '', + status varchar(20) NOT NULL default '', + type varchar(20) NOT NULL default '', + scope varchar(20) NOT NULL default 'all', + amount_type varchar(20) NOT NULL default '', + amount decimal(18,9) NOT NULL default '0', + description longtext NOT NULL default '', + max_uses bigint(20) unsigned NOT NULL default '0', + use_count bigint(20) unsigned NOT NULL default '0', + once_per_customer int(1) NOT NULL default '0', + min_charge_amount decimal(18,9) NOT NULL default '0', + start_date datetime default null, + end_date datetime default null, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY type_status (type(20), status(20)), + KEY code (code), + KEY date_created (date_created), + KEY date_start_end (start_date,end_date)"; + } + + /** + * Create the table + * + * @since 3.0 + * + * @return bool + */ + public function create() { + + $created = parent::create(); + + // After successful creation, we need to set the auto_increment for legacy orders. + if ( ! empty( $created ) ) { + + $result = $this->get_db()->get_var( "SELECT ID FROM {$this->get_db()->prefix}posts WHERE post_type = 'edd_discount' ORDER BY ID DESC LIMIT 1;" ); + + if ( ! empty( $result ) ) { + $auto_increment = $result + 1; + $this->get_db()->query( "ALTER TABLE {$this->table_name} AUTO_INCREMENT = {$auto_increment};" ); + } + + } + + return $created; + + } + + /** + * Upgrade to version 201906031 + * - Drop the `product_condition` column. + * + * @since 3.0 + * + * @return boolean True if upgrade was successful, false otherwise. + */ + protected function __201906031() { + + // Look for column + $result = $this->column_exists( 'product_condition' ); + + // Maybe remove column + if ( true === $result ) { + + // Try to remove it + $result = ! $this->get_db()->query( " + ALTER TABLE {$this->table_name} DROP COLUMN `product_condition` + " ); + + // Return success/fail + return $this->is_success( $result ); + + // Return true because column is already gone + } else { + return $this->is_success( true ); + } + } + + /** + * Upgrade to version 202002121 + * - Change default value to `null` for columns `start_date` and `end_date`. + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @return bool + */ + protected function __202002121() { + + // Update `start_date`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `start_date` datetime default null; + " ); + + if ( $this->is_success( $result ) ) { + $this->get_db()->query( "UPDATE {$this->table_name} SET `start_date` = NULL WHERE `start_date` = '0000-00-00 00:00:00'" ); + } + + // Update `end_date`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `end_date` datetime default null; + " ); + + if ( $this->is_success( $result ) ) { + $this->get_db()->query( "UPDATE {$this->table_name} SET `end_date` = NULL WHERE `end_date` = '0000-00-00 00:00:00'" ); + } + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + + /** + * Upgrade to version 202102161 + * - Drop old `code_status_type_scope_amount` index + * - Create new `status_type` index + * - Create new `code` index + * + * @since 3.0 + * @return bool + */ + protected function __202102161() { + if ( $this->index_exists( 'code_status_type_scope_amount' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} DROP INDEX code_status_type_scope_amount" ); + } + + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX type_status (type(20), status(20))" ); + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX code (code)" ); + + return true; + } +} diff --git a/includes/database/tables/class-customer-addresses.php b/includes/database/tables/class-customer-addresses.php new file mode 100644 index 00000000000..68b0e57f2b2 --- /dev/null +++ b/includes/database/tables/class-customer-addresses.php @@ -0,0 +1,149 @@ + 201906251, + '202002141' => 202002141, + '202004051' => 202004051, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + customer_id bigint(20) unsigned NOT NULL default '0', + is_primary tinyint(1) signed NOT NULL default '0', + type varchar(20) NOT NULL default 'billing', + status varchar(20) NOT NULL default 'active', + name mediumtext NOT NULL, + address mediumtext NOT NULL, + address2 mediumtext NOT NULL, + city mediumtext NOT NULL, + region mediumtext NOT NULL, + postal_code varchar(32) NOT NULL default '', + country mediumtext NOT NULL, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY customer_is_primary (customer_id, is_primary), + KEY type (type(20)), + KEY status (status(20)), + KEY date_created (date_created)"; + } + + /** + * Upgrade to version 201906251 + * - Add the `name` mediumtext column + * + * @since 3.0 + * + * @return boolean + */ + protected function __201906251() { + + $result = $this->column_exists( 'name' ); + + if ( false === $result ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN `name` mediumtext AFTER `status`; + " ); + } + + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + + /** + * Upgrade to version 202004051 + * - Update the customer physical address table to have `is_primary` + * + * @since 3.0 + * @return bool + */ + protected function __202004051() { + + $result = $this->column_exists( 'is_primary' ); + if ( false === $result ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN `is_primary` tinyint SIGNED NOT NULL default '0' AFTER `customer_id`; + " ); + } + + return $this->is_success( $result ); + } + +} diff --git a/includes/database/tables/class-customer-email-addresses.php b/includes/database/tables/class-customer-email-addresses.php new file mode 100644 index 00000000000..7e87081e742 --- /dev/null +++ b/includes/database/tables/class-customer-email-addresses.php @@ -0,0 +1,101 @@ + 202002141, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + customer_id bigint(20) unsigned NOT NULL default '0', + type varchar(20) NOT NULL default 'secondary', + status varchar(20) NOT NULL default 'active', + email varchar(100) NOT NULL default '', + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY customer (customer_id), + KEY email (email), + KEY type (type(20)), + KEY status (status(20)), + KEY date_created (date_created)"; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + +} diff --git a/includes/database/tables/class-customer-meta.php b/includes/database/tables/class-customer-meta.php new file mode 100644 index 00000000000..a46c2b74d51 --- /dev/null +++ b/includes/database/tables/class-customer-meta.php @@ -0,0 +1,131 @@ + 201807111 + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $max_index_length = 191; + $this->schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_customer_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_customer_id (edd_customer_id), + KEY meta_key (meta_key({$max_index_length}))"; + } + + /** + * Override the Base class `maybe_upgrade()` routine to do a very unique and + * special check against the old option. + * + * Maybe upgrades the database table from 2.x to 3.x standards. This method + * should be kept up-to-date with schema changes in `set_schema()` above. + * + * - Hooked to the "admin_init" action. + * - Calls the parent class `maybe_upgrade()` method + * + * @since 3.0 + */ + public function maybe_upgrade() { + + if ( $this->needs_initial_upgrade() ) { + + // Delete old/irrelevant database options. + delete_option( $this->table_prefix . 'edd_customermeta_db_version' ); + delete_option( 'wp_edd_customermeta_db_version' ); + + $this->get_db()->query( "ALTER TABLE {$this->table_name} CHANGE `customer_id` `edd_customer_id` bigint(20) unsigned NOT NULL default '0';" ); + $this->get_db()->query( "ALTER TABLE {$this->table_name} DROP INDEX customer_id" ); + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX edd_customer_id (edd_customer_id)" ); + } + + parent::maybe_upgrade(); + } + + /** + * Whether the initial upgrade from the 1.0 database needs to be run. + * + * @since 3.0.3 + * @return bool + */ + private function needs_initial_upgrade() { + return $this->exists() && $this->column_exists( 'customer_id' ) && ! $this->column_exists( 'edd_customer_id' ); + } + + /** + * Upgrade to version 201807111 + * - Rename `customer_id` column to `edd_customer_id` + * - Add `status` column. + * + * @since 3.0 + * + * @return bool + */ + protected function __201807111() { + + // Alter the database with separate queries so indexes succeed + if ( $this->column_exists( 'customer_id' ) && ! $this->column_exists( 'edd_customer_id' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} CHANGE `customer_id` `edd_customer_id` bigint(20) unsigned NOT NULL default '0'" ); + $this->get_db()->query( "ALTER TABLE {$this->table_name} DROP INDEX customer_id" ); + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX edd_customer_id (edd_customer_id)" ); + } + + // Return success/fail + return $this->is_success( true ); + } +} diff --git a/includes/database/tables/class-customers.php b/includes/database/tables/class-customers.php new file mode 100644 index 00000000000..672db177426 --- /dev/null +++ b/includes/database/tables/class-customers.php @@ -0,0 +1,265 @@ + 202006101, + '202301021' => 202301021, + '202303220' => 202303220, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + user_id bigint(20) unsigned NOT NULL default '0', + email varchar(100) NOT NULL default '', + name varchar(255) NOT NULL default '', + status varchar(20) NOT NULL default '', + purchase_value decimal(18,9) NOT NULL default '0', + purchase_count bigint(20) unsigned NOT NULL default '0', + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + UNIQUE KEY email (email), + KEY user (user_id), + KEY status (status(20)), + KEY date_created (date_created)"; + } + + /** + * Whether the initial upgrade from the 1.0 database needs to be run. + * + * @since 3.0.3 + * @since 3.1.1 Explicitly checks each of these cases to ensure a non-fully updated table is updated, but not currently used. + * @return bool + */ + private function needs_initial_upgrade() { + if ( $this->exists() && ! $this->column_exists( 'status' ) ) { + return true; + } + + if ( $this->exists() && ! $this->column_exists( 'uuid' ) ) { + return true; + } + + if ( $this->exists() && ! $this->column_exists( 'date_modified' ) ) { + return true; + } + + return false; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + + /** + * Upgrade to version 202006101 + * - Remove the payment_ids column if it still exists. + * + * @since 3.0 + * @return bool + */ + protected function __202006101() { + + $result = true; + + // Remove the column. + if ( $this->column_exists( 'payment_ids' ) ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} DROP `payment_ids` + " ); + } + + return $this->is_success( $result ); + } + + /** + * Checks the status of the edd_customers table for health and state and performs + * any necessary changes that haven't been prevoiusly made. + * + * @since 3.1.1 + * @return bool + */ + protected function __202301021() { + // Verify that columns that existed prior to 3.0 get the proper changes made. + $columns = $this->get_db()->get_results( "SHOW COLUMNS FROM {$this->table_name}" ); + $existing_column_updates = array(); + + // Set an array to hold the result of each query run. + $query_results = array(); + + foreach ( $columns as $column ) { + switch ( $column->Field ) { + case 'email': + if ( 'varchar(100)' !== $column->Type ) { + $existing_column_updates['alter-email-type'] = "ALTER TABLE {$this->table_name} MODIFY `email` varchar(100) NOT NULL default ''"; + } + break; + + case 'name': + if ( 'varchar(255)' !== $column->Type ) { + $existing_column_updates['alter-name-type'] = "ALTER TABLE {$this->table_name} MODIFY `name` varchar(255) NOT NULL default ''"; + } + break; + + case 'user_id': + if ( 'bigint(20)' !== $column->Type ) { + $existing_column_updates['alter-user_id-type'] = "ALTER TABLE {$this->table_name} MODIFY `user_id` bigint(20) unsigned NOT NULL default '0'"; + } + break; + + case 'purchase_value': + if ( 'decimal(18,9)' !== $column->Type ) { + $existing_column_updates['alter-purchase_value-type'] = "ALTER TABLE {$this->table_name} MODIFY `purchase_value` decimal(18,9) NOT NULL default '0'"; + } + break; + + case 'purchase_count': + if ( 'bigint(20)' !== $column->Type ) { + $existing_column_updates['alter-purchase_count-type'] = "ALTER TABLE {$this->table_name} MODIFY `purchase_count` bigint(20) unsigned NOT NULL default '0'"; + } + break; + + case 'date_created': + if ( 'datetime' !== $column->Type ) { + $existing_column_updates['alter-date_created-type'] = "ALTER TABLE {$this->table_name} MODIFY `date_created` datetime NOT NULL default CURRENT_TIMESTAMP"; + } + break; + + case 'payment_ids': + $existing_column_updates['drop-payment_ids'] = "ALTER TABLE {$this->table_name} DROP COLUMN `payment_ids`"; + break; + + case 'notes': + // Only remove the customer notes column if they've already run the customer notes migration. + if ( edd_has_upgrade_completed( 'v30_legacy_data_removed' ) ) { + $existing_column_updates['drop-notes'] = "ALTER TABLE {$this->table_name} DROP COLUMN `notes`"; + } + break; + } + } + + if ( ! empty( $existing_column_updates ) ) { + foreach ( $existing_column_updates as $query_key => $update_sql ) { + $query_results[ $query_key ] = $this->is_success( $this->get_db()->query( $update_sql ) ); + } + } + + // Now verify that new columns are created and exist. + if ( ! $this->column_exists( 'status' ) ) { + $query_results['add-status-column'] = $this->is_success( $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD COLUMN `status` varchar(20) NOT NULL default 'active' AFTER `name`;" ) ); + $query_results['index-status'] = $this->is_success( $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX status (status(20))" ) ); + } + + if ( ! $this->column_exists( 'date_modified' ) ) { + $query_results['add-date_modified-column'] = $this->is_success( $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD COLUMN `date_modified` datetime DEFAULT CURRENT_TIMESTAMP AFTER `date_created`" ) ); + $query_results['update-modified-with-created'] = $this->is_success( $this->get_db()->query( "UPDATE {$this->table_name} SET `date_modified` = `date_created`" ) ); + $query_results['index-date_created'] = $this->is_success( $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX date_created (date_created)" ) ); + } + + if ( ! $this->column_exists( 'uuid' ) ) { + $query_results['add-uuid-column'] = $this->is_success( $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD COLUMN `uuid` varchar(100) default '' AFTER `date_modified`;" ) ); + } + + $return_result = true; + + // Loop through each of the query results and force a debug log for any failures. + foreach ( $query_results as $query_key => $query_result ) { + if ( false === $query_result ) { + $return_result = false; + edd_debug_log( 'Customer\'s table version ' . $this->version . ' update failed for: ' . $query_key, true ); + } + } + + delete_option( $this->table_prefix . 'edd_customers_db_version' ); + delete_option( 'wp_edd_customers_db_version' ); + + return $this->is_success( $return_result ); + } + + /** + * Upgrades the customer database for sites which got into a bit of a snarl with the database versions. + * + * @since 3.1.1.3 + * @return bool + */ + protected function __202303220() { + if ( $this->needs_initial_upgrade() ) { + return $this->__202301021(); + } + + return true; + } +} diff --git a/includes/database/tables/class-log-meta.php b/includes/database/tables/class-log-meta.php new file mode 100644 index 00000000000..926300a9e69 --- /dev/null +++ b/includes/database/tables/class-log-meta.php @@ -0,0 +1,60 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_log_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_log_id (edd_log_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-logs-api-request-meta.php b/includes/database/tables/class-logs-api-request-meta.php new file mode 100644 index 00000000000..7a1fd20428a --- /dev/null +++ b/includes/database/tables/class-logs-api-request-meta.php @@ -0,0 +1,60 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_logs_api_request_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_logs_api_request_id (edd_logs_api_request_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-logs-api-requests.php b/includes/database/tables/class-logs-api-requests.php new file mode 100644 index 00000000000..0b40bf0a9a7 --- /dev/null +++ b/includes/database/tables/class-logs-api-requests.php @@ -0,0 +1,102 @@ + 202002141, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + user_id bigint(20) unsigned NOT NULL default '0', + api_key varchar(32) NOT NULL default 'public', + token varchar(32) NOT NULL default '', + version varchar(32) NOT NULL default '', + request longtext NOT NULL default '', + error longtext NOT NULL default '', + ip varchar(60) NOT NULL default '', + time varchar(60) NOT NULL default '', + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY user_id (user_id), + KEY date_created (date_created)"; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + +} diff --git a/includes/database/tables/class-logs-file-download-meta.php b/includes/database/tables/class-logs-file-download-meta.php new file mode 100644 index 00000000000..011fce67fb1 --- /dev/null +++ b/includes/database/tables/class-logs-file-download-meta.php @@ -0,0 +1,60 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_logs_file_download_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_logs_file_download_id (edd_logs_file_download_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-logs-file-downloads.php b/includes/database/tables/class-logs-file-downloads.php new file mode 100644 index 00000000000..2c26ed9d88f --- /dev/null +++ b/includes/database/tables/class-logs-file-downloads.php @@ -0,0 +1,102 @@ + 202002141, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + product_id bigint(20) unsigned NOT NULL default '0', + file_id bigint(20) unsigned NOT NULL default '0', + order_id bigint(20) unsigned NOT NULL default '0', + price_id bigint(20) unsigned NOT NULL default '0', + customer_id bigint(20) unsigned NOT NULL default '0', + ip varchar(60) NOT NULL default '', + user_agent varchar(200) NOT NULL default '', + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY customer_id (customer_id), + KEY product_id (product_id), + KEY date_created (date_created)"; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + +} diff --git a/includes/database/tables/class-logs.php b/includes/database/tables/class-logs.php new file mode 100644 index 00000000000..faf06487cc9 --- /dev/null +++ b/includes/database/tables/class-logs.php @@ -0,0 +1,101 @@ + 202002141, + ); + + /** + * Setup the database schema. + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + object_id bigint(20) unsigned NOT NULL default '0', + object_type varchar(20) DEFAULT NULL, + user_id bigint(20) unsigned NOT NULL default '0', + type varchar(20) DEFAULT NULL, + title varchar(200) DEFAULT NULL, + content longtext DEFAULT NULL, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY object_id_type (object_id,object_type(20)), + KEY user_id (user_id), + KEY type (type(20)), + KEY date_created (date_created)"; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } +} diff --git a/includes/database/tables/class-note-meta.php b/includes/database/tables/class-note-meta.php new file mode 100644 index 00000000000..aca2ecc1d42 --- /dev/null +++ b/includes/database/tables/class-note-meta.php @@ -0,0 +1,60 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_note_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_note_id (edd_note_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-notes.php b/includes/database/tables/class-notes.php new file mode 100644 index 00000000000..9ecbdbbf626 --- /dev/null +++ b/includes/database/tables/class-notes.php @@ -0,0 +1,99 @@ + 202002141, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + object_id bigint(20) unsigned NOT NULL default '0', + object_type varchar(20) NOT NULL default '', + user_id bigint(20) unsigned NOT NULL default '0', + content longtext NOT NULL default '', + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY object_id_type (object_id,object_type(20)), + KEY user_id (user_id), + KEY date_created (date_created)"; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + +} diff --git a/includes/database/tables/class-order-addresses.php b/includes/database/tables/class-order-addresses.php new file mode 100644 index 00000000000..a32036d1ca9 --- /dev/null +++ b/includes/database/tables/class-order-addresses.php @@ -0,0 +1,174 @@ + 201906251, + '201906281' => 201906281, + '202002141' => 202002141, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $max_index_length = 191; + $this->schema = "id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + order_id bigint(20) unsigned NOT NULL default '0', + type varchar(20) NOT NULL default 'billing', + name mediumtext NOT NULL, + address mediumtext NOT NULL, + address2 mediumtext NOT NULL, + city mediumtext NOT NULL, + region mediumtext NOT NULL, + postal_code varchar(32) NOT NULL default '', + country mediumtext NOT NULL, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY order_id (order_id), + KEY city (city({$max_index_length})), + KEY region (region({$max_index_length})), + KEY postal_code (postal_code(32)), + KEY country (country({$max_index_length})), + KEY date_created (date_created)"; + } + + /** + * Upgrade to version 201906251 + * - Adds the 'name' column + * - Combines the `first_name` and `last_name` columns to the `name` column. + * - Removes the `first_name` and `last_name` columns. + * + * @since 3.0 + * + * @return boolean + */ + protected function __201906251() { + + $success = true; + + $column_exists = $this->column_exists( 'name' ); + + // Don't take any action if the column already exists. + if ( false === $column_exists ) { + $column_exists = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN `name` mediumtext NOT NULL AFTER `last_name`; + " ); + + } + + $deprecated_columns_exist = ( $this->column_exists( 'first_name' ) && $this->column_exists( 'last_name' ) ); + if ( $column_exists && $deprecated_columns_exist ) { + $data_merged = $this->get_db()->query( " + UPDATE {$this->table_name} SET name = CONCAT(first_name, ' ', last_name); + " ); + + if ( $data_merged ) { + $success = $this->get_db()->query( " + ALTER TABLE {$this->table_name} DROP first_name, DROP last_name; + " ); + } + } + + return $this->is_success( $success ); + } + + /** + * Upgrade to version 201906281 + * - Add the `type` varchar column + * + * @since 3.0 + * + * @return boolean + */ + protected function __201906281() { + + // Look for column. + $result = $this->column_exists( 'type' ); + + // Maybe add column. + if ( false === $result ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN `type` varchar(20) default 'billing' AFTER `order_id`; + " ); + } + + // Return success/fail. + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + +} diff --git a/includes/database/tables/class-order-adjustment-meta.php b/includes/database/tables/class-order-adjustment-meta.php new file mode 100644 index 00000000000..149c30e6058 --- /dev/null +++ b/includes/database/tables/class-order-adjustment-meta.php @@ -0,0 +1,60 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_order_adjustment_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_order_adjustment_id (edd_order_adjustment_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-order-adjustments.php b/includes/database/tables/class-order-adjustments.php new file mode 100644 index 00000000000..18224bdfacc --- /dev/null +++ b/includes/database/tables/class-order-adjustments.php @@ -0,0 +1,187 @@ + 202002141, + '202011122' => 202011122, + '202103151' => 202103151, + '202105221' => 202105221, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + parent bigint(20) unsigned NOT NULL default '0', + object_id bigint(20) unsigned NOT NULL default '0', + object_type varchar(20) DEFAULT NULL, + type_id bigint(20) unsigned DEFAULT NULL, + type varchar(20) DEFAULT NULL, + type_key varchar(255) DEFAULT NULL, + description varchar(100) DEFAULT NULL, + subtotal decimal(18,9) NOT NULL default '0', + tax decimal(18,9) NOT NULL default '0', + total decimal(18,9) NOT NULL default '0', + rate decimal(10,5) NOT NULL DEFAULT 1.00000, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY object_id_type (object_id,object_type(20)), + KEY date_created (date_created), + KEY parent (parent)"; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + + /** + * Upgrade to version 202011122 + * - Change default value to `NULL` for `type_id` column. + * - Add `type_key` column. + * + * @since 3.0 + * @return bool + */ + protected function __202011122() { + + // Update `type_id`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `type_id` bigint(20) default NULL; + " ); + + // Add `type_key`. + $column_exists = $this->column_exists( 'type_key' ); + if ( false === $column_exists ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN `type_key` varchar(255) default NULL AFTER `type`; + " ); + } else { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY `type_key` varchar(255) default NULL AFTER `type` + " ); + } + + // Change `type_id` with `0` value to `null` to support new default. + $this->get_db()->query( "UPDATE {$this->table_name} SET type_id = null WHERE type_id = 0;" ); + + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202103151 + * - Add column `parent` + * - Add index on `parent` column. + * + * @since 3.0 + * @return bool + */ + protected function __202103151() { + // Look for column + $result = $this->column_exists( 'parent' ); + + // Maybe add column + if ( false === $result ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN parent bigint(20) unsigned NOT NULL default '0' AFTER id; + " ); + } + + if ( ! $this->index_exists( 'parent' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX parent (parent)" ); + } + + // Return success/fail. + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202105221 + * - Add `rate` column. + * + * @since 3.0 + * @return bool + */ + protected function __202105221() { + if ( ! $this->column_exists( 'rate' ) ) { + return $this->is_success( + $this->get_db()->query( + "ALTER TABLE {$this->table_name} ADD COLUMN rate decimal(10,5) NOT NULL DEFAULT 1.00000 AFTER total" + ) + ); + } + + return true; + } +} diff --git a/includes/database/tables/class-order-item-meta.php b/includes/database/tables/class-order-item-meta.php new file mode 100644 index 00000000000..4d4358d4a78 --- /dev/null +++ b/includes/database/tables/class-order-item-meta.php @@ -0,0 +1,60 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_order_item_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_order_item_id (edd_order_item_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-order-items.php b/includes/database/tables/class-order-items.php new file mode 100644 index 00000000000..4d128454e4a --- /dev/null +++ b/includes/database/tables/class-order-items.php @@ -0,0 +1,209 @@ + 201906241, + '202002141' => 202002141, + '202102010' => 202102010, + '202103151' => 202103151, + '202105221' => 202105221, + '202110141' => 202110141, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + parent bigint(20) unsigned NOT NULL default '0', + order_id bigint(20) unsigned NOT NULL default '0', + product_id bigint(20) unsigned NOT NULL default '0', + product_name text NOT NULL default '', + price_id bigint(20) unsigned default null, + cart_index bigint(20) unsigned NOT NULL default '0', + type varchar(20) NOT NULL default 'download', + status varchar(20) NOT NULL default 'pending', + quantity int signed NOT NULL default '0', + amount decimal(18,9) NOT NULL default '0', + subtotal decimal(18,9) NOT NULL default '0', + discount decimal(18,9) NOT NULL default '0', + tax decimal(18,9) NOT NULL default '0', + total decimal(18,9) NOT NULL default '0', + rate decimal(10,5) NOT NULL DEFAULT 1.00000, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY order_product_price_id (order_id,product_id,price_id), + KEY type_status (type(20),status(20)), + KEY parent (parent)"; + } + + /** + * Upgrade to version 201906241 + * - Make the quantity column signed so it can contain negative numbers. + * - Switch the quantity column from bigint to int for storage optimization. + * + * @since 3.0 + * + * @return bool + */ + protected function __201906241() { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY `quantity` int signed NOT NULL default '0'; + " ); + + // Return success/fail + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + + /** + * Upgrade to version 202102010. + * - Change default value for `status` column to 'pending'. + * + * @return bool + */ + protected function __202102010() { + // Update `status`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `status` varchar(20) NOT NULL default 'pending'; + " ); + + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202103151 + * - Add column `parent` + * - Add index on `parent` column. + * + * @since 3.0 + * @return bool + */ + protected function __202103151() { + // Look for column + $result = $this->column_exists( 'parent' ); + + // Maybe add column + if ( false === $result ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN parent bigint(20) unsigned NOT NULL default '0' AFTER id; + " ); + } + + if ( ! $this->index_exists( 'parent' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX parent (parent)" ); + } + + // Return success/fail. + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202105221 + * - Add `rate` column. + * + * @since 3.0 + * @return bool + */ + protected function __202105221() { + if ( ! $this->column_exists( 'rate' ) ) { + return $this->is_success( + $this->get_db()->query( + "ALTER TABLE {$this->table_name} ADD COLUMN rate decimal(10,5) NOT NULL DEFAULT 1.00000 AFTER total" + ) + ); + } + + return true; + } + + /** + * Upgrade to version 202110141 + * - Change default value for `price_id` to `null`. + * + * @since 3.0 + * @return bool + */ + protected function __202110141() { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN price_id bigint(20) unsigned default null; + " ); + + return $this->is_success( $result ); + } + +} diff --git a/includes/database/tables/class-order-meta.php b/includes/database/tables/class-order-meta.php new file mode 100644 index 00000000000..58b9aaa1715 --- /dev/null +++ b/includes/database/tables/class-order-meta.php @@ -0,0 +1,60 @@ +schema = "meta_id bigint(20) unsigned NOT NULL auto_increment, + edd_order_id bigint(20) unsigned NOT NULL default '0', + meta_key varchar(255) DEFAULT NULL, + meta_value longtext DEFAULT NULL, + PRIMARY KEY (meta_id), + KEY edd_order_id (edd_order_id), + KEY meta_key (meta_key({$max_index_length}))"; + } +} diff --git a/includes/database/tables/class-order-transactions.php b/includes/database/tables/class-order-transactions.php new file mode 100644 index 00000000000..3ba4d8a2677 --- /dev/null +++ b/includes/database/tables/class-order-transactions.php @@ -0,0 +1,161 @@ + 202002141, + '202005261' => 202005261, + '202105291' => 202105291, + '202205241' => 202205241, + ); + + /** + * Setup the database schema + * + * @access protected + * @since 3.0 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + object_id bigint(20) unsigned NOT NULL default '0', + object_type varchar(20) NOT NULL default '', + transaction_id varchar(256) NOT NULL default '', + gateway varchar(20) NOT NULL default '', + status varchar(20) NOT NULL default '', + total decimal(18,9) NOT NULL default '0', + rate decimal(10,5) NOT NULL DEFAULT 1.00000, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY transaction_id (transaction_id(64)), + KEY gateway (gateway(20)), + KEY status (status(20)), + KEY date_created (date_created), + KEY object_type_object_id (object_type, object_id)"; + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + return $this->is_success( $result ); + + } + + /** + * Upgrade to version 202005261 + * - Changed the column length from 64 to 256 in order to account for future updates to gateway data. + * + * @since 3.0 + * + * @return bool + */ + protected function __202005261() { + + // Increase the transaction_id column. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `transaction_id` varchar(256) NOT NULL default ''; + " ); + + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202105291 + * - Add `rate` column. + * + * @since 3.0 + * @return bool + */ + protected function __202105291() { + if ( ! $this->column_exists( 'rate' ) ) { + return $this->is_success( + $this->get_db()->query( + "ALTER TABLE {$this->table_name} ADD COLUMN rate decimal(10,5) NOT NULL DEFAULT 1.00000 AFTER total" + ) + ); + } + + return true; + } + + /** + * Upgrade to version 202205241 + * - Add combined index for object_type, object_id. + * + * @since 3.0 + * @return bool + */ + protected function __202205241() { + if ( $this->index_exists( 'object_type_object_id' ) ) { + return true; + } + + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX object_type_object_id (object_type, object_id)" ); + + return true; + } + +} diff --git a/includes/database/tables/class-orders.php b/includes/database/tables/class-orders.php new file mode 100644 index 00000000000..9b83940fbdb --- /dev/null +++ b/includes/database/tables/class-orders.php @@ -0,0 +1,304 @@ + 201901111, + '202002141' => 202002141, + '202012041' => 202012041, + '202102161' => 202102161, + '202103261' => 202103261, + '202105221' => 202105221, + '202108041' => 202108041, + '202302241' => 202302241, + ); + + /** + * Setup the database schema. + * + * @access protected + * @since 3.0 + */ + protected function set_schema() { + $max_index_length = 191; + $this->schema = "id bigint(20) unsigned NOT NULL auto_increment, + parent bigint(20) unsigned NOT NULL default '0', + order_number varchar(255) NOT NULL default '', + status varchar(20) NOT NULL default 'pending', + type varchar(20) NOT NULL default 'sale', + user_id bigint(20) unsigned NOT NULL default '0', + customer_id bigint(20) unsigned NOT NULL default '0', + email varchar(100) NOT NULL default '', + ip varchar(60) NOT NULL default '', + gateway varchar(100) NOT NULL default 'manual', + mode varchar(20) NOT NULL default '', + currency varchar(20) NOT NULL default '', + payment_key varchar(64) NOT NULL default '', + tax_rate_id bigint(20) DEFAULT NULL, + subtotal decimal(18,9) NOT NULL default '0', + discount decimal(18,9) NOT NULL default '0', + tax decimal(18,9) NOT NULL default '0', + total decimal(18,9) NOT NULL default '0', + rate decimal(10,5) NOT NULL DEFAULT 1.00000, + date_created datetime NOT NULL default CURRENT_TIMESTAMP, + date_modified datetime NOT NULL default CURRENT_TIMESTAMP, + date_completed datetime default null, + date_refundable datetime default null, + uuid varchar(100) NOT NULL default '', + PRIMARY KEY (id), + KEY order_number (order_number({$max_index_length})), + KEY status_type (status, type), + KEY user_id (user_id), + KEY customer_id (customer_id), + KEY email (email(100)), + KEY payment_key (payment_key(64)), + KEY date_created_completed (date_created,date_completed), + KEY currency (currency)"; + } + + /** + * Create the table + * + * @since 3.0 + * + * @return bool + */ + public function create() { + + $created = parent::create(); + + // After successful creation, we need to set the auto_increment for legacy orders. + if ( ! empty( $created ) ) { + + $last_payment_id = $this->get_db()->get_var( "SELECT ID FROM {$this->get_db()->prefix}posts WHERE post_type = 'edd_payment' ORDER BY ID DESC LIMIT 1;" ); + + if ( ! empty( $last_payment_id ) ) { + update_option( 'edd_v3_migration_pending', $last_payment_id, false ); + $auto_increment = $last_payment_id + 1; + $this->get_db()->query( "ALTER TABLE {$this->table_name} AUTO_INCREMENT = {$auto_increment};" ); + } + + } + + return $created; + + } + + /** + * Upgrade to version 201901111 + * - Set any 'publish' status items to 'complete'. + * + * @since 3.0 + * + * @return boolean + */ + protected function __201901111() { + $this->get_db()->query( " + UPDATE {$this->table_name} set `status` = 'complete' WHERE `status` = 'publish'; + " ); + + return $this->is_success( true ); + } + + /** + * Upgrade to version 202002141 + * - Change default value to `CURRENT_TIMESTAMP` for columns `date_created` and `date_modified`. + * - Change default value to `null` for columns `date_completed` and `date_refundable`. + * + * @since 3.0 + * @return bool + */ + protected function __202002141() { + + // Update `date_created`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_created` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_modified`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_modified` datetime NOT NULL default CURRENT_TIMESTAMP; + " ); + + // Update `date_completed`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_completed` datetime default null; + " ); + + if ( $this->is_success( $result ) ) { + $this->get_db()->query( "UPDATE {$this->table_name} SET `date_completed` = NULL WHERE `date_completed` = '0000-00-00 00:00:00'" ); + } + + // Update `date_refundable`. + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `date_refundable` datetime default null; + " ); + + if ( $this->is_success( $result ) ) { + $this->get_db()->query( "UPDATE {$this->table_name} SET `date_refundable` = NULL WHERE `date_refundable` = '0000-00-00 00:00:00'" ); + } + + return $this->is_success( $result ); + + } + + /** + * Upgrade to version 202012041 + * - Add column `tax_rate_id` + * + * @since 3.0 + * @return bool + */ + protected function __202012041() { + // Look for column + $result = $this->column_exists( 'tax_rate_id' ); + + // Maybe add column + if ( false === $result ) { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} ADD COLUMN tax_rate_id bigint(20) DEFAULT NULL AFTER payment_key; + " ); + } + + // Return success/fail. + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202102161 + * - Drop `status` index + * - Create new `status_type` index + * + * @since 3.0 + * @return bool + */ + protected function __202102161() { + if ( $this->index_exists( 'status' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} DROP INDEX status" ); + } + + if ( ! $this->index_exists( 'status_type' ) ) { + $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX status_type (status, type)" ); + } + + return true; + } + + /** + * Upgrade to version 202103261 + * - Change length of `gateway` column to `100`. + * + * @since 3.0 + * @return bool + */ + protected function __202103261() { + $result = $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `gateway` varchar(100) NOT NULL default ''; + " ); + + return $this->is_success( $result ); + } + + /** + * Upgrade to version 202105221 + * - Add `rate` column. + * + * @since 3.0 + * @return bool + */ + protected function __202105221() { + if ( ! $this->column_exists( 'rate' ) ) { + return $this->is_success( + $this->get_db()->query( + "ALTER TABLE {$this->table_name} ADD COLUMN rate decimal(10,5) NOT NULL DEFAULT 1.00000 AFTER total" + ) + ); + } + + return true; + } + + /** + * Upgrade to version 202108041 + * - Set any empty gateway items to 'manual'. + * + * @since 3.0 + * + * @return boolean + */ + protected function __202108041() { + $this->get_db()->query( " + UPDATE {$this->table_name} set `gateway` = 'manual' WHERE `gateway` = ''; + " ); + + $this->get_db()->query( " + ALTER TABLE {$this->table_name} MODIFY COLUMN `gateway` varchar(100) NOT NULL default 'manual'; + " ); + + return true; + } + + /** + * Upgrade to version 202302241 + * - Set an index for the 'currency' column as we use that in the admin frequenly. + * + * @since 3.1.1 + * + * @return boolean + */ + protected function __202302241() { + + if ( ! $this->index_exists( 'currency' ) ) { + $success = $this->get_db()->query( "ALTER TABLE {$this->table_name} ADD INDEX currency (currency)" ); + } else { + $success = true; + } + + return $this->is_success( $success ); + } +} diff --git a/includes/date-functions.php b/includes/date-functions.php new file mode 100644 index 00000000000..7703467c26e --- /dev/null +++ b/includes/date-functions.php @@ -0,0 +1,297 @@ +utils->date( 'now', edd_get_timezone_id(), false ); + + return date_i18n( $format, (int) $timestamp + $date->getOffset() ); +} + +/** + * Retrieve timezone ID + * + * @since 1.6 + * @return string $timezone The timezone ID + */ +function edd_get_timezone_id() { + return EDD()->utils->get_time_zone( true ); +} + +/** + * Accept an EDD date object and get the UTC equivalent version of it. + * The EDD date object passed-in can be in any timezone. The one you'll get back will be the UTC equivalent of that time. + * This is useful when querying data from the tables by a user-defined date range, like "today". + * + * @since 3.0 + * @param EDD\Utils\Date $edd_date_object The EDD Date object for which you wish to get the UTC equiavalent. + * @return EDD\Utils\Date The EDD date object set at the UTC equivalent time. + */ +function edd_get_utc_equivalent_date( $edd_date_object ) { + + $instance_check = 'EDD\Utils\Date'; + if ( ! $edd_date_object instanceof $instance_check ) { + return false; + } + + // Convert the timezone (and thus, also the time) from the WP/EDD Timezone to the UTC equivalent. + $utc_timezone = new DateTimeZone( 'utc' ); + $edd_date_object->setTimezone( $utc_timezone ); + + return $edd_date_object; +} + +/** + * Accept an EDD date object set in UTC, and get the WP/EDD Timezone equivalent version of it. + * The EDD date object must be in UTC. The one you'll get back will be the WP timezone equivalent of that time. + * This is useful when showing date information to the user, so that they see it in the proper timezone, instead of UTC. + * + * @since 3.0 + * @param EDD\Utils\Date $edd_date_object The EDD Date object for which you wish to get the UTC equiavalent. + * @return EDD\Utils\Date The EDD date object set at the UTC equivalent time. + */ +function edd_get_edd_timezone_equivalent_date_from_utc( $edd_date_object ) { + + $instance_check = 'EDD\Utils\Date'; + if ( ! $edd_date_object instanceof $instance_check ) { + return false; + } + + // If you passed a date object to this function that isn't set to UTC, that is incorrect usage. + if ( 'UTC' !== $edd_date_object->format( 'T' ) ) { + return false; + } + + // Convert the timezone (and thus, also the time) from UTC to the WP/EDD Timezone. + $edd_timezone = new DateTimeZone( edd_get_timezone_id() ); + $edd_date_object->setTimezone( $edd_timezone ); + + return $edd_date_object; +} + +/** + * Get the timezone abbreviation for the WordPress timezone setting. + * + * @since 3.0 + * + * @return string The abreviation for the current WordPress timezone setting. + */ +function edd_get_timezone_abbr() { + $edd_timezone = edd_get_timezone_id(); + $edd_date_object = EDD()->utils->date( 'now', $edd_timezone, true ); + return $edd_date_object->format( 'T' ); +} + +/** + * Retrieves a date format string based on a given short-hand format. + * + * @since 3.0 + * + * @see \EDD_Utilities::get_date_format_string() + * + * @param string $format Shorthand date format string. Accepts 'date', 'time', 'mysql', or + * 'datetime'. If none of the accepted values, the original value will + * simply be returned. Default 'date' represents the value of the + * 'date_format' option. + * + * @return string date_format()-compatible date format string. + */ +function edd_get_date_format( $format = 'date' ) { + return EDD()->utils->get_date_format_string( $format ); +} + +/** + * Get the format used by jQuery UI Datepickers. + * + * Use this if you need to use placeholder or format attributes in input fields. + * + * This is a bit different than `edd_get_date_format()` because these formats + * are exposed to users as hints and also used by jQuery UI so the Datepicker + * knows what format it returns into it's connected input value. + * + * Previous to this function existing, all values were hard-coded, causing some + * inconsistencies across admin-area screens. + * + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/commit/e9855762892b6eec578b0a402f7950f22bd19632 + * + * @since 3.0 + * + * @param string $context The context we are getting the format for. Accepts 'display' or 'js'. + * Use 'js' for use with jQuery UI Datepicker. Use 'display' for HTML attributes. + * @return string + */ +function edd_get_date_picker_format( $context = 'display' ) { + + // What is the context that we are getting the picker format for? + switch ( $context ) { + + // jQuery UI Datepicker does its own thing + case 'js' : + case 'javascript' : + $retval = EDD()->utils->get_date_format_string( 'date-js' ); + break; + + // Used to display in an attribute, placeholder, etc... + case 'display' : + default : + $retval = EDD()->utils->get_date_format_string( 'date-attribute' ); + break; + } + + /** + * Filter the date picker format, allowing for custom overrides + * + * @since 3.0 + * + * @param string $retval Date format for date picker + * @param string $context The context this format is for + */ + return apply_filters( 'edd_get_date_picker_format', $retval, $context ); +} + +/** + * Return an array of values used to populate an hour dropdown + * + * @since 3.0 + * + * @return array + */ +function edd_get_hour_values() { + return (array) apply_filters( 'edd_get_hour_values', array( + '00' => '00', + '01' => '01', + '02' => '02', + '03' => '03', + '04' => '04', + '05' => '05', + '06' => '06', + '07' => '07', + '08' => '08', + '09' => '09', + '10' => '10', + '11' => '11', + '12' => '12', + '13' => '13', + '14' => '14', + '15' => '15', + '16' => '16', + '17' => '17', + '18' => '18', + '19' => '19', + '20' => '20', + '21' => '21', + '22' => '22', + '23' => '23', + '24' => '24' + ) ); +} + +/** + * Return an array of values used to populate a minute dropdown + * + * @since 3.0 + * + * @return array + */ +function edd_get_minute_values() { + return (array) apply_filters( 'edd_get_minute_values', array( + '00' => '00', + '01' => '01', + '02' => '02', + '03' => '03', + '04' => '04', + '05' => '05', + '06' => '06', + '07' => '07', + '08' => '08', + '09' => '09', + '10' => '10', + '11' => '11', + '12' => '12', + '13' => '13', + '14' => '14', + '15' => '15', + '16' => '16', + '17' => '17', + '18' => '18', + '19' => '19', + '20' => '20', + '21' => '21', + '22' => '22', + '23' => '23', + '24' => '24', + '25' => '25', + '26' => '26', + '27' => '27', + '28' => '28', + '29' => '29', + '30' => '30', + '31' => '31', + '32' => '32', + '33' => '33', + '34' => '34', + '35' => '35', + '36' => '36', + '37' => '37', + '38' => '38', + '39' => '39', + '40' => '40', + '41' => '41', + '42' => '42', + '43' => '43', + '44' => '44', + '45' => '45', + '46' => '46', + '47' => '47', + '48' => '48', + '49' => '49', + '50' => '50', + '51' => '51', + '52' => '52', + '53' => '53', + '54' => '54', + '55' => '55', + '56' => '56', + '57' => '57', + '58' => '58', + '59' => '59' + ) ); +} + +/** + * Gets the UTC equivalent of a date in the local timezone. + * + * @since 3.1.4 + * @param string $date_string The date string to convert. + * @param string $format The format to return the date in. + * @return string + */ +function edd_get_utc_date_string( $date_string = 'now', $format = 'Y-m-d H:i:s' ) { + return EDD()->utils->date( $date_string, edd_get_timezone_id(), false )->get_utc_from_local( $format ); +} diff --git a/includes/deprecated-functions.php b/includes/deprecated-functions.php old mode 100644 new mode 100755 index 0a3d6437a85..01b82a60ab5 --- a/includes/deprecated-functions.php +++ b/includes/deprecated-functions.php @@ -4,36 +4,2151 @@ * * All functions that have been deprecated. * - * @package Easy Digital Downloads - * @subpackage Deprecated Functions - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Deprecated + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 + * @since 1.0 + */ + +use EDD\Reports; + +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; + +/** + * Get Download Sales Log + * + * Returns an array of sales and sale info for a download. + * + * @since 1.0 + * @deprecated 1.3.4 + * + * @param int $download_id ID number of the download to retrieve a log for + * @param bool $paginate Whether to paginate the results or not + * @param int $number Number of results to return + * @param int $offset Number of items to skip + * + * @return mixed array|bool +*/ +function edd_get_download_sales_log( $download_id, $paginate = false, $number = 10, $offset = 0 ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.3.4', null, $backtrace ); + + $sales_log = get_post_meta( $download_id, '_edd_sales_log', true ); + + if ( $sales_log ) { + $sales_log = array_reverse( $sales_log ); + $log = array(); + $log['number'] = count( $sales_log ); + $log['sales'] = $sales_log; + + if ( $paginate ) { + $log['sales'] = array_slice( $sales_log, $offset, $number ); + } + + return $log; + } + + return false; +} + +/** + * Get Downloads Of Purchase + * + * Retrieves an array of all files purchased. + * + * @since 1.0 + * @deprecated 1.4 + * + * @param int $payment_id ID number of the purchase + * @param null $payment_meta + * @return bool|mixed + */ +function edd_get_downloads_of_purchase( $payment_id, $payment_meta = null ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.4', 'edd_get_payment_meta_downloads', $backtrace ); + + if ( is_null( $payment_meta ) ) { + $payment_meta = edd_get_payment_meta( $payment_id ); + } + + $downloads = maybe_unserialize( $payment_meta['downloads'] ); + + if ( $downloads ) { + return $downloads; + } + + return false; +} + +/** + * Get Menu Access Level + * + * Returns the access level required to access the downloads menu. Currently not + * changeable, but here for a future update. + * + * @since 1.0 + * @deprecated 1.4.4 + * @return string +*/ +function edd_get_menu_access_level() { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.4.4', 'current_user_can(\'manage_shop_settings\')', $backtrace ); + + return apply_filters( 'edd_menu_access_level', 'manage_options' ); +} + + + +/** + * Check if only local taxes are enabled meaning users must opt in by using the + * option set from the EDD Settings. + * + * @since 1.3.3 + * @deprecated 1.6 + * @global $edd_options + * @return bool $local_only + */ +function edd_local_taxes_only() { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.6', 'no alternatives', $backtrace ); + + global $edd_options; + + $local_only = isset( $edd_options['tax_condition'] ) && $edd_options['tax_condition'] == 'local'; + + return apply_filters( 'edd_local_taxes_only', $local_only ); +} + +/** + * Checks if a customer has opted into local taxes + * + * @since 1.4.1 + * @deprecated 1.6 + * @uses EDD_Session::get() + * @return bool + */ +function edd_local_tax_opted_in() { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.6', 'no alternatives', $backtrace ); + + $opted_in = EDD()->session->get( 'edd_local_tax_opt_in' ); + return ! empty( $opted_in ); +} + +/** + * Show taxes on individual prices? + * + * @since 1.4 + * @deprecated 1.9 + * @global $edd_options + * @return bool Whether or not to show taxes on prices + */ +function edd_taxes_on_prices() { + global $edd_options; + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.9', 'no alternatives', $backtrace ); + + return apply_filters( 'edd_taxes_on_prices', isset( $edd_options['taxes_on_prices'] ) ); +} + +/** + * Show Has Purchased Item Message + * + * Prints a notice when user has already purchased the item. + * + * @since 1.0 + * @deprecated 1.8 + * @global $user_ID + */ +function edd_show_has_purchased_item_message() { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.8', 'no alternatives', $backtrace ); + + global $user_ID, $post; + + if ( !isset( $post->ID ) ) { + return; + } + + if ( edd_has_user_purchased( $user_ID, $post->ID ) ) { + $alert = '

    ' . __( 'You have already purchased this item, but you may purchase it again.', 'easy-digital-downloads' ) . '

    '; + echo apply_filters( 'edd_show_has_purchased_item_message', $alert ); + } +} + +/** + * Flushes the total earning cache when a new payment is created + * + * @since 1.2 + * @deprecated 1.8.4 + * @param int $payment Payment ID + * @param array $payment_data Payment Data + * @return void + */ +function edd_clear_earnings_cache( $payment, $payment_data ) { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.8.4', 'no alternatives', $backtrace ); + + delete_transient( 'edd_total_earnings' ); +} +//add_action( 'edd_insert_payment', 'edd_clear_earnings_cache', 10, 2 ); + +/** + * Get Cart Amount + * + * @since 1.0 + * @deprecated 1.9 + * @param bool $add_taxes Whether to apply taxes (if enabled) (default: true) + * @param bool $local_override Force the local opt-in param - used for when not reading $_POST (default: false) + * @return float Total amount +*/ +function edd_get_cart_amount( $add_taxes = true, $local_override = false ) { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '1.9', 'edd_get_cart_subtotal() or edd_get_cart_total()', $backtrace ); + + $amount = edd_get_cart_subtotal( ); + if ( ! empty( $_POST['edd-discount'] ) || edd_get_cart_discounts() !== false ) { + // Retrieve the discount stored in cookies + $discounts = edd_get_cart_discounts(); + + // Check for a posted discount + $posted_discount = isset( $_POST['edd-discount'] ) ? trim( $_POST['edd-discount'] ) : ''; + + if ( $posted_discount && ! in_array( $posted_discount, $discounts ) ) { + // This discount hasn't been applied, so apply it + $amount = edd_get_discounted_amount( $posted_discount, $amount ); + } + + if ( ! empty( $discounts ) ) { + // Apply the discounted amount from discounts already applied + $amount -= edd_get_cart_discounted_amount(); + } + } + + if ( edd_use_taxes() && edd_is_cart_taxed() && $add_taxes ) { + $tax = edd_get_cart_tax(); + $amount += $tax; + } + + if ( $amount < 0 ) { + $amount = 0.00; + } + + return apply_filters( 'edd_get_cart_amount', $amount, $add_taxes, $local_override ); +} + +/** + * Get Purchase Receipt Template Tags + * + * Displays all available template tags for the purchase receipt. + * + * @since 1.6 + * @deprecated 1.9 + * @author Daniel J Griffiths + * @return string $tags + */ +function edd_get_purchase_receipt_template_tags() { + $tags = __('Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:','easy-digital-downloads' ) . '
    ' . + '{download_list} - ' . __('A list of download links for each download purchased','easy-digital-downloads' ) . '
    ' . + '{file_urls} - ' . __('A plain-text list of download URLs for each download purchased','easy-digital-downloads' ) . '
    ' . + '{name} - ' . __('The buyer\'s first name','easy-digital-downloads' ) . '
    ' . + '{fullname} - ' . __('The buyer\'s full name, first and last','easy-digital-downloads' ) . '
    ' . + '{username} - ' . __('The buyer\'s user name on the site, if they registered an account','easy-digital-downloads' ) . '
    ' . + '{user_email} - ' . __('The buyer\'s email address','easy-digital-downloads' ) . '
    ' . + '{billing_address} - ' . __('The buyer\'s billing address','easy-digital-downloads' ) . '
    ' . + '{date} - ' . __('The date of the purchase','easy-digital-downloads' ) . '
    ' . + '{subtotal} - ' . __('The price of the purchase before taxes','easy-digital-downloads' ) . '
    ' . + '{tax} - ' . __('The taxed amount of the purchase','easy-digital-downloads' ) . '
    ' . + '{price} - ' . __('The total price of the purchase','easy-digital-downloads' ) . '
    ' . + '{payment_id} - ' . __('The unique ID number for this purchase','easy-digital-downloads' ) . '
    ' . + '{receipt_id} - ' . __('The unique ID number for this purchase receipt','easy-digital-downloads' ) . '
    ' . + '{payment_method} - ' . __('The method of payment used for this purchase','easy-digital-downloads' ) . '
    ' . + '{sitename} - ' . __('Your site name','easy-digital-downloads' ) . '
    ' . + '{receipt_link} - ' . __( 'Adds a link so users can view their receipt directly on your website if they are unable to view it in the browser correctly.', 'easy-digital-downloads' ); + + return apply_filters( 'edd_purchase_receipt_template_tags_description', $tags ); +} + + +/** + * Get Sale Notification Template Tags + * + * Displays all available template tags for the sale notification email + * + * @since 1.7 + * @deprecated 1.9 + * @author Daniel J Griffiths + * @return string $tags + */ +function edd_get_sale_notification_template_tags() { + $tags = __( 'Enter the email that is sent to sale notification emails after completion of a purchase. HTML is accepted. Available template tags:', 'easy-digital-downloads' ) . '
    ' . + '{download_list} - ' . __('A list of download links for each download purchased','easy-digital-downloads' ) . '
    ' . + '{file_urls} - ' . __('A plain-text list of download URLs for each download purchased','easy-digital-downloads' ) . '
    ' . + '{name} - ' . __('The buyer\'s first name','easy-digital-downloads' ) . '
    ' . + '{fullname} - ' . __('The buyer\'s full name, first and last','easy-digital-downloads' ) . '
    ' . + '{username} - ' . __('The buyer\'s user name on the site, if they registered an account','easy-digital-downloads' ) . '
    ' . + '{user_email} - ' . __('The buyer\'s email address','easy-digital-downloads' ) . '
    ' . + '{billing_address} - ' . __('The buyer\'s billing address','easy-digital-downloads' ) . '
    ' . + '{date} - ' . __('The date of the purchase','easy-digital-downloads' ) . '
    ' . + '{subtotal} - ' . __('The price of the purchase before taxes','easy-digital-downloads' ) . '
    ' . + '{tax} - ' . __('The taxed amount of the purchase','easy-digital-downloads' ) . '
    ' . + '{price} - ' . __('The total price of the purchase','easy-digital-downloads' ) . '
    ' . + '{payment_id} - ' . __('The unique ID number for this purchase','easy-digital-downloads' ) . '
    ' . + '{receipt_id} - ' . __('The unique ID number for this purchase receipt','easy-digital-downloads' ) . '
    ' . + '{payment_method} - ' . __('The method of payment used for this purchase','easy-digital-downloads' ) . '
    ' . + '{sitename} - ' . __('Your site name','easy-digital-downloads' ); + + return apply_filters( 'edd_sale_notification_template_tags_description', $tags ); +} + +/** + * Email Template Header + * + * @access private + * @since 1.0.8.2 + * @deprecated 2.0 + * @return string Email template header + */ +function edd_get_email_body_header() { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '2.0', '', $backtrace ); + + ob_start(); + ?> + + + + + + + + + 'AND', + array( + 'key' => '_edd_payment_purchase_key', + 'value' => $key + ), + array( + 'key' => '_edd_payment_user_email', + 'value' => $email + ) + ); + + $accepted_stati = apply_filters( 'edd_allowed_download_stati', array( 'publish', 'complete' ) ); + + $payments = get_posts( array( 'meta_query' => $meta_query, 'post_type' => 'edd_payment', 'post_status' => $accepted_stati ) ); + + if ( $payments ) { + foreach ( $payments as $payment ) { + + $cart_details = edd_get_payment_meta_cart_details( $payment->ID, true ); + + if ( ! empty( $cart_details ) ) { + foreach ( $cart_details as $cart_key => $cart_item ) { + + if ( $cart_item['id'] != $download_id ) { + continue; + } + + $price_options = isset( $cart_item['item_number']['options'] ) ? $cart_item['item_number']['options'] : false; + $price_id = isset( $price_options['price_id'] ) ? $price_options['price_id'] : false; + + $file_condition = edd_get_file_price_condition( $cart_item['id'], $file_key ); + + // Check to see if the file download limit has been reached + if ( edd_is_file_at_download_limit( $cart_item['id'], $payment->ID, $file_key, $price_id ) ) { + wp_die( apply_filters( 'edd_download_limit_reached_text', __( 'Sorry but you have hit your download limit for this file.', 'easy-digital-downloads' ) ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // If this download has variable prices, we have to confirm that this file was included in their purchase + if ( ! empty( $price_options ) && $file_condition != 'all' && edd_has_variable_prices( $cart_item['id'] ) ) { + if ( $file_condition == $price_options['price_id'] ) { + return $payment->ID; + } + } + + // Make sure the link hasn't expired + + if ( base64_encode( base64_decode( $expire, true ) ) === $expire ) { + $expire = base64_decode( $expire ); // If it is a base64 string, decode it. Old expiration dates were in base64 + } + + if ( current_time( 'timestamp' ) > $expire ) { + wp_die( apply_filters( 'edd_download_link_expired_text', __( 'Sorry but your download link has expired.', 'easy-digital-downloads' ) ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + return $payment->ID; // Payment has been verified and link is still valid + } + } + } + + } else { + wp_die( __( 'No payments matching your request were found.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + // Payment not verified + return false; +} + +/** + * Get Success Page URL + * + * @param string $query_string + * @since 1.0 + * @deprecated 2.6 Please avoid usage of this function in favor of edd_get_success_page_uri() + * @return string +*/ +function edd_get_success_page_url( $query_string = null ) { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '2.6', 'edd_get_success_page_uri()', $backtrace ); + + return apply_filters( 'edd_success_page_url', edd_get_success_page_uri( $query_string ) ); +} + +/** + * Reduces earnings and sales stats when a purchase is refunded + * + * @since 1.8.2 + * @param int $payment_id the ID number of the payment + * @param string $new_status the status of the payment, probably "publish" + * @param string $old_status the status of the payment prior to being marked as "complete", probably "pending" + * @deprecated 2.5.7 Please avoid usage of this function in favor of refund() in EDD_Payment + * @internal param Arguments $data passed + */ +function edd_undo_purchase_on_refund( $payment_id, $new_status, $old_status ) { + + $backtrace = debug_backtrace(); + _edd_deprecated_function( 'edd_undo_purchase_on_refund', '2.5.7', 'EDD_Payment->refund()', $backtrace ); + + $payment = new EDD_Payment( $payment_id ); + $payment->refund(); +} + +/** + * Get Earnings By Date + * + * @since 1.0 + * @deprecated 2.7 + * @param int $day Day number + * @param int $month_num Month number + * @param int $year Year + * @param int $hour Hour + * @return int $earnings Earnings + */ +function edd_get_earnings_by_date( $day, $month_num = null, $year = null, $hour = null, $include_taxes = true ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '2.7', 'EDD_Payment_Stats()->get_earnings()', $backtrace ); + + global $wpdb; + + $args = array( + 'post_type' => 'edd_payment', + 'nopaging' => true, + 'year' => $year, + 'monthnum' => $month_num, + 'post_status' => array( 'publish', 'revoked' ), + 'fields' => 'ids', + 'include_taxes' => $include_taxes, + 'update_post_term_cache' => false, + ); + + if ( ! empty( $day ) ) { + $args['day'] = $day; + } + + if ( ! empty( $hour ) || $hour == 0 ) { + $args['hour'] = $hour; + } + + $args = apply_filters( 'edd_get_earnings_by_date_args', $args ); + $cached = get_transient( 'edd_stats_earnings' ); + $key = md5( json_encode( $args ) ); + + if ( ! isset( $cached[ $key ] ) ) { + $sales = get_posts( $args ); + $earnings = 0; + if ( $sales ) { + $sales = implode( ',', $sales ); + + $total_earnings = $wpdb->get_var( "SELECT SUM(meta_value) FROM $wpdb->postmeta WHERE meta_key = '_edd_payment_total' AND post_id IN ({$sales})" ); + $total_tax = 0; + + if ( ! $include_taxes ) { + $total_tax = $wpdb->get_var( "SELECT SUM(meta_value) FROM $wpdb->postmeta WHERE meta_key = '_edd_payment_tax' AND post_id IN ({$sales})" ); + } + + $earnings += ( $total_earnings - $total_tax ); + } + // Cache the results for one hour + $cached[ $key ] = $earnings; + set_transient( 'edd_stats_earnings', $cached, HOUR_IN_SECONDS ); + } + + $result = $cached[ $key ]; + + return round( $result, 2 ); +} + +/** + * Get Sales By Date + * + * @since 1.1.4.0 + * @deprecated 2.7 + * @author Sunny Ratilal + * @param int $day Day number + * @param int $month_num Month number + * @param int $year Year + * @param int $hour Hour + * @return int $count Sales + */ +function edd_get_sales_by_date( $day = null, $month_num = null, $year = null, $hour = null ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '2.7', 'EDD_Payment_Stats()->get_sales()', $backtrace ); + + $args = array( + 'post_type' => 'edd_payment', + 'nopaging' => true, + 'year' => $year, + 'fields' => 'ids', + 'post_status' => array( 'publish', 'revoked' ), + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false + ); + + $show_free = apply_filters( 'edd_sales_by_date_show_free', true, $args ); + + if ( false === $show_free ) { + $args['meta_query'] = array( + array( + 'key' => '_edd_payment_total', + 'value' => 0, + 'compare' => '>', + 'type' => 'NUMERIC', + ), + ); + } + + if ( ! empty( $month_num ) ) { + $args['monthnum'] = $month_num; + } + + if ( ! empty( $day ) ) { + $args['day'] = $day; + } + + if ( ! empty( $hour ) ) { + $args['hour'] = $hour; + } + + $args = apply_filters( 'edd_get_sales_by_date_args', $args ); + + $cached = get_transient( 'edd_stats_sales' ); + $key = md5( json_encode( $args ) ); + + if ( ! isset( $cached[ $key ] ) ) { + $sales = new WP_Query( $args ); + $count = (int) $sales->post_count; + + // Cache the results for one hour + $cached[ $key ] = $count; + set_transient( 'edd_stats_sales', $cached, HOUR_IN_SECONDS ); + } + + $result = $cached[ $key ]; + + return $result; +} + +/** + * Set the Page Style for PayPal Purchase page + * + * @since 1.4.1 + * @deprecated 2.8 + * @return string + */ +function edd_get_paypal_page_style() { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '2.8', 'edd_get_paypal_image_url', $backtrace ); + + $page_style = trim( edd_get_option( 'paypal_page_style', 'PayPal' ) ); + return apply_filters( 'edd_paypal_page_style', $page_style ); +} + +/** + * Should we add schema.org microdata? + * + * @since 1.7 + * @since 3.0 - Deprecated as the switch was made to JSON-LD. + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5240 + * + * @return bool + */ +function edd_add_schema_microdata() { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'EDD_Structured_Data', $backtrace ); + + // Don't modify anything until after wp_head() is called + $ret = (bool)did_action( 'wp_head' ); + return apply_filters( 'edd_add_schema_microdata', $ret ); +} + +/** + * Add Microdata to download titles + * + * @since 1.5 + * @since 3.0 - Deprecated as the switch was made to JSON-LD. + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5240 + * + * @param string $title Post Title + * @param int $id Post ID + * @return string $title New title + */ +function edd_microdata_title( $title, $id = 0 ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'EDD_Structured_Data', $backtrace ); + + global $post; + + if ( ! edd_add_schema_microdata() || ! is_object( $post ) ) { + return $title; + } + + if ( $post->ID == $id && is_singular( 'download' ) && 'download' == get_post_type( intval( $id ) ) ) { + $title = '' . $title . ''; + } + + return $title; +} + +/** + * Start Microdata to wrapper download + * + * @since 2.3 + * @since 3.0 - Deprecated as the switch was made to JSON-LD. + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5240 + * + * @return void + */ +function edd_microdata_wrapper_open( $query ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'EDD_Structured_Data', $backtrace ); + + static $microdata_open = NULL; + + if ( ! edd_add_schema_microdata() || true === $microdata_open || ! is_object( $query ) ) { + return; + } + + if ( $query && ! empty( $query->query['post_type'] ) && $query->query['post_type'] == 'download' && is_singular( 'download' ) && $query->is_main_query() ) { + $microdata_open = true; + echo '
    '; + } +} + +/** + * End Microdata to wrapper download + * + * @since 2.3 + * @since 3.0 - Deprecated as the switch was made to JSON-LD. + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5240 + * + * @return void + */ +function edd_microdata_wrapper_close() { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'EDD_Structured_Data', $backtrace ); + + global $post; + + static $microdata_close = NULL; + + if ( ! edd_add_schema_microdata() || true === $microdata_close || ! is_object( $post ) ) { + return; + } + + if ( $post && $post->post_type == 'download' && is_singular( 'download' ) && is_main_query() ) { + $microdata_close = true; + echo '
    '; + } +} + +/** + * Add Microdata to download description + * + * @since 1.5 + * @since 3.0 - Deprecated as the switch was made to JSON-LD. + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5240 + * + * @param $content + * @return mixed|void New title + */ +function edd_microdata_description( $content ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'EDD_Structured_Data', $backtrace ); + + global $post; + + static $microdata_description = NULL; + + if ( ! edd_add_schema_microdata() || true === $microdata_description || ! is_object( $post ) ) { + return $content; + } + + if ( $post && $post->post_type == 'download' && is_singular( 'download' ) && is_main_query() ) { + $microdata_description = true; + $content = apply_filters( 'edd_microdata_wrapper', '
    ' . $content . '
    ' ); + } + return $content; +} + +/** + * Output schema markup for single price products. + * + * @since 2.6.14 + * @since 3.0 - Deprecated as the switch was made to JSON-LD. + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5240 + * + * @param int $download_id The download being output. + * @return void + */ +function edd_purchase_link_single_pricing_schema( $download_id = 0, $args = array() ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'EDD_Structured_Data', $backtrace ); + + // Bail if the product has variable pricing, or if we aren't showing schema data. + if ( edd_has_variable_prices( $download_id ) || ! edd_add_schema_microdata() ) { + return; + } + + // Grab the information we need. + $download = new EDD_Download( $download_id ); + ?> + + + + + 403 ) ); + } + + $current_view = 'earnings'; + $views = edd_reports_default_views(); + + if ( isset( $_GET['view'] ) && array_key_exists( $_GET['view'], $views ) ) { + $current_view = $_GET['view']; + } + + /** + * Legacy: fired inside the old global 'Reports' tab. + * + * The dynamic portion of the hook name, `$current_view`, represented the parsed value of + * the 'view' query variable. + * + * @since 1.3 + * @deprecated 3.0 Unused. + */ + edd_do_action_deprecated( 'edd_reports_view_' . $current_view, array(), '3.0' ); + +} + +/** + * Default Report Views + * + * Checks the $_GET['view'] parameter to ensure it exists within the default allowed views. + * + * @param string $default Default view to use. + * + * @since 1.9.6 + * @deprecated 3.0 Unused. + * + * @return string $view Report View + */ +function edd_get_reporting_view( $default = 'earnings' ) { + + _edd_deprecated_function( __FUNCTION__, '3.0' ); + + if ( ! isset( $_GET['view'] ) || ! in_array( $_GET['view'], array_keys( edd_reports_default_views() ) ) ) { + $view = $default; + } else { + $view = $_GET['view']; + } + + /** + * Legacy: filters the current reporting view (now implemented solely via the 'tab' var). + * + * @since 1.9.6 + * @deprecated 3.0 Unused. + * + * @param string $view View slug. + */ + return edd_apply_filters_deprecated( 'edd_get_reporting_view', array( $view ), '3.0' ); +} + +/** + * Renders the Reports Page Views Drop Downs + * + * @since 1.3 + * @deprecated 3.0 Unused. + * + * @return void + */ +function edd_report_views() { + + _edd_deprecated_function( __FUNCTION__, '3.0' ); + + /** + * Legacy: fired before the view actions drop-down was output. + * + * @since 1.3 + * @deprecated 3.0 Unused. + */ + edd_do_action_deprecated( 'edd_report_view_actions', array(), '3.0' ); + + /** + * Legacy: fired after the view actions drop-down was output. + * + * @since 1.3 + * @deprecated 3.0 Unused. + */ + edd_do_action_deprecated( 'edd_report_view_actions_after', array(), '3.0' ); + + return; +} + +/** + * Show report graph date filters. + * + * @since 1.3 + * @deprecated 3.0 Unused. + */ +function edd_reports_graph_controls() { + _edd_deprecated_function( __FUNCTION__, 'EDD 3.0' ); +} + +/** + * Sets up the dates used to filter graph data + * + * Date sent via $_GET is read first and then modified (if needed) to match the + * selected date-range (if any) + * + * @since 1.3 + * @deprecated 3.0 Use \EDD\Reports\get_dates_filter() instead + * @see \EDD\Reports\get_dates_filter() + * + * @param string $timezone Optional. Timezone to force for report filter dates calculations. + * Default is the WP timezone. + * @return array Array of report filter dates. + */ +function edd_get_report_dates( $timezone = null ) { + + _edd_deprecated_function( __FUNCTION__, '3.0', '\EDD\Reports\get_dates_filter' ); + + Reports\Init::bootstrap(); + + add_filter( 'edd_get_dates_filter_range', '\EDD\Reports\compat_filter_date_range' ); + + $filter_dates = Reports\get_dates_filter( 'objects', $timezone ); + $range = Reports\get_dates_filter_range(); + + remove_filter( 'edd_get_report_dates_default_range', '\EDD\Reports\compat_filter_date_range' ); + + $dates = array( + 'range' => $range, + 'day' => $filter_dates['start']->format( 'd' ), + 'day_end' => $filter_dates['end']->format( 'd' ), + 'm_start' => $filter_dates['start']->month, + 'm_end' => $filter_dates['end']->month, + 'year' => $filter_dates['start']->year, + 'year_end' => $filter_dates['end']->year, + ); + + /** + * Filters the legacy list of parsed report dates for use in the Reports API. + * + * @since 1.3 + * @deprecated 3.0 + * + * @param array $dates Array of legacy date parts. + */ + return edd_apply_filters_deprecated( 'edd_report_dates', array( $dates ), '3.0' ); +} + +/** + * Intercept default Edit post links for EDD orders and rewrite them to the View Order Details screen. + * + * @since 1.8.3 + * @deprecated 3.0 No alternative present as get_post() does not work with orders. + * + * @param $url + * @param $post_id + * @param $context + * + * @return string + */ +function edd_override_edit_post_for_payment_link( $url = '', $post_id = 0, $context = '') { + _edd_deprecated_function( __FUNCTION__, '3.0', '' ); + + $post = get_post( $post_id ); + + if ( empty( $post ) ) { + return $url; + } + + if ( 'edd_payment' !== $post->post_type ) { + return $url; + } + + return edd_get_admin_url( array( + 'page' => 'edd-payment-history', + 'view' => 'view-order-details', + 'id' => absint( $post_id ), + ) ); +} + +/** + * Record sale as a log. + * + * Stores log information for a download sale. + * + * @since 1.0 + * @deprecated 3.0 Sales logs are no longed stored. + * + * @param int $download_id Download ID + * @param int $payment_id Payment ID. + * @param int $price_id Optional. Price ID. + * @param string $sale_date Optional. Date of the sale. + */ +function edd_record_sale_in_log( $download_id, $payment_id, $price_id = false, $sale_date = null ) { + _edd_deprecated_function( __FUNCTION__, '3.0' ); + + $edd_logs = EDD()->debug_log; + + $log_data = array( + 'post_parent' => $download_id, + 'log_type' => 'sale', + 'post_date' => ! empty( $sale_date ) ? $sale_date : null, + 'post_date_gmt' => ! empty( $sale_date ) ? get_gmt_from_date( $sale_date ) : null, + ); + + $log_meta = array( + 'payment_id' => $payment_id, + 'price_id' => (int) $price_id, + ); + + $edd_logs->insert_log( $log_data, $log_meta ); +} + +/** + * Outputs the JavaScript code for the Agree to Terms section to toggle + * the T&Cs text + * + * @since 1.0 + * @deprecated 3.0 Moved to external scripts in assets/js/frontend/checkout/components/agree-to-terms + */ +function edd_agree_to_terms_js() { + _edd_deprecated_function( __FUNCTION__, '3.0' ); +} + +/** + * Record payment status change + * + * @since 1.4.3 + * @deprecated since 3.0 + * @param int $payment_id the ID number of the payment. + * @param string $new_status the status of the payment, probably "publish". + * @param string $old_status the status of the payment prior to being marked as "complete", probably "pending". + * @return void + */ +function edd_record_status_change( $payment_id, $new_status, $old_status ) { + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'edd_record_order_status_change', $backtrace ); + + // Get the list of statuses so that status in the payment note can be translated + $stati = edd_get_payment_statuses(); + $old_status = isset( $stati[ $old_status ] ) ? $stati[ $old_status ] : $old_status; + $new_status = isset( $stati[ $new_status ] ) ? $stati[ $new_status ] : $new_status; + + $status_change = sprintf( __( 'Status changed from %s to %s', 'easy-digital-downloads' ), $old_status, $new_status ); + + edd_insert_payment_note( $payment_id, $status_change ); +} + +/** + * Shows checkbox to automatically refund payments made in PayPal. + * + * @deprecated 3.0 In favour of `edd_paypal_refund_checkbox()` + * @see edd_paypal_refund_checkbox() + * + * @since 2.6.0 + * + * @param int $payment_id The current payment ID. + * @return void + */ +function edd_paypal_refund_admin_js( $payment_id = 0 ) { + + $backtrace = debug_backtrace(); + + _edd_deprecated_function( __FUNCTION__, '3.0', 'edd_paypal_refund_checkbox', $backtrace ); + + // If not the proper gateway, return early. + if ( 'paypal' !== edd_get_payment_gateway( $payment_id ) ) { + return; + } + + // If our credentials are not set, return early. + $key = edd_get_payment_meta( $payment_id, '_edd_payment_mode', true ); + $username = edd_get_option( 'paypal_' . $key . '_api_username' ); + $password = edd_get_option( 'paypal_' . $key . '_api_password' ); + $signature = edd_get_option( 'paypal_' . $key . '_api_signature' ); + + if ( empty( $username ) || empty( $password ) || empty( $signature ) ) { + return; + } + + // Localize the refund checkbox label. + $label = __( 'Refund Payment in PayPal', 'easy-digital-downloads' ); + + ?> + + ID ) ) { + return; + } + + if ( empty( $_POST['edd-paypal-refund'] ) ) { + return; + } + + $processed = $payment->get_meta( '_edd_paypal_refunded', true ); + + // If the status is not set to "refunded", return early. + if ( 'complete' !== $payment->old_status && 'revoked' !== $payment->old_status ) { + return; + } + + // If not PayPal/PayPal Express, return early. + if ( 'paypal' !== $payment->gateway ) { + return; + } + + // If the payment has already been refunded in the past, return early. + if ( $processed ) { + return; + } + + // Process the refund in PayPal. + edd_refund_paypal_purchase( $payment ); +} + +/** + * Jilt Callback + * + * Renders Jilt Settings + * + * @deprecated 2.10.2 + * + * @param array $args arguments passed by the setting. + * @return void + */ +function edd_jilt_callback( $args ) { + + _edd_deprecated_function( __FUNCTION__, '2.10.2' ); + + $activated = is_callable( 'edd_jilt' ); + $connected = $activated && edd_jilt()->get_integration()->is_jilt_connected(); + $connect_url = $activated ? edd_jilt()->get_connect_url() : ''; + $account_url = $connected ? edd_jilt()->get_integration()->get_jilt_app_url() : ''; + + echo wp_kses_post( $args['desc'] ); + + if ( $activated ) : + ?> + + + +

    + +

    + +

    + tag, %2$s - tag */ + __( '%1$sClick here%2$s to visit your Jilt dashboard', 'easy-digital-downloads' ), + '', + '' + ) + ); + ?> +

    + + + +

    + + + +

    + + + + + +

    + +

    + + __( 'You do not have permission to do this.', 'easy-digital-downloads' ), + ) + ); + } + + include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + include_once ABSPATH . 'wp-admin/includes/file.php'; + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + $plugins = get_plugins(); + + if ( ! array_key_exists( 'jilt-for-edd/jilt-for-edd.php', $plugins ) ) { + /* + * Use the WordPress Plugins API to get the plugin download link. + */ + $api = plugins_api( + 'plugin_information', + array( + 'slug' => 'jilt-for-edd', + ) + ); + + if ( is_wp_error( $api ) ) { + wp_send_json_error( + array( + 'error' => $api->get_error_message(), + 'debug' => $api, + ) + ); + } + + /* + * Use the AJAX Upgrader skin to quietly install the plugin. + */ + $upgrader = new Plugin_Upgrader( new WP_Ajax_Upgrader_Skin() ); + $install = $upgrader->install( $api->download_link ); + if ( is_wp_error( $install ) ) { + wp_send_json_error( + array( + 'error' => $install->get_error_message(), + 'debug' => $api, + ) + ); + } + + activate_plugin( $upgrader->plugin_info() ); + + } else { + + activate_plugin( 'jilt-for-edd/jilt-for-edd.php' ); + } + + /* + * Final check to see if Jilt is available. + */ + if ( ! class_exists( 'EDD_Jilt_Loader' ) ) { + wp_send_json_error( + array( + 'error' => __( 'Something went wrong. Jilt was not installed correctly.', 'easy-digital-downloads' ), + ) + ); + } + + wp_send_json_success(); +} + +/** + * Handle connection for Jilt via AJAX + * + * @deprecated 2.10.2 + * @since n.n.n + */ +function edd_jilt_connect_handler() { + + _edd_deprecated_function( __FUNCTION__, '2.10.2' ); + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + wp_send_json_error( + array( + 'error' => __( 'You do not have permission to do this.', 'easy-digital-downloads' ), + ) + ); + } + + if ( ! is_callable( 'edd_jilt' ) ) { + wp_send_json_error( + array( + 'error' => __( 'Something went wrong. Jilt was not installed correctly.', 'easy-digital-downloads' ), + ) + ); + } + + wp_send_json_success( array( 'connect_url' => edd_jilt()->get_connect_url() ) ); +} + +/** + * Handle disconnection and deactivation for Jilt via AJAX + * + * @deprecated 2.10.2 + * @since n.n.n + */ +function edd_jilt_disconnect_handler() { + + _edd_deprecated_function( __FUNCTION__, '2.10.2' ); + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + wp_send_json_error( + array( + 'error' => __( 'You do not have permission to do this.', 'easy-digital-downloads' ), + ) + ); + } + + if ( is_callable( 'edd_jilt' ) ) { + + edd_jilt()->get_integration()->unlink_shop(); + edd_jilt()->get_integration()->revoke_authorization(); + edd_jilt()->get_integration()->clear_connection_data(); + } + + deactivate_plugins( 'jilt-for-edd/jilt-for-edd.php' ); + + wp_send_json_success(); +} + +/** + * Maybe adds a notice to abandoned payments if Jilt isn't installed. + * + * @deprecated 2.10.2 + * @since n.n.n + * + * @param int $payment_id The ID of the abandoned payment, for which a jilt notice is being thrown. + */ +function maybe_add_jilt_notice_to_abandoned_payment( $payment_id ) { + + _edd_deprecated_function( __FUNCTION__, '2.10.2' ); + + if ( ! is_callable( 'edd_jilt' ) + && ! is_plugin_active( 'recapture-for-edd/recapture.php' ) + && 'abandoned' === edd_get_payment_status( $payment_id ) + && ! get_user_meta( get_current_user_id(), '_edd_try_jilt_dismissed', true ) + ) { + ?> +
    +

    + tag, %2$s - tag, %3$s - tag, %4$s - tag */ + __( '%1$sRecover abandoned purchases like this one.%2$s %3$sTry Jilt for free%4$s.', 'easy-digital-downloads' ), + '', + '', + '', + '' + ) + ); + ?> +

    + 'dismiss_notices', + 'edd_notice' => 'try_jilt', + ) + ) + ), + '" type="button" class="notice-dismiss">', + '', + ' + ' + ) + ); + ?> +
    + SendWP) + $connected = ''; + $connected .= __( 'Access your SendWP account', 'easy-digital-downloads' ); + $connected .= '.'; + + $disconnected = sprintf( + __( 'Note: Email sending is currently disabled. Click here to enable it.', 'easy-digital-downloads' ) + ); + + // Checks if SendWP is connected + $client_connected = function_exists( 'sendwp_client_connected' ) && sendwp_client_connected() ? true : false; + + // Checks if email sending is enabled in SendWP + $forwarding_enabled = function_exists( 'sendwp_forwarding_enabled' ) && sendwp_forwarding_enabled() ? true : false; + + ob_start(); + + echo $args['desc']; + + // Output the appropriate button and label based on connection status + if( $client_connected ) : + ?> +
    +

    + +

    + +

    +
    + +

    + ', '' ); ?> +

    +

    + +

    + + __( 'You do not have permission to do this.', 'easy-digital-downloads' ) + ) ); + } + + include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + include_once ABSPATH . 'wp-admin/includes/file.php'; + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + $plugins = get_plugins(); + + if( ! array_key_exists( 'sendwp/sendwp.php', $plugins ) ) { + + /* + * Use the WordPress Plugins API to get the plugin download link. + */ + $api = plugins_api( 'plugin_information', array( + 'slug' => 'sendwp', + ) ); + + if ( is_wp_error( $api ) ) { + wp_send_json_error( array( + 'error' => $api->get_error_message(), + 'debug' => $api + ) ); + } + + /* + * Use the AJAX Upgrader skin to quietly install the plugin. + */ + $upgrader = new Plugin_Upgrader( new WP_Ajax_Upgrader_Skin() ); + $install = $upgrader->install( $api->download_link ); + if ( is_wp_error( $install ) ) { + wp_send_json_error( array( + 'error' => $install->get_error_message(), + 'debug' => $api + ) ); + } + + $activated = activate_plugin( $upgrader->plugin_info() ); + + } else { + + $activated = activate_plugin( 'sendwp/sendwp.php' ); + + } + + /* + * Final check to see if SendWP is available. + */ + if( ! function_exists('sendwp_get_server_url') ) { + wp_send_json_error( array( + 'error' => __( 'Something went wrong. SendWP was not installed correctly.', 'easy-digital-downloads' ) + ) ); + } + + wp_send_json_success( array( + 'partner_id' => 81, + 'register_url' => sendwp_get_server_url() . '_/signup', + 'client_name' => sendwp_get_client_name(), + 'client_secret' => sendwp_get_client_secret(), + 'client_redirect' => admin_url( 'edit.php?post_type=download&page=edd-settings&tab=emails&edd-message=sendwp-connected' ), + ) ); +} +add_action( 'wp_ajax_edd_sendwp_remote_install', 'edd_sendwp_remote_install_handler' ); + +/** + * Handle deactivation of SendWP via ajax + * + * @since 2.9.15 + */ +function edd_sendwp_disconnect () { + + _edd_deprecated_function( __FUNCTION__, '2.11.4' ); + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + wp_send_json_error( array( + 'error' => __( 'You do not have permission to do this.', 'easy-digital-downloads' ) + ) ); + } + + sendwp_disconnect_client(); + + deactivate_plugins( 'sendwp/sendwp.php' ); + + wp_send_json_success(); +} +add_action( 'wp_ajax_edd_sendwp_disconnect', 'edd_sendwp_disconnect' ); + +/** + * Reverts to the original download URL validation. + * + * @since 2.11.4 + * @todo Remove this function in 3.0. + * + * @param bool $ret + * @param string $url + * @param array $query_args + * @param string $original_url + */ +add_filter( 'edd_validate_url_token', function( $ret, $url, $query_args, $original_url ) { + // If the URL is already validated, we don't need to validate it again. + if ( $ret ) { + return $ret; + } + $allowed = edd_get_url_token_parameters(); + $remove = array(); + foreach ( $query_args as $key => $value ) { + if ( ! in_array( $key, $allowed, true ) ) { + $remove[] = $key; + } + } + + if ( ! empty( $remove ) ) { + $original_url = remove_query_arg( $remove, $original_url ); + } + + return isset( $query_args['token'] ) && hash_equals( $query_args['token'], edd_get_download_token( $original_url ) ); +}, 10, 4 ); + +/** + * Get the path of the Product Reviews plugin + * + * @since 2.9.20 + * + * @return mixed|string + */ +function edd_reviews_location() { + + _edd_deprecated_function( __FUNCTION__, '2.11.4' ); + + $possible_locations = array( 'edd-reviews/edd-reviews.php', 'EDD-Reviews/edd-reviews.php' ); + $reviews_location = ''; + + foreach ( $possible_locations as $location ) { + + if ( 0 !== validate_plugin( $location ) ) { + continue; + } + $reviews_location = $location; + } + + return $reviews_location; +} + +/** + * Outputs a metabox for the Product Reviews extension to show or activate it. + * + * @since 2.8 + * @return void + */ +function edd_render_review_status_metabox() { + + _edd_deprecated_function( __FUNCTION__, '2.11.4' ); + + $reviews_location = edd_reviews_location(); + + ob_start(); + + if ( ! empty( $reviews_location ) ) { + $review_path = ''; + $base_url = wp_nonce_url( admin_url( 'plugins.php' ), 'activate-plugin_' . sanitize_key( $reviews_location ) ); + $args = array( + 'action' => 'activate', + 'plugin' => sanitize_text_field( $reviews_location ), + 'plugin_status' => 'all', + ); + $activate_url = add_query_arg( $args, $base_url ); + ?>

    'edit-download', + 'utm_content' => 'product-reviews', + ) + ); + ?> +

    + Product Reviews extension.', 'easy-digital-downloads' ), $url ) ); + ?> +

    + get_sales(); +} + +/** + * Decreases the sale count of a download. Primarily for when a purchase is + * refunded. + * + * @since 1.0.8.1 + * + * @param int $download_id Download ID. + * @param int $quantity Optional. Quantity to decrease by. Default 1. + * + * @return bool|int Updated sale count, false if download does not exist. + */ +function edd_decrease_purchase_count( $download_id = 0, $quantity = 1 ) { + + _edd_deprecated_function( __FUNCTION__, '3.0' ); + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + if ( ! $download ) { + return false; + } + + return $download->get_sales(); +} + +/** + * Increases the total earnings of a download. + * + * @since 1.0 + * + * @param int $download_id Download ID. + * @param float $amount Earnings to increase by. + * + * @return float|false Updated earnings, false if invalid data passed. + */ +function edd_increase_earnings( $download_id = 0, $amount = 0.00 ) { + + _edd_deprecated_function( __FUNCTION__, '3.0' ); + + // Bail if no download ID or amount was passed. + if ( empty( $download_id ) || empty( $amount ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + if ( ! $download ) { + return false; + } + + return $download->get_earnings(); +} + +/** + * Decreases the total earnings of a download. Primarily for when a purchase + * is refunded. + * + * @since 1.0.8.1 + * + * @param int $download_id Download ID. + * @param float $amount Earnings to decrease by. + * + * @return float|false Updated earnings, false if invalid data passed. + */ +function edd_decrease_earnings( $download_id = 0, $amount = 0.00 ) { + + _edd_deprecated_function( __FUNCTION__, '3.0' ); + + // Bail if no download ID or amount was passed. + if ( empty( $download_id ) || empty( $amount ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + if ( ! $download ) { + return false; + } + + return $download->get_earnings(); +} + +/** + * Check to see if we should be displaying promotional content + * + * In various parts of the plugin, we may choose to promote something like a sale for a limited time only. This + * function should be used to set the conditions under which the promotions will display. + * + * @since 2.9.20 + * @deprecated 3.1 + * + * @return bool + */ +function edd_is_promo_active() { + _edd_deprecated_function( __FUNCTION__, '3.1' ); + + return false; +} + +/** + * Outputs a metabox for promotional content. + * + * @since 2.9.20 + * @deprecated 3.1 + * + * @return void + */ +function edd_render_promo_metabox() { + _edd_deprecated_function( __FUNCTION__, '3.1' ); + return; +} + +/** + * Plugin row meta links + * + * @since 1.8 + * @deprecated 3.1 + * @param array $links already defined meta links. + * @param string $file plugin file path and name being processed. + * @return array $input + */ +function edd_plugin_row_meta( $links = array(), $file = '' ) { + _edd_deprecated_function( __FUNCTION__, '3.1' ); + return $links; +} + +/** + * Listens to the updated_postmeta hook for our backwards compatible payment_meta updates, and runs through them + * + * Previously hooked into: updated_postmeta + * + * @since 2.3 + * @deprecated 3.1.0.3 + * @param int $meta_id The Meta ID that was updated + * @param int $object_id The Object ID that was updated (post ID) + * @param string $meta_key The Meta key that was updated + * @param string|int|float $meta_value The Value being updated + * @return bool|int If successful the number of rows updated, if it fails, false + */ +function edd_update_payment_backwards_compat( $meta_id, $object_id, $meta_key, $meta_value ) { + + _edd_deprecated_function( __FUNCTION__, '3.1.0.3' ); + + $meta_keys = array( '_edd_payment_meta', '_edd_payment_tax' ); + + if ( ! in_array( $meta_key, $meta_keys ) ) { + return; + } + + global $wpdb; + switch( $meta_key ) { + + case '_edd_payment_meta': + $meta_value = maybe_unserialize( $meta_value ); + + if( ! isset( $meta_value['tax'] ) ){ + return; + } + + $tax_value = $meta_value['tax']; + + $data = array( 'meta_value' => $tax_value ); + $where = array( 'post_id' => $object_id, 'meta_key' => '_edd_payment_tax' ); + $data_format = array( '%f' ); + $where_format = array( '%d', '%s' ); + break; + + case '_edd_payment_tax': + $tax_value = ! empty( $meta_value ) ? $meta_value : 0; + $current_meta = edd_get_payment_meta( $object_id, '_edd_payment_meta', true ); + + $current_meta['tax'] = $tax_value; + $new_meta = maybe_serialize( $current_meta ); + + $data = array( 'meta_value' => $new_meta ); + $where = array( 'post_id' => $object_id, 'meta_key' => '_edd_payment_meta' ); + $data_format = array( '%s' ); + $where_format = array( '%d', '%s' ); + + break; + + } + + $updated = $wpdb->update( $wpdb->postmeta, $data, $where, $data_format, $where_format ); + + if ( ! empty( $updated ) ) { + // Since we did a direct DB query, clear the postmeta cache. + wp_cache_delete( $object_id, 'post_meta' ); + } + + return $updated; + +} + +/** + * Deletes edd_stats_ transients that have expired to prevent database clogs + * + * Previously hooked into: edd_daily_scheduled_events + * + * @since 2.6.7 + * @deprecated 3.1.0.3 + * @return void */ +function edd_cleanup_stats_transients() { + + _edd_deprecated_function( __FUNCTION__, '3.1.0.3' ); + + global $wpdb; + if ( defined( 'WP_SETUP_CONFIG' ) ) { + return; + } + + if ( defined( 'WP_INSTALLING' ) ) { + return; + } + + $now = current_time( 'timestamp' ); + $transients = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE '%\_transient_timeout\_edd\_stats\_%' AND option_value+0 < $now LIMIT 0, 200;" ); + $to_delete = array(); + + if( ! empty( $transients ) ) { + + foreach( $transients as $transient ) { + + $to_delete[] = $transient->option_name; + $to_delete[] = str_replace( '_timeout', '', $transient->option_name ); + + } + + } + + if ( ! empty( $to_delete ) ) { + + $option_names = implode( "','", $to_delete ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name IN ('$option_names')" ); + + } + +} /** - * Count Payments + * Updates all old payments, prior to 1.2, with new + * meta for the total purchase amount * - * Returns the total number of payments recorded. + * This is so that payments can be queried by their totals * - * @access public - * @since 1.0 - * @return integer + * Prevsiouly hooked into: edd_upgrade_payments + * + * @since 1.2 + * @deprecated 3.1.0.3 + * @param array $data Arguments passed + * @return void */ +function edd_update_old_payments_with_totals( $data ) { + _edd_deprecated_function( __FUNCTION__, '3.1.0.3' ); + + if ( ! wp_verify_nonce( $data['_wpnonce'], 'edd_upgrade_payments_nonce' ) ) { + return; + } + + if ( get_option( 'edd_payment_totals_upgraded' ) ) { + return; + } -function edd_count_payments($mode, $user = null) { $payments = edd_get_payments( array( - 'offset' => 0, - 'number' => -1, - 'mode' => $mode, - 'orderby' => 'ID', - 'order' => 'DESC', - 'user' => $user + 'offset' => 0, + 'number' => 9999999, + 'mode' => 'all', ) ); - $count = 0; - if($payments) { - $count = count($payments); + + if ( $payments ) { + foreach ( $payments as $payment ) { + + $payment = new EDD_Payment( $payment->ID ); + $meta = $payment->get_meta(); + + $payment->total = $meta['amount']; + $payment->save(); + } + } + + add_option( 'edd_payment_totals_upgraded', 1 ); +} + +/** + * Flushes the current user's purchase history transient when a payment status + * is updated + * + * Previously hooked into: edd_update_payment_status + * + * @since 1.2.2 + * @deprecated 3.1.0.3 + * @param int $payment_id the ID number of the payment + * @param string $new_status the status of the payment, probably "publish" + * @param string $old_status the status of the payment prior to being marked as "complete", probably "pending" + */ +function edd_clear_user_history_cache( $payment_id, $new_status, $old_status ) { + + _edd_deprecated_function( __FUNCTION__, '3.1.0.3' ); + + $payment = new EDD_Payment( $payment_id ); + + if( ! empty( $payment->user_id ) ) { + delete_transient( 'edd_user_' . $payment->user_id . '_purchases' ); } - return $count; +} + +/** + * Filters the WHERE SQL query for the edd_download_search. + * This searches the download titles only, not the excerpt/content. + * + * @since 3.1.0.2 + * @deprecated 3.1.0.5 + * @param string $where + * @param WP_Query $wp_query + * @return string + */ +function edd_ajax_filter_download_where( $where, $wp_query ) { + + _edd_deprecated_function( __FUNCTION__, '3.1.0.5' ); + + $search = new EDD\Downloads\Search(); + + return $search->filter_where( $where, $wp_query ); +} + +/** + * Gets the next available order number. + * + * This is used when inserting a new order. + * + * @deprecated 3.1.2 + * @since 2.0 + * @return false|int $number The next available order number, unformatted. + */ +function edd_get_next_payment_number() { + + _edd_deprecated_function( __FUNCTION__, '3.1.2', 'EDD\Orders\Number\get_next_payment_number' ); + $order_number = new EDD\Orders\Number(); + + return $order_number->get_next_payment_number(); } diff --git a/includes/deprecated-hooks.php b/includes/deprecated-hooks.php new file mode 100644 index 00000000000..f27ed12d603 --- /dev/null +++ b/includes/deprecated-hooks.php @@ -0,0 +1,272 @@ +product_id The product ID. + * @param int $order->id The order ID. + */ +add_action( 'edd_order_receipt_files', function( $filekey, $file, $product_id, $order_id ) { + if ( ! has_action( 'edd_receipt_files' ) ) { + return; + } + $meta = edd_get_payment_meta( $order_id ); + do_action( 'edd_receipt_files', $filekey, $file, $product_id, $order_id, $meta ); +}, 10, 4 ); + + +/** + * Fires after the order receipt bundled items, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param int $filekey Index of array of files returned by edd_get_download_files() that this download link is for. + * @param array $file The array of file information. + * @param int $item->product_id The product ID. + * @param array $bundle_item The array of information about the bundled item. + * @param int $order->id The order ID. + */ +add_action( 'edd_order_receipt_bundle_files', function( $filekey, $file, $product_id, $bundle_item, $order_id ) { + if ( ! has_action( 'edd_receipt_bundle_files' ) ) { + return; + } + $meta = edd_get_payment_meta( $order_id ); + do_action( 'edd_receipt_bundle_files', $filekey, $file, $product_id, $bundle_item, $order_id, $meta ); +}, 10, 5 ); + +/** + * Fires at the end of the product cell. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order_Item $item The current order item. + * @param \EDD\Orders\Order $order The current order object. + */ +add_action( 'edd_order_receipt_after_files', function( $item, $order ) { + if ( ! has_action( 'edd_purchase_receipt_after_files' ) ) { + return; + } + $meta = edd_get_payment_meta( $order->id ); + do_action( 'edd_purchase_receipt_after_files', $item->product_id, $order->id, $meta, $item->price_id ); +}, 10, 2 ); + +/** + * Fires before the order receipt table, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order $order The current order object. + * @param array $edd_receipt_args The shortcode parameters for the receipt. + */ +add_action( 'edd_order_receipt_before_table', function( $order, $edd_receipt_args ) { + if ( ! has_action( 'edd_payment_receipt_before_table' ) ) { + return; + } + $payment = edd_get_payment( $order->id ); + do_action( 'edd_payment_receipt_before_table', $payment, $edd_receipt_args ); +}, 10, 2 ); + +/** + * Fires at the beginning of the order receipt `thead`, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order $order The current order object. + * @param array $edd_receipt_args The shortcode parameters for the receipt. + */ +add_action( 'edd_order_receipt_before', function( $order, $edd_receipt_args ) { + if ( ! has_action( 'edd_payment_receipt_before' ) ) { + return; + } + $payment = edd_get_payment( $order->id ); + do_action( 'edd_payment_receipt_before', $payment, $edd_receipt_args ); +}, 10, 2 ); + +/** + * Fires at the end of the order receipt `tbody`, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order $order The current order object. + * @param array $edd_receipt_args The shortcode parameters for the receipt. + */ +add_action( 'edd_order_receipt_after', function( $order, $edd_receipt_args ) { + if ( ! has_action( 'edd_payment_receipt_after' ) ) { + return; + } + $payment = edd_get_payment( $order->id ); + do_action( 'edd_payment_receipt_after', $payment, $edd_receipt_args ); +}, 10, 2 ); + +/** + * Fires after the order receipt table, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order $order The current order object. + * @param array $edd_receipt_args The shortcode parameters for the receipt. + */ +add_action( 'edd_order_receipt_after_table', function( $order, $edd_receipt_args ) { + if ( ! has_action( 'edd_payment_receipt_after_table' ) ) { + return; + } + $payment = edd_get_payment( $order->id ); + do_action( 'edd_payment_receipt_after_table', $payment, $edd_receipt_args ); +}, 10, 2 ); + +/** + * Fires the edd_before_purchase_history hook in the purchase history, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order[] $orders The array of the current user's orders. + */ +add_action( 'edd_before_order_history', function( $orders ) { + if ( ! has_action( 'edd_before_purchase_history' ) ) { + return; + } + + $payments = array(); + + if ( ! empty( $orders ) ) { + $order_ids = wp_list_pluck( $orders, 'id' ); + $payments = edd_get_payments( + array( + 'id__in' => $order_ids, + 'orderby' => 'date', + ) + ); + } + + do_action( 'edd_before_purchase_history', $payments ); +} ); + +/** + * Fires at the beginning of the purchase history row, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order $order The current order object. + */ +add_action( 'edd_order_history_row_start', function( \EDD\Orders\Order $order ) { + if ( ! has_action( 'edd_purchase_history_row_start' ) ) { + return; + } + + $payment = edd_get_payment( $order->id ); + if ( ! $payment ) { + return; + } + + do_action( 'edd_purchase_history_row_start', $payment->ID, $payment->payment_meta ); +} ); + +/** + * Fires at the end of the purchase history row, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order $order The current order object. + */ +add_action( 'edd_order_history_row_end', function( \EDD\Orders\Order $order ) { + if ( ! has_action( 'edd_purchase_history_row_end' ) ) { + return; + } + + $payment = edd_get_payment( $order->id ); + if ( ! $payment ) { + return; + } + + do_action( 'edd_purchase_history_row_end', $payment->ID, $payment->payment_meta ); +} ); + +/** + * Fires the edd_after_purchase_history hook in the purchase history, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in EDD 3.1 + * @param \EDD\Orders\Order[] $orders The array of the current user's orders. + */ +add_action( 'edd_after_order_history', function( $orders ) { + if ( ! has_action( 'edd_after_purchase_history' ) ) { + return; + } + + $payments = array(); + + if ( ! empty( $orders ) ) { + $order_ids = wp_list_pluck( $orders, 'id' ); + $payments = edd_get_payments( + array( + 'id__in' => $order_ids, + 'orderby' => 'date', + ) + ); + } + + do_action( 'edd_after_purchase_history', $payments ); +} ); + +/** + * Fires after the individual download file in the downloads history, if needed. + * + * @deprecated 3.0 + * @todo Formally deprecate in 3.1 + * @param int $filekey Download file ID. + * @param array $file Array of file information. + * @param \EDD\Orders\Order_Item $item The order item object. + * @param \EDD\Orders\Order $order The order object. + */ +add_action( 'edd_download_history_download_file', function( $filekey, $file, $item, $order ) { + if ( ! has_action( 'edd_download_history_files' ) ) { + return; + } + $purchase_data = edd_get_payment_meta( $order->id ); + do_action( 'edd_download_history_files', $filekey, $file, $item->product_id, $order->id, $purchase_data ); +}, 10, 4 ); diff --git a/includes/discount-functions.php b/includes/discount-functions.php index 9a3f933afa1..9d8ca234e7d 100755 --- a/includes/discount-functions.php +++ b/includes/discount-functions.php @@ -2,523 +2,1596 @@ /** * Discount Functions * - * @package Easy Digital Downloads - * @subpackage Discount Functions - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Functions + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** - * Get Discounts + * Add a discount. * - * Retrieves an array of all available discount codes. + * @since 1.0 + * @since 3.0 This function has been repurposed. Previously it was an internal admin callback for adding + * a discount via the UI. It's now used as a public function for inserting a new discount + * into the database. * - * @access public - * @since 1.0 - * @return boolean -*/ + * @param array $data Discount data. + * @return int Discount ID. + */ +function edd_add_discount( $data = array() ) { + + // Juggle requirements and products. + $product_requirements = isset( $data['product_reqs'] ) ? $data['product_reqs'] : null; + $excluded_products = isset( $data['excluded_products'] ) ? $data['excluded_products'] : null; + $product_condition = isset( $data['product_condition'] ) ? $data['product_condition'] : null; + $pre_convert_args = $data; + unset( $data['product_reqs'], $data['excluded_products'], $data['product_condition'] ); + + if ( isset( $data['expiration'] ) ) { + $data['end_date'] = $data['expiration']; + unset( $data['expiration'] ); + } -function edd_get_discounts() { - $discounts = get_option('edd_discounts'); - if(false === $discounts) { - add_option('edd_discounts'); + if ( isset( $data['start'] ) ) { + $data['start_date'] = $data['start']; + unset( $data['start'] ); } - if($discounts) - return $discounts; - return false; -} + // Setup the discounts query. + $discounts = new EDD\Compat\Discount_Query(); + + // Attempt to add the discount. + $discount_id = $discounts->add_item( $data ); + + // Maybe add requirements & exclusions. + if ( ! empty( $discount_id ) ) { + + // Product requirements. + if ( ! empty( $product_requirements ) ) { + if ( is_string( $product_requirements ) ) { + $product_requirements = maybe_unserialize( $product_requirements ); + } + + if ( is_array( $product_requirements ) ) { + foreach ( array_filter( $product_requirements ) as $product_requirement ) { + edd_add_adjustment_meta( $discount_id, 'product_requirement', $product_requirement ); + } + } + } + + // Excluded products. + if ( ! empty( $excluded_products ) ) { + if ( is_string( $excluded_products ) ) { + $excluded_products = maybe_unserialize( $excluded_products ); + } + + if ( is_array( $excluded_products ) ) { + foreach ( array_filter( $excluded_products ) as $excluded_product ) { + edd_add_adjustment_meta( $discount_id, 'excluded_product', $excluded_product ); + } + } + } + + if ( ! empty( $product_condition ) ) { + edd_add_adjustment_meta( $discount_id, 'product_condition', $product_condition ); + } + + // If the end date has passed, mark the discount as expired. + edd_is_discount_expired( $discount_id ); + } + + /** + * Fires after the discount code is inserted. This hook exists for + * backwards compatibility purposes. It uses the $pre_convert_args variable + * to ensure the arguments maintain backwards compatible array keys. + * + * @since 2.7 + * + * @param array $pre_convert_args Discount args. + * @param int $return Discount ID. + */ + do_action( 'edd_post_insert_discount', $pre_convert_args, $discount_id ); + + // Return the new discount ID. + return $discount_id; +} /** - * Has Active Discounts + * Delete a discount. * - * Checks if there is any active discounts, returns a boolean. + * @since 3.0 * - * @access public - * @since 1.0 - * @return boolean -*/ + * @param int $discount_id Discount ID. + * @return int + */ +function edd_delete_discount( $discount_id = 0 ) { + $discount = edd_get_discount( $discount_id ); + + // Do not allow for a discount to be deleted if it has been used. + if ( $discount && 0 < $discount->use_count ) { + return false; + } -function edd_has_active_discounts() { - $has_active = false; - $discounts = edd_get_discounts(); - if(is_array($discounts) && !empty($discounts)) { - foreach($discounts as $discount) { - if(isset($discount['status']) && $discount['status'] == 'active' && !edd_is_discount_expired($discount['code'])) { - $has_active = true; - break; - } - } - } - return $has_active; + $discounts = new EDD\Compat\Discount_Query(); + + // Pre-3.0 pre action. + do_action( 'edd_pre_delete_discount', $discount_id ); + + $retval = $discounts->delete_item( $discount_id ); + + // Pre-3.0 post action. + do_action( 'edd_post_delete_discount', $discount_id ); + + return $retval; } - /** - * Get Discount + * Get Discount. * - * Retrieves a complete discount code by ID/key. + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object + * @since 3.0 Updated to call use new query class. * - * @access public - * @since 1.0 - * @return array -*/ + * @param int $discount_id Discount ID. + * @return \EDD_Discount|bool EDD_Discount object or false if not found. + */ +function edd_get_discount( $discount_id = 0 ) { + $discounts = new EDD\Compat\Discount_Query(); + + // Return discount + return $discounts->get_item( $discount_id ); +} -function edd_get_discount($key) { - $discounts = edd_get_discounts(); - if($discounts) { - return isset($discounts[$key]) ? $discounts[$key] : false; - } - return false; +/** + * Get discount by code. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object + * @since 3.0 Updated to call use new query class. + * @since 3.0 Updated to include a filter. + * + * @param string $code Discount code. + * @return EDD_Discount|bool EDD_Discount object or false if not found. + */ +function edd_get_discount_by_code( $code = '' ) { + $discount = edd_get_discount_by( 'code', $code ); + + /** + * Filters the get discount by request. + * + * @since 3.0 + * + * @param \EDD_Discount $discount Discount object. + * @param string $code Discount code. + */ + return apply_filters( 'edd_get_discount_by_code', $discount, $code ); } +/** + * Retrieve discount by a given field + * + * @since 2.0 + * @since 2.7 Updated to use EDD_Discount object + * @since 3.0 Updated to call use new query class. + * + * @param string $field The field to retrieve the discount with. + * @param mixed $value The value for $field. + * @return mixed EDD_Discount|bool EDD_Discount object or false if not found. + */ +function edd_get_discount_by( $field = '', $value = '' ) { + $discounts = new EDD\Compat\Discount_Query(); + + // Return discount + return $discounts->get_item_by( $field, $value ); +} /** - * Get Discount By Code + * Retrieve discount by a given field * - * Retrieves all details for a discount by its code. + * @since 2.0 + * @since 2.7 Updated to use EDD_Discount object + * @since 3.0 Updated to call edd_get_discount() * - * @access public - * @since 1.0 - * @return array -*/ + * @param int $discount_id Discount ID. + * @param string $field The field to retrieve the discount with. + * @return mixed object|bool EDD_Discount object or false if not found. + */ +function edd_get_discount_field( $discount_id, $field = '' ) { + $discount = edd_get_discount( $discount_id ); + + // Check that field exists + return isset( $discount->{$field} ) + ? $discount->{$field} + : null; +} + +/** + * Update a discount + * + * @since 3.0 + * @param int $discount_id Discount ID. + * @param array $data + * @return int + */ +function edd_update_discount( $discount_id = 0, $data = array() ) { + + // Pre-3.0 pre action + do_action( 'edd_pre_update_discount', $data, $discount_id ); + + // Product requirements. + if ( isset( $data['product_reqs'] ) && ! empty( $data['product_reqs'] ) ) { + if ( is_string( $data['product_reqs'] ) ) { + $data['product_reqs'] = maybe_unserialize( $data['product_reqs'] ); + } -function edd_get_discount_by_code($code) { - $discounts = edd_get_discounts(); - if($discounts) { - foreach($discounts as $id => $discount) { - if($discount['code'] == $code) { - return $discounts[$id]; + if ( is_array( $data['product_reqs'] ) ) { + edd_delete_adjustment_meta( $discount_id, 'product_requirement' ); + + foreach ( $data['product_reqs'] as $product_requirement ) { + edd_add_adjustment_meta( $discount_id, 'product_requirement', $product_requirement ); } } + + unset( $data['product_reqs'] ); + } elseif ( isset( $data['product_reqs'] ) ) { + edd_delete_adjustment_meta( $discount_id, 'product_requirement' ); + + // We don't have product conditions when there are no product requirements. + edd_delete_adjustment_meta( $discount_id, 'product_condition' ); + unset( $data['product_condition'] ); } - return false; + + // Excluded products are handled differently. + if ( isset( $data['excluded_products'] ) && ! empty( $data['excluded_products'] ) ) { + if ( is_string( $data['excluded_products'] ) ) { + $data['excluded_products'] = maybe_unserialize( $data['excluded_products'] ); + } + + if ( is_array( $data['excluded_products'] ) ) { + edd_delete_adjustment_meta( $discount_id, 'excluded_product' ); + + foreach ( $data['excluded_products'] as $excluded_product ) { + edd_add_adjustment_meta( $discount_id, 'excluded_product', $excluded_product ); + } + } + + unset( $data['excluded_products'] ); + } elseif( isset( $data['excluded_products'] ) ) { + edd_delete_adjustment_meta( $discount_id, 'excluded_product' ); + } + + if ( isset( $data['product_condition'] ) ) { + $product_condition = sanitize_text_field( $data['product_condition'] ); + edd_update_adjustment_meta( $discount_id, 'product_condition', $product_condition ); + } + + $discounts = new EDD\Compat\Discount_Query(); + + $retval = $discounts->update_item( $discount_id, $data ); + + // Pre-3.0 post action + do_action( 'edd_post_update_discount', $data, $discount_id ); + + return $retval; } +/** + * Get Discounts + * + * Retrieves an array of all available discount codes. + * + * @since 1.0 + * @param array $args Query arguments + * @return mixed array if discounts exist, false otherwise + */ +function edd_get_discounts( $args = array() ) { + + // Parse arguments. + $r = wp_parse_args( $args, array( + 'number' => 30 + ) ); + + // Back compat for old query arg. + if ( isset( $r['posts_per_page'] ) ) { + $r['number'] = $r['posts_per_page']; + } + + // Instantiate a query object. + $discounts = new EDD\Compat\Discount_Query(); + // Return discounts + return $discounts->query( $r ); +} /** - * Store Discount + * Return total number of discounts * - * Stores a discount code. - * If the code exists, it updates it, otherwise it creates a new one. + * @since 3.0 * - * @access public - * @since 1.0 - * @return boolean -*/ + * @param array $args Arguments. + * @return int + */ +function edd_get_discount_count( $args = array() ) { -function edd_store_discount($discount_details, $id = null) { - if(edd_discount_exists($id) && !is_null($id)) { + // Parse args. + $r = wp_parse_args( $args, array( + 'count' => true + ) ); - // update an existing discount - $discounts = edd_get_discounts(); - if(!$discounts) $discounts = array(); - - $discounts[$id] = $discount_details; - - apply_filters( 'edd_update_discount', $discount_details, $id ); - - update_option('edd_discounts', $discounts); + // Query for count(s). + $discounts = new EDD\Compat\Discount_Query( $r ); - do_action( 'edd_post_update_discount', $discount_details, $id ); - - // discount code updated - return true; - - } else { - // add the discount - $discounts = edd_get_discounts(); + // Return count(s). + return absint( $discounts->found_items ); +} + +/** + * Query for and return array of discount counts, keyed by status. + * + * @since 3.0 + * + * @return array + */ +function edd_get_discount_counts( $args = array() ) { - if(!$discounts) $discounts = array(); - - $discounts[] = $discount_details; - - apply_filters( 'edd_insert_discount', $discount_details ); + // Parse arguments + $r = wp_parse_args( $args, array( + 'count' => true, + 'groupby' => 'status' + ) ); - update_option('edd_discounts', $discounts); - - do_action( 'edd_post_insert_discount', $discount_details ); + // Query for count. + $counts = new EDD\Compat\Discount_Query( $r ); - // discount code created - return true; + // Format & return + return edd_format_counts( $counts, $r['groupby'] ); +} + +/** + * Query for discount notes. + * + * @since 3.0 + * + * @param int $discount_id Discount ID. + * @return array Retrieved notes. + */ +function edd_get_discount_notes( $discount_id = 0 ) { + return edd_get_notes( array( + 'object_id' => $discount_id, + 'object_type' => 'discount', + 'order' => 'asc' + ) ); +} + +/** + * Checks if there is any active discounts, returns a boolean. + * + * @since 1.0 + * @since 3.0 Updated to be more efficient and make direct calls to the EDD_Discount object. + * + * @return bool + */ +function edd_has_active_discounts() { + + // Query for active discounts. + $discounts = edd_get_discounts( array( + 'number' => 10, + 'status' => 'active' + ) ); + + // Bail if none. + if ( empty( $discounts ) ) { + return false; } - - // something went wrong + + // Check each discount for active status, applying filters, etc... + foreach ( $discounts as $discount ) { + /** @var $discount EDD_Discount */ + if ( $discount->is_active( false, true ) ) { + return true; + } + } + return false; } - /** - * Remove Discount + * Stores a discount code. If the code already exists, it updates it, otherwise + * it creates a new one. * - * Deletes a discount code. + * @internal This method exists for backwards compatibility. `edd_add_discount()` should be used. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to use new query class. + * @deprecated 3.0 Use edd_add_discount() * - * @access public - * @since 1.0 - * @return void -*/ + * @param array $details Discount args. + * @param int $discount_id Discount ID. + * @return mixed bool|int The discount ID of the discount code, or false on failure. + */ +function edd_store_discount( $details, $discount_id = null ) { + + edd_debug_log( + sprintf( + __( '%1$s is deprecated since Easy Digital Downloads version %2$s! Use %3$s instead.', 'easy-digital-downloads' ), + __FUNCTION__, + '3.0', + 'edd_add_discount()' + ), + true + ); -function edd_remove_discount($discount_id) { - - $discounts = edd_get_discounts(); + // Set default return value to false. + $return = false; - do_action( 'edd_pre_delete_discount', $discount_id ); + // Back-compat for start date. + if ( isset( $details['start'] ) && strstr( $details['start'], '/' ) ) { + $time_format = date( 'H:i:s', strtotime( $details['start'] ) ); + $date_format = date( 'Y-m-d', strtotime( $details['start'] ) ) . ' ' . $time_format; + $details['start_date'] = edd_get_utc_equivalent_date( EDD()->utils->date( $date_format, edd_get_timezone_id(), false )); + unset( $details['start'] ); + } - unset($discounts[$discount_id]); - - do_action( 'edd_post_delete_discount', $discount_id ); + // Back-compat for end date. + if ( isset( $details['expiration'] ) && strstr( $details['expiration'], '/' ) ) { + $time_format = date( 'H:i:s', strtotime( $details['expiration'] ) ); + $date_format = date( 'Y-m-d', strtotime( $details['expiration'] ) ) . ' ' . ( '00:00:00' !== $time_format ? $time_format : '23:59:59' ); + $details['end_date'] = edd_get_utc_equivalent_date( EDD()->utils->date( $date_format, edd_get_timezone_id(), false ) ); + unset( $details['expiration'] ); + } - update_option('edd_discounts', $discounts); + /** + * Filters the args before being inserted into the database. This hook + * exists for backwards compatibility purposes. + * + * @since 2.7 + * + * @param array $details Discount args. + */ + $details = apply_filters( 'edd_insert_discount', $details ); + + /** + * Fires before the discount has been added to the database. This hook + * exists for backwards compatibility purposes. It fires before the + * call to `EDD_Discount::convert_legacy_args` to ensure the arguments + * maintain backwards compatible array keys. + * + * @since 2.7 + * + * @param array $details Discount args. + */ + do_action( 'edd_pre_insert_discount', $details ); + + // Convert legacy arguments to new ones accepted by `edd_add_discount()`. + $details = EDD_Discount::convert_legacy_args( $details ); + + if ( null === $discount_id ) { + $return = (int) edd_add_discount( $details ); + } else { + edd_update_discount( $discount_id, $details ); + $return = $discount_id; + } + + return $return; } - +/** + * Deletes a discount code. + * + * @internal This method exists for backwards compatibility. `edd_delete_discount()` should be used. + * + * @since 1.0 + * @deprecated 3.0 + * + * @param int $discount_id Discount ID. + */ +function edd_remove_discount( $discount_id = 0 ) { + edd_delete_discount( $discount_id ); +} /** - * Update Discount Status + * Updates a discount status from one status to another. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() * - * Updates a discount's status from one status to another. + * @param int $discount_id Discount ID (default: 0) + * @param string $new_status New status (default: active) * - * @access public - * @since 1.0 - * @return void -*/ + * @return bool Whether the status has been updated or not. + */ +function edd_update_discount_status( $discount_id = 0, $new_status = 'active' ) { -function edd_update_discount_status($code_id, $new_status) { - $discount = edd_get_discount($code_id); - $discounts = edd_get_discounts(); - if($discount) { - $discounts[$code_id]['status'] = $new_status; - update_option('edd_discounts', $discounts); + // Bail if an invalid ID is passed. + if ( $discount_id <= 0 ) { + return false; + } + + // Set defaults. + $updated = false; + $new_status = sanitize_key( $new_status ); + $discount = edd_get_discount( $discount_id ); + + // No change. + if ( $new_status === $discount->status ) { return true; } - - return false; + + // Try to update status. + if ( ! empty( $discount->id ) ) { + $updated = (bool) edd_update_discount( $discount->id, array( + 'status' => $new_status + ) ); + } + + // Return. + return $updated; } +/** + * Checks to see if a discount code already exists. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount(). + * + * @param int $discount_id Discount ID. + * + * @return bool Whether or not the discount exists. + */ +function edd_discount_exists( $discount_id ) { + $discount = edd_get_discount( $discount_id ); + + return $discount instanceof EDD_Discount && $discount->exists(); +} /** - * Discount Exists + * Checks whether a discount code is active. * - * Checks to see if a discount code already exists. + * @since 1.0 + * @since 2.6.11 Added $update parameter. + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount(). * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_discount_exists($code_id) { - $discounts = edd_get_discounts(); - - // no discounts, so the code does not exist - if(!$discounts) return false; - - // a discount with this code has been found - if(isset($discounts[$code_id])) return true; - - // no discount with the specified ID exists - return false; + * @param int $discount_id Discount ID. + * @param bool $update Update the discount to expired if an one is found but has an active status/ + * @param bool $set_error Whether an error message should be set in session. + * @return bool Whether or not the discount is active. + */ +function edd_is_discount_active( $discount_id = 0, $update = true, $set_error = true ) { + $discount = edd_get_discount( $discount_id ); + + if ( ! $discount instanceof EDD_Discount ) { + return false; + } + + return $discount->is_active( $update, $set_error ); } +/** + * Retrieve the discount code. + * + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field() + * + * @param int $discount_id Discount ID. + * @return string $code Discount Code. + */ +function edd_get_discount_code( $discount_id = 0 ) { + return edd_get_discount_field( $discount_id, 'code' ); +} /** - * Is Discount Active + * Retrieve the discount code start date. * - * Checks whether a discount code is active. + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field() * - * @access public - * @since 1.0 - * @return void -*/ + * @param int $discount_id Discount ID. + * @return string $start Discount start date. + */ +function edd_get_discount_start_date( $discount_id = 0 ) { + return edd_get_discount_field( $discount_id, 'start_date' ); +} -function edd_is_discount_active( $code_id = null ) { - $discount = edd_get_discount( $code_id ); - $return = false; - if($discount) { - if( isset( $discount['status'] ) && $discount['status'] == 'active' && !edd_is_discount_expired( $code_id ) ) { - $return = true; - } - } - return apply_filters( 'edd_is_discount_active', $return, $code_id ); +/** + * Retrieve the discount code expiration date. + * + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field() + * + * @param int $discount_id Discount ID. + * @return string $expiration Discount expiration. + */ +function edd_get_discount_expiration( $discount_id = 0 ) { + return edd_get_discount_field( $discount_id, 'end_date' ); } +/** + * Retrieve the maximum uses that a certain discount code. + * + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field() + * + * @param int $discount_id Discount ID. + * @return int $max_uses Maximum number of uses for the discount code. + */ +function edd_get_discount_max_uses( $discount_id = 0 ) { + return edd_get_discount_field( $discount_id, 'max_uses' ); +} /** - * Is Discount Expired + * Retrieve number of times a discount has been used. * - * Checks whether a discount code is expired. + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field(). * - * @access public - * @since 1.0 - * @return void -*/ + * @param int $discount_id Discount ID. + * @return int $uses Number of times a discount has been used. + */ +function edd_get_discount_uses( $discount_id = 0 ) { + return (int) edd_get_discount_field( $discount_id, 'use_count' ); +} -function edd_is_discount_expired( $code_id = null ) { +/** + * Retrieve the minimum purchase amount for a discount. + * + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field(). + * + * @param int $discount_id Discount ID. + * @return float $min_price Minimum purchase amount. + */ +function edd_get_discount_min_price( $discount_id = 0 ) { + return edd_format_amount( edd_get_discount_field( $discount_id, 'min_charge_amount' ) ); +} - $discount = edd_get_discount($code_id); - $return = false; - if($discount) { - if(isset($discount['expiration']) && $discount['expiration'] != '') { - $expiration = strtotime($discount['expiration']); - if($expiration < time() - (24 * 60 * 60)) { - // discount is expired - $return = true; - } - } - } - return apply_filters( 'edd_is_discount_expired', $return, $code_id ); +/** + * Retrieve the discount amount. + * + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field(). + * + * @param int $discount_id Discount ID. + * @return float $amount Discount amount. + */ +function edd_get_discount_amount( $discount_id = 0 ) { + return edd_get_discount_field( $discount_id, 'amount' ); } +/** + * Retrieve the discount type + * + * @since 1.4 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field(). + * + * @param int $discount_id Discount ID. + * @return string $type Discount type + */ +function edd_get_discount_type( $discount_id = 0 ) { + return edd_get_discount_field( $discount_id, 'type' ); +} /** - * Is Discount Started + * Retrieve the products the discount cannot be applied to. * - * Checks whether a discount code is available yet (start date). + * @since 1.9 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() * - * @access public - * @since 1.0 - * @return void -*/ + * @param int $discount_id Discount ID. + * @return array $excluded_products IDs of the required products. + */ +function edd_get_discount_excluded_products( $discount_id = 0 ) { + $discount = edd_get_discount( $discount_id ); -function edd_is_discount_started( $code_id = null ) { - $discount = edd_get_discount($code_id); - $return = false; - if($discount) { - if(isset($discount['start']) && $discount['start'] != '') { - $start_date = strtotime($discount['start']); - if($start_date < time()) { - // discount has pased the start date - $return = true; - } - } else { - // no start date for this discount, so has to be true - $return = true; - } - } - return apply_filters( 'edd_is_discount_started', $return, $code_id ); + return $discount instanceof EDD_Discount ? $discount->excluded_products : array(); } +/** + * Retrieve the discount product requirements. + * + * @since 1.5 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() + * + * @param int $discount_id Discount ID. + * @return array $product_reqs IDs of the required products. + */ +function edd_get_discount_product_reqs( $discount_id = 0 ) { + $discount = edd_get_discount( $discount_id ); + + return $discount instanceof EDD_Discount ? $discount->product_reqs : array(); +} /** - * Is Discount Maxed Out + * Retrieve the product condition. + * + * @since 1.5 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field() * - * Checks to see if a discount has uses left. + * @param int $discount_id Discount ID. * - * @access public - * @since 1.0 - * @return void -*/ + * @return string Product condition. + */ +function edd_get_discount_product_condition( $discount_id = 0 ) { + $discount = edd_get_discount( $discount_id ); -function edd_is_discount_maxed_out( $code_id = null ) { - $discount = edd_get_discount($code_id); - $return = false; - if($discount) { - $uses = isset($discount['uses']) ? $discount['uses'] : 0; - // large number that will never be reached - $max_uses = isset($discount['max']) ? $discount['max'] : 99999999; - // should never be greater than, but just in case - if($uses >= $max_uses && $max_uses != '' && isset($discount['max'])) { - // discount is maxed out - $return = true; - } - } - return apply_filters( 'edd_is_discount_maxed_out', $return, $code_id ); + return $discount instanceof EDD_Discount ? $discount->product_condition : ''; +} + +/** + * Retrieves the discount status label. + * + * @since 2.9 + * + * @param int $discount_id Discount ID. + * @return string Product condition. + */ +function edd_get_discount_status_label( $discount_id = null ) { + $discount = edd_get_discount( $discount_id ); + + return $discount instanceof EDD_Discount ? $discount->get_status_label() : ''; } +/** + * Check if a discount is not global. + * + * By default discounts are applied to all products in the cart. Non global discounts are + * applied only to the products selected as requirements. + * + * @since 1.5 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Please use edd_get_discount_scope() instead. + * + * @param int $discount_id Discount ID. + * + * @return boolean Whether or not discount code is not global. + */ +function edd_is_discount_not_global( $discount_id = 0 ) { + return ( 'not_global' === edd_get_discount_field( $discount_id, 'scope' ) ); +} + +/** + * Retrieve the discount scope. + * + * By default this will return "global" as discounts are applied to all products in the cart. Non global discounts are + * applied only to the products selected as requirements. + * + * @since 3.0 + * + * @param int $discount_id Discount ID. + * + * @return string global or not_global. + */ +function edd_get_discount_scope( $discount_id = 0 ) { + return edd_get_discount_field( $discount_id, 'scope' ); +} /** - * Is Cart Minimum Met + * Checks whether a discount code is expired. * - * Checks to see if the minimum purchase amount has been met + * @since 1.0 + * @since 2.6.11 Added $update parameter. + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() * - * @access public - * @since 1.1.7 - * @return void -*/ + * @param int $discount_id Discount ID. + * @param bool $update Update the discount to expired if an one is found but has an active status. + * @return bool Whether on not the discount has expired. + */ +function edd_is_discount_expired( $discount_id = 0, $update = true ) { + $discount = edd_get_discount( $discount_id ); + return ! empty( $discount->id ) + ? $discount->is_expired( $update ) + : false; +} -function edd_discount_is_min_met( $code_id = null ) { - $discount = edd_get_discount($code_id); - $return = false; - if($discount) { - $min = isset($discount['min_price']) ? $discount['min_price'] : 0; - $cart_amount = edd_get_cart_amount(); +/** + * Checks whether a discount code is available to use yet (start date). + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() + * + * @param int $discount_id Discount ID. + * @param bool $set_error Whether an error message be set in session. + * @return bool Is discount started? + */ +function edd_is_discount_started( $discount_id = 0, $set_error = true ) { + $discount = edd_get_discount( $discount_id ); + return ! empty( $discount->id ) + ? $discount->is_started( $set_error ) + : false; +} - if( (float)$cart_amount >= (float)$min ) { - // minimum has been met - $return = true; - } - } - return apply_filters( 'edd_is_discount_min_met', $return, $code_id ); +/** + * Is Discount Maxed Out. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() + * + * @param int $discount_id Discount ID. + * @param bool $set_error Whether an error message be set in session. + * @return bool Is discount maxed out? + */ +function edd_is_discount_maxed_out( $discount_id = 0, $set_error = true ) { + $discount = edd_get_discount( $discount_id ); + return ! empty( $discount->id ) + ? $discount->is_maxed_out( $set_error ) + : false; +} + +/** + * Checks to see if the minimum purchase amount has been met. + * + * @since 1.1.7 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() + * + * @param int $discount_id Discount ID. + * @param bool $set_error Whether an error message be set in session. + * @return bool Whether the minimum amount has been met or not. + */ +function edd_discount_is_min_met( $discount_id = 0, $set_error = true ) { + $discount = edd_get_discount( $discount_id ); + return ! empty( $discount->id ) + ? $discount->is_min_price_met( $set_error ) + : false; } +/** + * Is the discount limited to a single use per customer? + * + * @since 1.5 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_field() + * + * @param int $discount_id Discount ID. + * + * @return bool Whether the discount is single use or not. + */ +function edd_discount_is_single_use( $discount_id = 0 ) { + return (bool) edd_get_discount_field( $discount_id, 'once_per_customer' ); +} /** - * Is Discount Used + * Checks to see if the required products are in the cart + * + * @since 1.5 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() * + * @param int $discount_id Discount ID. + * @param bool $set_error Whether an error message be set in session. + * @return bool Are required products in the cart for the discount to hold. + */ +function edd_discount_product_reqs_met( $discount_id = 0, $set_error = true ) { + $discount = edd_get_discount( $discount_id ); + + return $discount instanceof EDD_Discount && $discount->is_product_requirements_met( $set_error ); +} + +/** * Checks to see if a user has already used a discount. * - * @access public - * @since 1.1.5 - * @return bool -*/ + * @since 1.1.5 + * @since 1.5 Added $discount_id parameter. + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount() + * + * @param string $code Discount Code. + * @param string $user User info. + * @param int $discount_id Discount ID. + * @param bool $set_error Whether an error message be set in session + * + * @return bool $return Whether the the discount code is used. + */ +function edd_is_discount_used( $code = null, $user = '', $discount_id = 0, $set_error = true ) { + $discount = ( null == $code ) + ? edd_get_discount( $discount_id ) + : edd_get_discount_by_code( $code ); + + return $discount instanceof EDD_Discount && $discount->is_used( $user, $set_error ); +} -function edd_is_discount_used( $code = null, $email = '' ) { +/** + * Check whether a discount code is valid (when purchasing). + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_by_code() + * + * @param string $code Discount Code. + * @param string $user User info. + * @param bool $set_error Whether an error message be set in session. + * @return bool Whether the discount code is valid. + */ +function edd_is_discount_valid( $code = '', $user = '', $set_error = true ) { + $discount = edd_get_discount_by_code( $code ); + + if ( ! empty( $discount->id ) ) { + return $discount->is_valid( $user, $set_error ); + } elseif ( $set_error ) { + edd_set_error( 'edd-discount-error', _x( 'This discount is invalid.', 'error for when a discount is invalid based on its configuration', 'easy-digital-downloads' ) ); + return false; + } else { + return false; + } +} - $return = false; - $query_args = array( - 'post_type' => 'edd_payment', - 'meta_query' => array( - array( - 'key' => '_edd_payment_user_email', - 'value' => $email, - 'compare' => '=' - ) - ) +/** + * Retrieves a discount ID from the code. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_by_code() + * + * @param string $code Discount code. + * @return int|bool Discount ID, or false if discount does not exist. + */ +function edd_get_discount_id_by_code( $code = '' ) { + $discount = edd_get_discount_by_code( $code ); + + return ( $discount instanceof EDD_Discount ) ? $discount->id : false; +} + +/** + * Get Discounted Amount. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_by_code() + * + * @param string $code Code to calculate a discount for. + * @param mixed string|int $base_price Price before discount. + * @return string Amount after discount. + */ +function edd_get_discounted_amount( $code = '', $base_price = 0 ) { + $discount = edd_get_discount_by_code( $code ); + + return ! empty( $discount->id ) + ? $discount->get_discounted_amount( $base_price ) + : $base_price; +} + +/** + * Increases the use count of a discount code. + * + * @since 1.0 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_by_code() + * + * @param string $code Discount code to be incremented. + * @return int New usage. + */ +function edd_increase_discount_usage( $code = '' ) { + $discount = edd_get_discount_by_code( $code ); + + // Increase if discount exists + return ! empty( $discount->id ) + ? (int) $discount->increase_usage() + : false; +} + +/** + * Decreases the use count of a discount code. + * + * @since 2.5.7 + * @since 2.7 Updated to use EDD_Discount object. + * @since 3.0 Updated to call edd_get_discount_by_code() + * + * @param string $code Discount code to be decremented. + * @return int New usage. + */ +function edd_decrease_discount_usage( $code = '' ) { + $discount = edd_get_discount_by_code( $code ); + + // Decrease if discount exists + return ! empty( $discount->id ) + ? (int) $discount->decrease_usage() + : false; +} + +/** + * Format Discount Rate + * + * @since 1.0 + * @param string $type Discount code type + * @param string|int $amount Discount code amount + * @return string $amount Formatted amount + */ +function edd_format_discount_rate( $type = '', $amount = '' ) { + return ( 'flat' === $type ) + ? edd_currency_filter( edd_format_amount( $amount ) ) + : edd_format_amount( $amount ) . '%'; +} + +/** + * Retrieves a discount amount for an item. + * + * Calculates an amount based on the context of other items. + * + * @since 3.0 + * + * @global float $edd_flat_discount_total Track flat rate discount total for penny adjustments. + * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/2757 + * + * @param array $item { + * Order Item data, matching Cart line item format. + * + * @type string $id Download ID. + * @type array $options { + * Download options. + * + * @type string $price_id Download Price ID. + * } + * @type int $quantity Purchase quantity. + * } + * @param array $items All items (including item being calculated). + * @param \EDD_Discount[]|string[] $discounts Discount to determine adjustment from. + * A discount code can be passed as a string. + * @param int $item_unit_price (Optional) Pass in a defined price for a specific context, such as the cart. + * @return float Discount amount. 0 if Discount is invalid or no Discount is applied. + */ +function edd_get_item_discount_amount( $item, $items, $discounts, $item_unit_price = false ) { + global $edd_flat_discount_total; + + // Validate item. + if ( empty( $item ) || empty( $item['id'] ) ) { + return 0; + } + + if ( ! isset( $item['quantity'] ) ) { + return 0; + } + + if ( ! isset( $item['options'] ) ) { + $item['options'] = array(); + + /* + * Support for variable pricing when the `item_number` key is set (cart details). + */ + if ( isset( $item['item_number']['options'] ) ) { + $item['options'] = $item['item_number']['options']; + } + } + + // Validate and normalize Discounts. + $discounts = array_map( + function( $discount ) { + // Convert a Discount code to a Discount object. + if ( is_string( $discount ) ) { + $discount = edd_get_discount_by_code( $discount ); + } + + if ( ! $discount instanceof \EDD_Discount ) { + return false; + } + + return $discount; + }, + $discounts ); - $discounts_query = new WP_Query($query_args); // Get all payments with matching email - if( $discounts_query->have_posts() ) { - foreach ( $discounts_query->posts as $payment ) { - // Check all matching payments for discount code. - $payment_meta = get_post_meta( $payment->ID, '_edd_payment_meta', true ); - $user_info = maybe_unserialize( $payment_meta['user_info'] ); - if ($user_info['discount'] == $code){ - $return = true; + + $discounts = array_filter( $discounts ); + + if ( false === $item_unit_price ) { + // Determine the price of the item. + if ( edd_has_variable_prices( $item['id'] ) ) { + // Mimics the original behavior of `\EDD_Cart::get_item_amount()` that + // does not fallback to the first Price ID if none is provided. + if ( ! isset( $item['options']['price_id'] ) ) { + return 0; + } + + $item_unit_price = edd_get_price_option_amount( $item['id'], $item['options']['price_id'] ); + } else { + $item_unit_price = edd_get_download_price( $item['id'] ); + } + } + + $item_amount = ( $item_unit_price * $item['quantity'] ); + $discount_amount = 0; + + foreach ( $discounts as $discount ) { + $reqs = $discount->get_product_reqs(); + $excluded_products = $discount->get_excluded_products(); + + // Make sure requirements are set and that this discount shouldn't apply to the whole cart. + if ( ! empty( $reqs ) && 'global' !== $discount->get_scope() ) { + // This is a product(s) specific discount. + foreach ( $reqs as $download_id ) { + if ( $download_id == $item['id'] && ! in_array( $item['id'], $excluded_products ) ) { + $discount_amount += ( $item_amount - $discount->get_discounted_amount( $item_amount ) ); + } + } + } else { + // This is a global cart discount. + if ( ! in_array( $item['id'], $excluded_products ) ) { + if ( 'flat' === $discount->get_type() ) { + // In order to correctly record individual item amounts, global flat rate discounts + // are distributed across all items. + // + // The discount amount is divided by the number of items in the cart and then a + // portion is evenly applied to each item. + $items_amount = 0; + + foreach ( $items as $i ) { + + if ( ! in_array( $i['id'], $excluded_products ) ) { + $i_amount = 0; + + if ( edd_has_variable_prices( $i['id'] ) ) { + $price_id = isset( $i['options']['price_id'] ) ? $i['options']['price_id'] : $i['item_number']['options']['price_id']; + $i_amount = edd_get_price_option_amount( $i['id'], $price_id ); + } else { + $i_amount = edd_get_download_price( $i['id'] ); + } + + $items_amount += ( $i_amount * $i['quantity'] ); + } + } + + $subtotal_percent = ! empty( $items_amount ) ? ( $item_amount / $items_amount ) : 0; + $discount_amount += ( $discount->get_amount() * $subtotal_percent ); + + $edd_flat_discount_total += round( $discount_amount, edd_currency_decimal_filter() ); + + if ( $item['id'] === end( $items )['id'] && $edd_flat_discount_total < $discount->get_amount() ) { + $adjustment = ( $discount->get_amount() - $edd_flat_discount_total ); + $discount_amount += $adjustment; + } + + if ( $discount_amount > $item_amount ) { + $discount_amount = $item_amount; + } + } else { + $discount_amount += ( $item_amount - $discount->get_discounted_amount( $item_amount ) ); + } } } } - return apply_filters( 'edd_is_discount_used', $return, $code, $email ); + + return $discount_amount; } +/** Cart **********************************************************************/ /** - * Is Discount Valid - * - * Check whether a discount code is valid (when purchasing). + * Set the active discount for the shopping cart * - * @access public - * @since 1.0 - * @return void -*/ + * @since 1.4.1 + * @param string $code Discount code + * @return string[] All currently active discounts + */ +function edd_set_cart_discount( $code = '' ) { -function edd_is_discount_valid( $code = '', $email = '') { + // Get all active cart discounts + if ( edd_multiple_discounts_allowed() ) { + $discounts = edd_get_cart_discounts(); - $return = false; - $discount_id = edd_get_discount_id_by_code($code); - $email = trim($email); - - if($discount_id !== false && $email !== "") { - if( - edd_is_discount_active( $discount_id ) && - edd_is_discount_started( $discount_id ) && - !edd_is_discount_maxed_out( $discount_id ) && - !edd_is_discount_used( $code, $email ) && - edd_discount_is_min_met( $discount_id ) - ) { - $return = true; + // Only one discount allowed per purchase, so override any existing + } else { + $discounts = false; + } + + if ( $discounts ) { + $key = array_search( strtolower( $code ), array_map( 'strtolower', $discounts ) ); + + // Can't set the same discount more than once + if ( false !== $key ) { + unset( $discounts[ $key ] ); + } + $discounts[] = $code; + } else { + $discounts = array(); + $discounts[] = $code; + } + + EDD()->session->set( 'cart_discounts', implode( '|', $discounts ) ); + + do_action( 'edd_cart_discount_set', $code, $discounts ); + do_action( 'edd_cart_discounts_updated', $discounts ); + + return $discounts; +} + +/** + * Remove an active discount from the shopping cart + * + * @since 1.4.1 + * @param string $code Discount code + * @return array $discounts All remaining active discounts + */ +function edd_unset_cart_discount( $code = '' ) { + $discounts = edd_get_cart_discounts(); + + if ( $discounts ) { + $discounts = array_map( 'strtoupper', $discounts ); + $key = array_search( strtoupper( $code ), $discounts ); + + if ( false !== $key ) { + unset( $discounts[ $key ] ); } + + $discounts = implode( '|', array_values( $discounts ) ); + // update the active discounts + EDD()->session->set( 'cart_discounts', $discounts ); } - return apply_filters( 'edd_is_discount_valid', $return, $discount_id, $code, $email ); + + do_action( 'edd_cart_discount_removed', $code, $discounts ); + do_action( 'edd_cart_discounts_updated', $discounts ); + + return $discounts; } +/** + * Remove all active discounts + * + * @since 1.4.1 + * @return void + */ +function edd_unset_all_cart_discounts() { + EDD()->cart->remove_all_discounts(); +} /** - * Get Discount By Code + * Retrieve the currently applied discount * - * Retrieves a discount code ID from the code. + * @since 1.4.1 + * @return array $discounts The active discount codes + */ +function edd_get_cart_discounts() { + return EDD()->cart->get_discounts(); +} + +/** + * Check if the cart has any active discounts applied to it * - * @access public - * @since 1.0 - * @param $code string The discount code to retrieve an ID for - * @return void -*/ + * @since 1.4.1 + * @return bool + */ +function edd_cart_has_discounts() { + return EDD()->cart->has_discounts(); +} -function edd_get_discount_id_by_code($code) { - $discounts = edd_get_discounts(); - $code_id = false; - if($discounts) { - foreach($discounts as $key => $discount) { - if( isset( $discount['code'] ) && trim($discount['code']) === trim($code)) { - $code_id = $key; +/** + * Retrieves the total discounted amount on the cart + * + * @since 1.4.1 + * + * @param bool $discounts Discount codes + * + * @return float|mixed|void Total discounted amount + */ +function edd_get_cart_discounted_amount( $discounts = false ) { + return EDD()->cart->get_discounted_amount( $discounts ); +} + +/** + * Get the discounted amount on a price + * + * @since 1.9 + * @param array $item Cart item array + * @param bool|string $discount False to use the cart discounts or a string to check with a discount code + * @return float The discounted amount + */ +function edd_get_cart_item_discount_amount( $item = array(), $discount = false ) { + return EDD()->cart->get_item_discount_amount( $item, $discount ); +} + +/** + * Outputs the HTML for all discounts applied to the cart + * + * @since 1.4.1 + * + * @return void + */ +function edd_cart_discounts_html() { + echo edd_get_cart_discounts_html(); +} + +/** + * Retrieves the HTML for all discounts applied to the cart + * + * @since 1.4.1 + * + * @param mixed $discounts Array of cart discounts. + * @return string + */ +function edd_get_cart_discounts_html( $discounts = false ) { + if ( ! $discounts ) { + $discounts = EDD()->cart->get_discounts(); + } + + if ( empty( $discounts ) ) { + return apply_filters( 'edd_get_cart_discounts_html', '', $discounts, 0, '' ); + } + + $html = _n( 'Discount', 'Discounts', count( $discounts ), 'easy-digital-downloads' ) . ': '; + + foreach ( $discounts as $discount ) { + $discount_id = edd_get_discount_id_by_code( $discount ); + $discount_amount = 0; + $items = EDD()->cart->get_contents_details(); + + if ( is_array( $items ) && ! empty( $items ) ) { + foreach ( $items as $key => $item ) { + $discount_amount += edd_get_item_discount_amount( $item, $items, array( $discount ), $item['item_price'] ); } } + + $type = edd_get_discount_type( $discount_id ); + $rate = edd_format_discount_rate( $type, edd_get_discount_amount( $discount_id ) ); + + $remove_url = add_query_arg( + array( + 'edd_action' => 'remove_cart_discount', + 'discount_id' => urlencode( $discount_id ), + 'discount_code' => urlencode( $discount ), + ), + edd_get_checkout_uri() + ); + + $discount_html = ''; + $discount_html .= "\n"; + $discount_amount = edd_currency_filter( edd_format_amount( $discount_amount ) ); + $discount_html .= "{$discount} – {$discount_amount}\n"; + if ( 'percent' === $type ) { + $discount_html .= "($rate)\n"; + } + $discount_html .= sprintf( + '%s', + esc_url( $remove_url ), + esc_attr( $discount ), + esc_attr__( 'Remove discount', 'easy-digital-downloads' ) + ); + $discount_html .= "\n"; + + $html .= apply_filters( 'edd_get_cart_discount_html', $discount_html, $discount, $rate, $remove_url ); } - return $code_id; + + return apply_filters( 'edd_get_cart_discounts_html', $html, $discounts, $rate, $remove_url ); } +/** + * Show the fully formatted cart discount + * + * Note the $formatted parameter was removed from the display_cart_discount() function + * within EDD_Cart in 2.7 as it was a redundant parameter. + * + * @since 1.4.1 + * @param bool $formatted + * @param bool $echo Echo? + * @return string $amount Fully formatted cart discount + */ +function edd_display_cart_discount( $formatted = false, $echo = false ) { + if ( ! $echo ) { + return EDD()->cart->display_cart_discount( $echo ); + } else { + EDD()->cart->display_cart_discount( $echo ); + } +} /** - * Get Discounted Amount + * Processes a remove discount from cart request * - * Gets the discounted price. + * @since 1.4.1 + * @return void + */ +function edd_remove_cart_discount() { + + // Get ID + $discount_id = isset( $_GET['discount_id'] ) + ? absint( $_GET['discount_id'] ) + : 0; + + // Get code + $discount_code = isset( $_GET['discount_code'] ) + ? urldecode( $_GET['discount_code'] ) + : ''; + + // Bail if either ID or code are empty + if ( empty( $discount_id ) || empty( $discount_code ) ) { + return; + } + + // Pre-3.0 pre action + do_action( 'edd_pre_remove_cart_discount', $discount_id ); + + edd_unset_cart_discount( $discount_code ); + + // Pre-3.0 post action + do_action( 'edd_post_remove_cart_discount', $discount_id ); + + // Redirect + edd_redirect( edd_get_checkout_uri() ); +} +add_action( 'edd_remove_cart_discount', 'edd_remove_cart_discount' ); + +/** + * Checks whether discounts are still valid when removing items from the cart * - * @access public - * @since 1.0 - * @param $code - string - the code to calculate a discount for - * @param $base_price - string/int the price before discount - * @return $discounted_price - string - the amount after discount -*/ + * If a discount requires a certain product, and that product is no longer in + * the cart, the discount is removed. + * + * @since 1.5.2 + * + * @param int $cart_key + */ +function edd_maybe_remove_cart_discount( $cart_key = 0 ) { -function edd_get_discounted_amount($code, $base_price) { + $discounts = edd_get_cart_discounts(); - $discount_id = edd_get_discount_id_by_code($code); - $discounts = edd_get_discounts(); - $type = $discounts[$discount_id]['type']; - $rate = $discounts[$discount_id]['amount']; - - if($type == 'flat') { - // set amount - $discounted_price = $base_price - $rate; - if( $discounted_price < 0 ) { - $discounted_price = 0; - } + if ( empty( $discounts ) ) { + return; + } - } else { - // percentage discount - $discounted_price = $base_price - ( $base_price * ( $rate / 100 ) ); + foreach ( $discounts as $discount ) { + if ( ! edd_is_discount_valid( $discount ) ) { + edd_unset_cart_discount( $discount ); + } } - return apply_filters( 'edd_discounted_amount', $discounted_price ); } - +add_action( 'edd_post_remove_from_cart', 'edd_maybe_remove_cart_discount' ); /** - * Increase Discount Usage + * Checks whether multiple discounts can be applied to the same purchase * - * Increases the use count of a discount code. + * @since 1.7 + * @return bool + */ +function edd_multiple_discounts_allowed() { + $ret = edd_get_option( 'allow_multiple_discounts', false ); + return (bool) apply_filters( 'edd_multiple_discounts_allowed', $ret ); +} + +/** + * Listens for a discount and automatically applies it if present and valid * - * @access public - * @since 1.0 - * @param $code string - the discount code to be incremented - * @return int - the new use count -*/ - -function edd_increase_discount_usage($code) { - $discount_id = edd_get_discount_id_by_code($code); - $discounts = edd_get_discounts(); - $uses = isset($discounts[$discount_id]['uses']) ? $discounts[$discount_id]['uses'] : false; - if($uses) { - $uses++; - } else { - $uses = 1; + * @since 2.0 + * @return void + */ +function edd_listen_for_cart_discount() { + + // Bail if in admin + if ( is_admin() ) { + return; + } + + // Array stops the bulk delete of discount codes from storing as a preset_discount + if ( empty( $_REQUEST['discount'] ) || is_array( $_REQUEST['discount'] ) ) { + return; } - $discounts[$discount_id]['uses'] = $uses; - return update_option('edd_discounts', $discounts); + + $code = preg_replace('/[^a-zA-Z0-9-_]+/', '', $_REQUEST['discount'] ); + + EDD()->session->set( 'preset_discount', $code ); } +add_action( 'init', 'edd_listen_for_cart_discount', 0 ); + +/** + * Applies the preset discount, if any. This is separated from edd_listen_for_cart_discount() in order to allow items to be + * added to the cart and for it to persist across page loads if necessary + * + * @return void + */ +function edd_apply_preset_discount() { + + // Bail if in admin + if ( is_admin() ) { + return; + } + + $code = sanitize_text_field( EDD()->session->get( 'preset_discount' ) ); + + if ( empty( $code ) ) { + return; + } + + if ( ! edd_is_discount_valid( $code, '', false ) ) { + return; + } + + $code = apply_filters( 'edd_apply_preset_discount', $code ); + edd_set_cart_discount( $code ); + + EDD()->session->set( 'preset_discount', null ); +} +add_action( 'init', 'edd_apply_preset_discount', 999 ); /** - * Format Discount Rate + * Validate discount code, optionally against an array of download IDs. + * Note: this function does not evaluate whether a current user can use the discount, + * or check the discount minimum cart requirement. + * + * @param int $discount_id Discount ID. + * @param array $download_ids Array of download IDs. * - * @access public - * @since 1.0 - * @return string -*/ + * @return boolean True if discount holds, false otherwise. + */ +function edd_validate_discount( $discount_id = 0, $download_ids = array() ) { -function edd_format_discount_rate($type, $amount) { - if($type == 'flat') { - return edd_currency_filter($amount); - } else { - return $amount . '%'; + // Bail if discount ID not passed. + if ( empty( $discount_id ) ) { + return false; + } + + $discount = edd_get_discount( $discount_id ); + + // Bail if discount not found. + if ( ! $discount ) { + return false; + } + + // Check if discount is active, started, and not maxed out. + if ( ! $discount->is_active( true, false ) || ! $discount->is_started( false ) || $discount->is_maxed_out( false ) ) { + return false; + } + + $product_requirements = $discount->get_product_reqs(); + $excluded_products = $discount->get_excluded_products(); + + // Return true if there are no requirements/excluded products set. + if ( empty( $product_requirements ) && empty( $excluded_products ) ) { + return true; } -} \ No newline at end of file + + // At this point, we assume the discount is valid. + $is_valid = true; + + $product_requirements = array_map( 'absint', $product_requirements ); + asort( $product_requirements ); + $product_requirements = array_filter( array_values( $product_requirements ) ); + + if ( ! empty( $product_requirements ) ) { + + $matches = array_intersect( $product_requirements, $download_ids ); + + switch ( $discount->get_product_condition() ) { + case 'all': + $is_valid = count( $matches ) === count( $product_requirements ); + break; + default: + $is_valid = 0 < count( $matches ); + } + } + + $excluded_products = array_map( 'absint', $excluded_products ); + asort( $excluded_products ); + $excluded_products = array_filter( array_values( $excluded_products ) ); + + if ( ! empty( $excluded_products ) ) { + $is_valid = false === (bool) array_intersect( $excluded_products, $download_ids ); + } + + /** + * Filters the validity of a discount. + * + * @since 3.0 + * + * @param bool $is_valid True if valid, false otherwise. + * @param \EDD_Discount $discount Discount object. + * @param array $download_ids Download IDs to check against. + */ + return apply_filters( 'edd_validate_discount', $is_valid, $discount, $download_ids ); +} diff --git a/includes/download-functions.php b/includes/download-functions.php index 0bbad8ca82e..19615662afe 100755 --- a/includes/download-functions.php +++ b/includes/download-functions.php @@ -2,601 +2,1625 @@ /** * Download Functions * - * @package Easy Digital Downloads - * @subpackage Download Functions - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Functions + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ + +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; + +/** + * Retrieve a download by a given field. + * + * @since 2.0 + * + * @param string $field Field to retrieve the download with. + * @param mixed $value Value of the row. + * + * @return WP_Post|false WP_Post object if download found, false otherwise. + */ +function edd_get_download_by( $field = '', $value = '' ) { + + // Bail if empty values passed. + if ( empty( $field ) || empty( $value ) ) { + return false; + } + + switch ( strtolower( $field ) ) { + case 'id': + $download = get_post( $value ); + + if ( 'download' !== get_post_type( $download ) ) { + return false; + } + + break; + + case 'slug': + case 'name': + $download = get_posts( array( + 'post_type' => 'download', + 'name' => $value, + 'posts_per_page' => 1, + 'post_status' => 'any', + ) ); + + if ( $download ) { + $download = $download[0]; + } + + break; + + case 'sku': + $download = get_posts( array( + 'post_type' => 'download', + 'meta_key' => 'edd_sku', + 'meta_value' => $value, + 'posts_per_page' => 1, + 'post_status' => 'any', + ) ); + + if ( $download ) { + $download = $download[0]; + } + + break; + + default: + return false; + } + + return $download ?: false; +} + +/** + * Retrieves a download post object by ID or slug. + * + * @since 1.0 + * @since 2.9 - Return an EDD_Download object. + * + * @param int $download_id Download ID. + * @return EDD_Download|null EDD_Download object if found, null otherwise. + */ +function edd_get_download( $download_id = 0 ) { + $download = null; + + if ( is_numeric( $download_id ) ) { + $found_download = new EDD_Download( $download_id ); + + if ( ! empty( $found_download->ID ) ) { + $download = $found_download; + } + + // Fetch download by name. + } else { + $args = array( + 'post_type' => 'download', + 'name' => $download_id, + 'post_per_page' => 1, + 'fields' => 'ids', + ); + + $downloads = new WP_Query( $args ); + + if ( is_array( $downloads->posts ) && ! empty( $downloads->posts ) ) { + $download_id = $downloads->posts[0]; + + $download = new EDD_Download( $download_id ); + } + } + + return $download; +} + +/** + * Checks whether or not a download is free. + * + * @since 2.1 + * + * @param int $download_id Download ID. + * @param int $price_id Optional. Price ID. + * @return bool $is_free True if the product is free, false if the product is not free or the check fails + */ +function edd_is_free_download( $download_id = 0, $price_id = false ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + + return $download + ? $download->is_free( $price_id ) + : false; +} + +/** + * Return the name of a download. + * + * Pass a price ID to append the specific price variation name. + * + * @since 3.0 + * + * @param int $download_id + * @param int|null $price_id + * + * @return false|string + */ +function edd_get_download_name( $download_id = 0, $price_id = null ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) || ! is_numeric( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + + // Bail if the download cannot be retrieved. + if ( ! $download instanceof EDD_Download ) { + return false; + } + + // Get the download title + $retval = $download->get_name(); + + // Check for variable pricing + if ( $download->has_variable_prices() && is_numeric( $price_id ) ) { + + // Check for price option name + $price_name = edd_get_price_option_name( $download_id, $price_id ); + + // Product has prices + if ( ! empty( $price_name ) ) { + $retval .= ' — ' . $price_name; + } + } + + /** + * Override the download name. + * + * @since 3.0 + * + * @param string $retval The download name. + * @param int $id The download ID. + * @param int $price_id The price ID, if any. + */ + return apply_filters( 'edd_get_download_name', $retval, $download_id, $price_id ); +} + +/** + * Returns the price of a download, but only for non-variable priced downloads. + * + * @since 1.0 + * + * @param int $download_id Download ID. + * @return string|int Price of the download. + */ +function edd_get_download_price( $download_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + + return $download + ? $download->get_price() + : 0; +} + +/** + * Displays a formatted price for a download. + * + * @since 1.0 + * + * @param int $download_id Download ID. + * @param bool $echo Optional. Whether to echo or return the result. Default true. + * @param int $price_id Optional. Price ID. + * + * @return string Download price if $echo set to false. + */ +function edd_price( $download_id = 0, $echo = true, $price_id = false ) { + + // Attempt to get the ID of the current item in the WordPress loop. + if ( empty( $download_id ) || ! is_numeric( $download_id ) ) { + $download_id = get_the_ID(); + } + + // Variable prices + if ( edd_has_variable_prices( $download_id ) ) { + + // Get the price variations + $prices = edd_get_variable_prices( $download_id ); + + // Use the amount for the price ID + if ( is_numeric( $price_id ) && isset( $prices[ $price_id ] ) ) { + $price = edd_get_price_option_amount( $download_id, $price_id ); + + // Maybe use the default variable price + } elseif ( $default = edd_get_default_variable_price( $download_id ) ) { + $price = edd_get_price_option_amount( $download_id, $default ); + + // Maybe guess the lowest price + } else { + $price = edd_get_lowest_price_option( $download_id ); + } + + // Single price (not variable) + } else { + $price = edd_get_download_price( $download_id ); + } + + // Filter the price (already sanitized) + $price = apply_filters( 'edd_download_price', $price, $download_id, $price_id ); + + // Format the price (do not escape $price) + $formatted_price = '' . $price . ''; + $formatted_price = apply_filters( 'edd_download_price_after_html', $formatted_price, $download_id, $price, $price_id ); + + // Echo or return + if ( ! empty( $echo ) ) { + echo $formatted_price; // WPCS: XSS ok. + } else { + return $formatted_price; + } +} +add_filter( 'edd_download_price', 'edd_format_amount', 10 ); +add_filter( 'edd_download_price', 'edd_currency_filter', 20 ); + +/** + * Retrieves the final price of a downloadable product after purchase. + * This price includes any necessary discounts that were applied + * + * @since 1.0 + * @param int $download_id ID of the download + * @param array $user_purchase_info - an array of all information for the payment + * @param string $amount_override a custom amount that over rides the 'edd_price' meta, used for variable prices + * @return string - the price of the download + */ +function edd_get_download_final_price( $download_id, $user_purchase_info, $amount_override = null ) { + if ( is_null( $amount_override ) ) { + $original_price = get_post_meta( $download_id, 'edd_price', true ); + } else { + $original_price = $amount_override; + } + + if ( isset( $user_purchase_info['discount'] ) && 'none' !== $user_purchase_info['discount'] ) { + + // If the discount was a percentage, we modify the amount. + // Flat rate discounts are ignored + if ( EDD_Discount::FLAT !== edd_get_discount_type( edd_get_discount_id_by_code( $user_purchase_info['discount'] ) ) ) { + $price = edd_get_discounted_amount( $user_purchase_info['discount'], $original_price ); + } else { + $price = $original_price; + } + } else { + $price = $original_price; + } + + // Filter & return. + return apply_filters( 'edd_final_price', $price, $download_id, $user_purchase_info ); +} + +/** + * Retrieves the variable prices for a download. + * + * @since 1.2 + * + * @param int $download_id Download ID. + * @return array|false Variable prices if found, false otherwise. + */ +function edd_get_variable_prices( $download_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + + return $download + ? $download->get_prices() + : false; +} + +/** + * Checks to see if a download has variable prices enabled. + * + * @since 1.0.7 + * + * @param int $download_id Download ID. + * @return bool True if the download has variable prices, false otherwise. + */ +function edd_has_variable_prices( $download_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = new EDD_Download( $download_id ); + + return $download + ? $download->has_variable_prices() + : false; +} + +/** + * Returns the default price ID for variable pricing, or the first price if + * none set. + * + * @since 2.2 + * @since 3.1.2 Moved this behavior into the EDD_Download class as it really does belong there. + * + * @param int $download_id Download ID. + * @return int|null The default price ID, or false if the product does not have variable prices. + */ +function edd_get_default_variable_price( $download_id = 0 ) { + + // Bail if no download ID was passed. + if ( ! is_numeric( $download_id ) || empty( $download_id ) ) { + return null; + } + + $download = new EDD_Download( $download_id ); + + return $download->get_default_price_id(); +} + +/** + * Retrieves the name of a variable price option. + * + * @since 1.0.9 + * @since 3.0 Renamed $payment_id parameter to $order_id. + * + * @param int $download_id Download ID. + * @param int $price_id Price ID. + * @param int $order_id Optional. Order ID for use in filters. + * + * @return string $price_name Name of the price option. + */ +function edd_get_price_option_name( $download_id = 0, $price_id = 0, $order_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + // Fetch variable prices. + $prices = edd_get_variable_prices( $download_id ); + + $price_name = ''; + + if ( $prices && is_array( $prices ) ) { + if ( isset( $prices[ $price_id ] ) ) { + $price_name = $prices[ $price_id ]['name']; + } + } + + return apply_filters( 'edd_get_price_option_name', $price_name, $download_id, $order_id, $price_id ); +} + +/** + * Retrieves the amount for a variable price option. + * + * @since 1.8.2 + * + * @param int $download_id Download ID. + * @param int $price_id Price ID. + * + * @return float $amount Price option amount. + */ +function edd_get_price_option_amount( $download_id = 0, $price_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + // Fetch variable prices. + $prices = edd_get_variable_prices( $download_id ); + + // Set default prices. + $amount = 0.00; + + if ( $prices && is_array( $prices ) ) { + if ( isset( $prices[ $price_id ] ) ) { + $amount = $prices[ $price_id ]['amount']; + } + } + + // Filter & return. + return apply_filters( 'edd_get_price_option_amount', edd_sanitize_amount( $amount ), $download_id, $price_id ); +} + +/** + * Retrieve the lowest price option of a variable priced download. + * + * @since 1.4.4 + * + * @param int $download_id Download ID. + * @return float Amount of the lowest price option. + */ +function edd_get_lowest_price_option( $download_id = 0 ) { + + // Attempt to get the ID of the current item in the WordPress loop. + if ( empty( $download_id ) ) { + $download_id = get_the_ID(); + } + + // Bail if download ID is still empty. + if ( empty( $download_id ) ) { + return false; + } + + // Return download price if variable prices do not exist for download. + if ( ! edd_has_variable_prices( $download_id ) ) { + return edd_get_download_price( $download_id ); + } + + // Set lowest to 0. + $lowest = 0.00; + $prices = edd_get_variable_prices( $download_id ); + $list_handler = new EDD\Utils\ListHandler( $prices ); + $min_key = $list_handler->search( 'amount', 'min' ); + if ( false !== $min_key ) { + $lowest = $prices[ $min_key ]['amount']; + } + + return edd_sanitize_amount( $lowest ); +} + +/** + * Retrieves the ID for the cheapest price option of a variable priced download. + * + * @since 2.2 + * + * @param int $download_id Download ID. + * @return int|false ID of the lowest price, false if download does not exist. + */ +function edd_get_lowest_price_id( $download_id = 0 ) { + + // Attempt to get the ID of the current item in the WordPress loop. + if ( empty( $download_id ) ) { + $download_id = get_the_ID(); + } + + // Bail if download ID is still empty. + if ( empty( $download_id ) ) { + return false; + } + + // Return download price if variable prices do not exist for download. + if ( ! edd_has_variable_prices( $download_id ) ) { + return edd_get_download_price( $download_id ); + } + + $list_handler = new EDD\Utils\ListHandler( edd_get_variable_prices( $download_id ) ); + $min_key = $list_handler->search( 'amount', 'min' ); + + return false !== $min_key ? absint( $min_key ) : false; +} + +/** + * Retrieves most expensive price option of a variable priced download + * + * @since 1.4.4 + * @param int $download_id ID of the download + * @return float Amount of the highest price + */ +function edd_get_highest_price_option( $download_id = 0 ) { + + // Attempt to get the ID of the current item in the WordPress loop. + if ( empty( $download_id ) ) { + $download_id = get_the_ID(); + } + + // Bail if download ID is still empty. + if ( empty( $download_id ) ) { + return false; + } + + // Return download price if variable prices do not exist for download. + if ( ! edd_has_variable_prices( $download_id ) ) { + return edd_get_download_price( $download_id ); + } + + // Set highest to 0. + $highest = 0.00; + $prices = edd_get_variable_prices( $download_id ); + $list_handler = new EDD\Utils\ListHandler( $prices ); + $max_key = $list_handler->search( 'amount', 'max' ); + if ( false !== $max_key ) { + $highest = $prices[ $max_key ]['amount']; + } + + return edd_sanitize_amount( $highest ); +} + +/** + * Retrieves a price from from low to high of a variable priced download. + * + * @since 1.4.4 + * + * @param int $download_id Download ID. + * @return string $range A fully formatted price range. + */ +function edd_price_range( $download_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $low = edd_get_lowest_price_option( $download_id ); + $high = edd_get_highest_price_option( $download_id ); + $range = '' . edd_currency_filter( edd_format_amount( $low ) ) . ''; + $range .= ' – '; + $range .= '' . edd_currency_filter( edd_format_amount( $high ) ) . ''; + + return apply_filters( 'edd_price_range', $range, $download_id, $low, $high ); +} + +/** + * Checks to see if multiple price options can be purchased at once. + * + * @since 1.4.2 + * + * @param int $download_id Download ID. + * @return bool True if multiple price options can be purchased at once, false otherwise. + */ +function edd_single_price_option_mode( $download_id = 0 ) { + + // Attempt to get the ID of the current item in the WordPress loop. + if ( empty( $download_id ) ) { + $download_id = get_the_ID(); + } + + // Bail if download ID is still empty. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + return $download + ? $download->is_single_price_mode() + : false; +} + +/** + * Get product types. + * + * @since 1.8 + * + * @return array $types Download types. + */ +function edd_get_download_types() { + $types = array( + '0' => __( 'Single Product', 'easy-digital-downloads' ), + 'bundle' => __( 'Bundle', 'easy-digital-downloads' ), + ); + + return apply_filters( 'edd_download_types', $types ); +} + +/** + * Get the download type: either `default` or `bundled`. + * + * @since 1.6 + * + * @param int $download_id Download ID. + * @return string $type Download type. + */ +function edd_get_download_type( $download_id = 0 ) { + $download = edd_get_download( $download_id ); + + return $download + ? $download->type + : false; +} + +/** + * Determines if a product is a bundle. + * + * @since 1.6 + * + * @param int $download_id Download ID. + * @return bool True if a bundle, false otherwise. + */ +function edd_is_bundled_product( $download_id = 0 ) { + $download = edd_get_download( $download_id ); + + return $download + ? $download->is_bundled_download() + : false; +} + + +/** + * Retrieves the product IDs of bundled products. + * + * @since 1.6 + * @since 2.7 Added $price_id parameter. + * + * @param int $download_id Download ID. + * @param int $price_id Optional. Price ID. Default null. + * + * @return array|false Products in the bundle, false if download does not exist. + */ +function edd_get_bundled_products( $download_id = 0, $price_id = null ) { + $download = edd_get_download( $download_id ); + + // Bail if download does not exist. + if ( ! $download ) { + return false; + } + + if ( null !== $price_id ) { + return $download->get_variable_priced_bundled_downloads( $price_id ); + } else { + return $download->bundled_downloads; + } +} + +/** + * Returns the total earnings for a download. + * + * @since 1.0 + * + * @param int $download_id Download ID. + * @return float|false $earnings Download earnings, false if download not found. + */ +function edd_get_download_earnings_stats( $download_id = 0 ) { + $download = edd_get_download( $download_id ); + + return $download + ? $download->earnings + : false; +} + +/** + * Return the sales number for a download. + * + * @since 1.0 + * + * @param int $download_id Download ID. + * @return int|false Number of sales, false if download was not found. + */ +function edd_get_download_sales_stats( $download_id = 0 ) { + $download = edd_get_download( $download_id ); + + return $download + ? $download->sales + : false; +} + +/** + * Record a file download. + * + * @since 1.0 + * @since 3.0 Refactored to use new query methods. + * + * @param int $download_id Download ID. + * @param int $file_id File ID. + * @param array $user_info User information (deprecated). + * @param string $ip Optional. IP address. + * @param int $order_id Order ID. + * @param int $price_id Optional. Price ID, + * @param string $user_agent Optional. User agent. + * @return void + */ +function edd_record_download_in_log( $download_id = 0, $file_id = 0, $user_info = array(), $ip = '', $order_id = 0, $price_id = 0, $user_agent = '' ) { + $order = edd_get_order( $order_id ); + + if ( empty( $user_agent ) ) { + if ( ! class_exists( 'Browser' ) ) { + require_once EDD_PLUGIN_DIR . 'includes/libraries/browser.php'; + } + $browser = new Browser(); + $user_agent = $browser->getBrowser() . ' ' . $browser->getVersion() . '/' . $browser->getPlatform(); + } + + if ( empty( $ip ) ) { + $ip = edd_get_ip(); + } + + $file_id = absint( $file_id ); + $files = edd_get_download_files( $download_id ); + $file_name = ''; + + if ( is_array( $files ) ) { + foreach ( $files as $key => $file ) { + if ( absint( $key ) === $file_id ) { + $file_name = edd_get_file_name( $file ); + break; + } + } + } + + $log_id = edd_add_file_download_log( array( + 'product_id' => absint( $download_id ), + 'file_id' => $file_id, + 'order_id' => absint( $order_id ), + 'price_id' => absint( $price_id ), + 'customer_id' => $order->customer_id, + 'ip' => sanitize_text_field( $ip ), + 'user_agent' => $user_agent, + ) ); + + if ( $log_id && ! empty( $file_name ) ) { + edd_add_file_download_log_meta( $log_id, 'file_name', $file_name ); + } +} + +/** + * Delete log entries when deleting downloads. + * + * Removes all related log entries when a download is completely deleted. + * (Does not run when a download is trashed) + * + * @since 1.3.4 + * @since 3.0 Updated to use new query methods. + * + * @param int $download_id Download ID. + */ +function edd_remove_download_logs_on_delete( $download_id = 0 ) { + global $wpdb; + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return; + } + + // Ensure download ID is an integer. + $download_id = absint( $download_id ); + + // Bail if the post type is not `download`. + if ( 'download' !== get_post_type( $download_id ) ) { + return; + } + + // Delete file download logs. + $wpdb->delete( $wpdb->edd_logs_file_downloads, array( + 'product_id' => $download_id, + ), array( '%d' ) ); + // Delete logs. + $wpdb->delete( $wpdb->edd_logs, array( + 'object_id' => $download_id, + 'object_type' => 'download', + ), array( '%d', '%s' ) ); +} +add_action( 'delete_post', 'edd_remove_download_logs_on_delete' ); /** - * Get Download - * - * Retrieves a download post object by ID or slug. + * Recalculates both the net and gross sales and earnings for a download. * - * @access public - * @since 1.0 - * @return object -*/ + * @since 3.0 + * @param int $download_id + * @return void + */ +function edd_recalculate_download_sales_earnings( $download_id ) { + $download = edd_get_download( $download_id ); + if ( ! $download instanceof \EDD_Download ) { + return; + } + $download->recalculate_net_sales_earnings(); + $download->recalculate_gross_sales_earnings(); +} -function edd_get_download($download) { +/** + * Retrieves the average monthly earnings for a specific download. + * + * @since 1.3 + * + * @param int $download_id Download ID. + * @return float $earnings Average monthly earnings. + */ +function edd_get_average_monthly_download_earnings( $download_id = 0 ) { - if(is_numeric($download)) { - $download = get_post($download); - if($download->post_type != 'download') - return null; - return $download; + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return 0; } - - $args = array( - 'post_type' => 'download', - 'name' => $download, - 'numberposts' => 1 - ); - - $download = get_posts($args); - - if ($download) { - return $download[0]; + + $earnings = edd_get_download_earnings_stats( $download_id ); + $release_date = get_post_field( 'post_date', $download_id ); + + $diff = abs( current_time( 'timestamp' ) - strtotime( $release_date ) ); + + // Number of months since publication + $months = floor( $diff / ( 30 * 60 * 60 * 24 ) ); + + if ( $months > 0 ) { + $earnings = ( $earnings / $months ); } - - return null; -} + return $earnings < 0 + ? 0 + : $earnings; +} /** - * Get Download Price + * Retrieves the average monthly sales for a specific download. * - * Returns the price of a download, but only for non-variable priced downloads. + * @since 1.3 * - * @access public - * @since 1.0 - * @param $download_id INT the ID number of the download to retrieve a price for - * @return $string/int the price of the download -*/ + * @param int $download_id Download ID. + * @return float $sales Average monthly sales. + */ +function edd_get_average_monthly_download_sales( $download_id = 0 ) { + $sales = edd_get_download_sales_stats( $download_id ); + $release_date = get_post_field( 'post_date', $download_id ); -function edd_get_download_price($download_id) { - $price = get_post_meta($download_id, 'edd_price', true); - if($price) - return edd_sanitize_amount( $price ); - return 0; -} + $diff = abs( current_time( 'timestamp' ) - strtotime( $release_date ) ); + + // Number of months since publication + $months = floor( $diff / ( 30 * 60 * 60 * 24 ) ); + + if ( $months > 0 ) { + $sales = ( $sales / $months ); + } + return $sales; +} /** - * Price + * Gets all download files for a product. * - * Displays a formatted price for a download. + * @since 1.0 + * @since 3.0 Renamed $variable_price_id parameter to $price)id for consistency. * - * @access public - * @since 1.0 - * @param int $download_id the ID of the download price to show - * @param bool whether to echo or return the results -* @return void -*/ + * @param int $download_id Download ID. + * @param int $price_id Optional. Price ID. Default null. + * + * @return array|false Download files, false if invalid data was passed. + */ +function edd_get_download_files( $download_id = 0, $price_id = null ) { -function edd_price($download_id, $echo = true) { - if( edd_has_variable_prices( $download_id ) ) { - $prices = edd_get_variable_prices( $download_id ); - $price = edd_sanitize_amount( $prices[0]['amount'] ); // show the first price option - } else { - $price = edd_get_download_price( $download_id ); + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; } - - $price = apply_filters( 'edd_download_price', $price, $download_id ); - if( $echo ) - echo $price; - else - return $price; -} -add_filter( 'edd_download_price', 'edd_format_amount', 10 ); -add_filter( 'edd_download_price', 'edd_currency_filter', 20 ); + $download = edd_get_download( $download_id ); + return $download + ? $download->get_files( $price_id ) + : false; +} /** - * Get Download Final Price + * Retrieves a file name for a file attached to a download. Defaults to the + * file's actual name if no 'name' key is present. * - * retrieves the price of a downloadable product after purchase - * this price includes any necessary discounts that were applied. + * @since 1.6 * - * @access public - * @since 1.0 - * @param int $download_id - the ID of the download - * @param array $user_purchase_info - an array of all information for the payment - * @param string $amount_override a custom amount that over rides the 'edd_price' meta, used for variable prices - * @return string - the price of the download -*/ + * @param array $file File information. + * @return string Filename. + */ +function edd_get_file_name( $file = array() ) { -function edd_get_download_final_price($download_id, $user_purchase_info, $amount_override = null) { - if(is_null($amount_override)) { - $original_price = get_post_meta($download_id, 'edd_price', true); - } else { - $original_price = $amount_override; - } - if(isset($user_purchase_info['discount']) && $user_purchase_info['discount'] != 'none') { - $price = edd_get_discounted_amount($user_purchase_info['discount'], $original_price); - } else { - $price = $original_price; + // Bail if no data was passed. + if ( empty( $file ) || ! is_array( $file ) ) { + return false; } - return $price; -} + $name = ! empty( $file['name'] ) + ? esc_html( $file['name'] ) + : basename( $file['file'] ); + + return $name; +} /** - * Get Download Variable Prices + * Gets the number of times a file has been downloaded for a specific order. * - * retrieves the variable prices for a download + * @since 1.6 + * @since 3.0 Renamed parameters for consistency across new query methods + * introduced. + * Refactored to use new query methods. * - * @access public - * @since 1.2 - * @param int $download_id - the ID of the download - * @return array -*/ + * @param int $download_id Download ID. + * @param int $file_id File ID. + * @param int $order_id Order ID. + * + * @return int Number of times the file has been downloaded for the order. + */ +function edd_get_file_downloaded_count( $download_id = 0, $file_id = 0, $order_id = 0 ) { -function edd_get_variable_prices( $download_id ) { - return get_post_meta($download_id, 'edd_variable_prices', true); + // Bail if no download ID or order ID was passed. + if ( empty( $download_id ) || empty( $order_id ) ) { + return false; + } + + // Ensure arguments passed are valid. + $download_id = absint( $download_id ); + $file_id = absint( $file_id ); + $order_id = absint( $order_id ); + + return edd_count_file_download_logs( array( + 'product_id' => $download_id, + 'order_id' => $order_id, + 'file_id' => $file_id, + ) ); } /** - * Has Variable Prices + * Gets the file download file limit for a particular download. This limit refers + * to the maximum number of times files connected to a product can be downloaded. * - * Checks to see if a download has variable prices enabled. + * @since 1.3.1 * - * @access public - * @since 1.0.7 - * @param int $download_id the ID number of the download to checl - * @return boolean true if has variable prices, false otherwise -*/ + * @param int $download_id Download ID. + * @return int|false File download limit, false if invalid download ID passed. + */ +function edd_get_file_download_limit( $download_id = 0 ) { -function edd_has_variable_prices($download_id) { - if(get_post_meta($download_id, '_variable_pricing', true)) { - return true; + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; } - return false; -} + $download = edd_get_download( $download_id ); + + return $download + ? $download->get_file_download_limit() + : false; +} /** - * Get Download Price Name + * Gets the file refund window for a particular download * - * retrieves the name of a variable price option + * This window refers to the maximum number of days it can be refunded after + * it has been purchased. * - * @access public - * @since 1.0.9 - * @param int $download_id - the ID of the download - * @param int $price_id - the ID of the price option - * @return string - the name of the price option -*/ + * @since 3.0 + * + * @param int $download_id Download ID. + * @return int Refund window. + */ +function edd_get_download_refund_window( $download_id = 0 ) { -function edd_get_price_option_name($download_id, $price_id) { - $prices = edd_get_variable_prices( $download_id ); - if( $prices && is_array( $prices ) ) { - $price_name = $prices[$price_id]['name']; - } else { - $price_name = ''; + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; } - return $price_name; -} + $download = edd_get_download( $download_id ); + + return $download + ? $download->get_refund_window() + : false; +} /** - * Get Download Earnings Stats + * Get the refundability status for a download. * - * Returns the total earnings for a download. + * @since 3.0 * - * @access public - * @since 1.0 - * @return integer -*/ + * @param int $download_id Download ID. + * @return string `refundable` or `nonrefundable`. + */ +function edd_get_download_refundability( $download_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } -function edd_get_download_earnings_stats($download_id) { + $download = edd_get_download( $download_id ); - // If the current Download CPT has no earnings value associated wht it, we need to initialize it. - // This is what enables us to sort it. - if ( '' == get_post_meta($download_id, '_edd_download_earnings', true) ) { - add_post_meta( $download_id, '_edd_download_earnings', 0 ); - } // end if - - $earnings = get_post_meta( $download_id, '_edd_download_earnings', true ); - - return $earnings; - + return $download + ? $download->get_refundability() + : false; } - /** - * Get Download Sales Stats + * Gets the file download file limit override for a particular download. + * The override allows the main file download limit to be bypassed. * - * Return the sales number for a download. + * @since 1.3.2 + * @since 3.0 Renamed $payment_id parameter to $order_id. * - * @access public - * @since 1.0 - * @return integer + * @param int $download_id Download ID. + * @param int $order_id Order ID. + * + * @return int|false New file download limit, false if invalid download ID passed. */ +function edd_get_file_download_limit_override( $download_id = 0, $order_id = 0 ) { -function edd_get_download_sales_stats($download_id) { - - // If the current Download CPT has no sales value associated wht it, we need to initialize it. - // This is what enables us to sort it. - if ( '' == get_post_meta($download_id, '_edd_download_sales', true) ) { - add_post_meta( $download_id, '_edd_download_sales', 0 ); - } // end if - - $sales = get_post_meta( $download_id, '_edd_download_sales', true ); - - return $sales; -} + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $limit_override = get_post_meta( $download_id, '_edd_download_limit_override_' . $order_id, true ); + return $limit_override + ? absint( $limit_override ) + : 0; +} /** - * Get Download Sales Log + * Sets the file download file limit override for a particular download. * - * Returns an array of sales and sale info for a download. - * - * @param $download_id INT the ID number of the download to retrieve a log for - * @param $paginate bool whether to paginate the results or not - * @param $number int the number of results to return - * @param $offset int the number of items to skip + * The override allows the main file download limit to be bypassed. + * If no override is set yet, the override is set to the main limit + 1. + * If the override is already set, then it is simply incremented by 1. * - * @access public - * @since 1.0 - * @return array -*/ + * @since 1.3.2 + * @since 3.0 Renamed $payment_id parameter to $order_id. + * + * @param int $download_id Download ID. + * @param int $order_id Order ID. + * + * @return false False if invalid download ID or order ID was passed. + */ +function edd_set_file_download_limit_override( $download_id = 0, $order_id = 0 ) { -function edd_get_download_sales_log($download_id, $paginate = false, $number = 10, $offset = 0) { - - $sales_log = get_post_meta($download_id, '_edd_sales_log', true); - if($sales_log) { - $sales_log = array_reverse( $sales_log ); - $log = array(); - $log['number'] = count( $sales_log ); - $log['sales'] = $sales_log; - if( $paginate ) { - $log['sales'] = array_slice( $sales_log, $offset, $number ); - } - return $log; + // Bail if no download ID or order ID was passed. + if ( empty( $download_id ) || empty( $order_id ) ) { + return false; + } + + $override = edd_get_file_download_limit_override( $download_id, $order_id ); + $limit = edd_get_file_download_limit( $download_id ); + + if ( ! empty( $override ) ) { + $override = $override += 1; + } else { + $override = $limit += 1; } - - return false; + + update_post_meta( $download_id, '_edd_download_limit_override_' . $order_id, $override ); } /** - * Get File Download Log + * Checks if a file is at its download limit * - * Returns an array of file download dates and user info. + * This limit refers to the maximum number of times files connected to a product + * can be downloaded. * - * @access public - * @since 1.0 - * - * @param $download_id INT the ID number of the download to retrieve a log for - * @param $paginate bool whether to paginate the results or not - * @param $number int the number of results to return - * @param $offset int the number of items to skip + * @since 1.3.1 + * @since 3.0 Refactored to use new query methods. + * Renamed $payment_id parameter to $order_id. + * Set default value of $price_id to 0. * - * @return array -*/ + * @param int $download_id Download ID. + * @param int $order_id Order ID. + * @param int $file_id File ID. + * @param int $price_id Price ID. + * + * @return bool True if at limit, false otherwise. + */ +function edd_is_file_at_download_limit( $download_id = 0, $order_id = 0, $file_id = 0, $price_id = 0 ) { + + // Bail if invalid data was passed. + if ( empty( $download_id ) || empty( $order_id ) ) { + return false; + } -function edd_get_file_download_log($download_id, $paginate = false, $number = 10, $offset = 0) { - $download_log = get_post_meta($download_id, '_edd_file_download_log', true); - if($download_log) { - $download_log = array_reverse( $download_log ); - $log = array(); - $log['number'] = count($download_log); - $log['downloads'] = $download_log; - if( $paginate ) { - $log['downloads'] = array_slice($download_log, $offset, $number); + // Sanitize parameters. + $download_id = absint( $download_id ); + $order_id = absint( $order_id ); + $file_id = absint( $file_id ); + $price_id = absint( $price_id ); + + // Default to false. + $ret = false; + $download_limit = edd_get_file_download_limit( $download_id ); + + if ( ! empty( $download_limit ) ) { + $unlimited_purchase = edd_payment_has_unlimited_downloads( $order_id ); + + if ( empty( $unlimited_purchase ) ) { + // Retrieve the file download count. + $download_count = edd_count_file_download_logs( array( + 'product_id' => $download_id, + 'file_id' => $file_id, + 'order_id' => $order_id, + 'price_id' => $price_id, + ) ); + + if ( $download_count >= $download_limit ) { + $ret = true; + + // Check to make sure the limit isn't overwritten. + // A limit is overwritten when purchase receipt is resent. + $limit_override = edd_get_file_download_limit_override( $download_id, $order_id ); + + if ( ! empty( $limit_override ) && $download_count < $limit_override ) { + $ret = false; + } + } } - return $log; } - return false; -} + /** + * Filters whether or not a file is at its download limit. + * + * @param bool $ret + * @param int $download_id + * @param int $payment_id + * @param int $file_id + * @param int $price_id + * + * @since 2.10 Added `$price_id` parameter. + */ + return (bool) apply_filters( 'edd_is_file_at_download_limit', $ret, $download_id, $order_id, $file_id, $price_id ); +} /** - * Record Sale In Log + * Retrieve the price option that has access to the specified file. * - * Stores log information for a download sale. + * @since 1.0.9 * - * @access public - * @since 1.0 - * @return void -*/ + * @param int $download_id Download ID. + * @param string $file_key File key. + * + * @return string|false Price ID if restricted, "all" otherwise, false if no download ID was passed. + */ +function edd_get_file_price_condition( $download_id = 0, $file_key = '' ) { -function edd_record_sale_in_log($download_id, $payment_id, $user_info, $date) { - $log = get_post_meta($download_id, '_edd_sales_log', true); - if(!$log) { - $log = array(); + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; } - $log_entry = array( - 'payment_id' => $payment_id, - 'user_info' => $user_info, - 'date' => $date - ); - $log[] = $log_entry; - - update_post_meta($download_id, '_edd_sales_log', $log); -} + $download = edd_get_download( $download_id ); + + return $download + ? $download->get_file_price_condition( $file_key ) + : false; +} /** - * Record Download In Log + * Constructs a secure file download url for a specific file. * - * Stores a log entry for a file download. + * @since 1.0 + * @since 3.0 Updated to use new query methods. * - * @access public - * @since 1.0 - * @return void -*/ + * @param string $order_or_key The order object or payment key. Using the payment key will eventually be deprecated. + * @param string $email Customer email address. Use edd_get_payment_user_email() to get user email. + * @param int $filekey Index of array of files returned by edd_get_download_files() that this download link is for. + * @param int $download_id Optional. ID of download this download link is for. Default is 0. + * @param bool|int $price_id Optional. Price ID when using variable prices. Default is false. + * + * @return string Secure download URL. + */ +function edd_get_download_file_url( $order_or_key, $email, $filekey, $download_id = 0, $price_id = false ) { + $hours = absint( edd_get_option( 'download_link_expiration', 24 ) ); + + if ( ! ( $date = strtotime( '+' . $hours . 'hours', current_time( 'timestamp' ) ) ) ) { + $date = 2147472000; // Highest possible date, January 19, 2038 + } -function edd_record_download_in_log($download_id, $file_id, $user_info, $ip, $date) { - $log = get_post_meta($download_id, '_edd_file_download_log', true); - if(!$log) { - $log = array(); + // Fetch order. + if ( $order_or_key instanceof EDD\Orders\Order ) { + $order = $order_or_key; + $key = $order->payment_key; + } else { + $key = $order_or_key; + $order = edd_get_order_by( 'payment_key', $key ); } - $log_entry = array( - 'file_id' => $file_id, - 'user_info' => $user_info, - 'ip' => $ip, - 'date' => $date + + // Leaving in this array and the filter for backwards compatibility now + $old_args = array( + 'download_key' => rawurlencode( $key ), + 'email' => rawurlencode( $email ), + 'file' => rawurlencode( $filekey ), + 'price_id' => (int) $price_id, + 'download_id' => $download_id, + 'expire' => rawurlencode( $date ), ); - $log[] = $log_entry; - - update_post_meta($download_id, '_edd_file_download_log', $log); + + $params = apply_filters( 'edd_download_file_url_args', $old_args ); + + // Bail if order wasn't found. + if ( ! $order ) { + return false; + } + + // Get the array of parameters in the same order in which they will be validated. + $args = array_fill_keys( edd_get_url_token_parameters(), '' ); + + // Simply the URL by concatenating required data using a colon as a delimiter. + if ( ! is_numeric( $price_id ) ) { + $eddfile = sprintf( '%d:%d:%d', $order->id, $params['download_id'], $params['file'] ); + } else { + $eddfile = sprintf( '%d:%d:%d:%d', $order->id, $params['download_id'], $params['file'], $price_id ); + } + $args['eddfile'] = rawurlencode( $eddfile ); + + if ( isset( $params['expire'] ) ) { + $args['ttl'] = $params['expire']; + } + + // Ensure all custom args registered with extensions through edd_download_file_url_args get added to the URL, but without adding all the old args + $args = array_merge( $args, array_diff_key( $params, $old_args ) ); + + /** + * Allow the file download args to be filtered. + * + * @since 3.1.1 Includes the order object as the fourth parameter. + * @param array $args The full array of parameters. + * @param int $order_id The order ID. + * @param array $params The original array of parameters. + * @param EDD\Orders\Order $order The order object. + */ + $args = apply_filters( 'edd_get_download_file_url_args', $args, $order->id, $params, $order ); + + $args['file'] = $params['file']; + $args['token'] = edd_get_download_token( add_query_arg( array_filter( $args ), untrailingslashit( site_url() ) ) ); + + return add_query_arg( array_filter( $args ), site_url( 'index.php' ) ); } +/** + * Gets the array of parameters to be used for the URL token generation and validation. + * Used by `edd_get_download_file_url` and `edd_validate_url_token` so that their parameters are ordered the same. + * + * @since 2.11.4 + * @return array + */ +function edd_get_url_token_parameters() { + return apply_filters( + 'edd_url_token_allowed_params', + array( + 'eddfile', + 'ttl', + 'file', + 'token', + ) + ); +} /** - * Increase Purchase Count + * Get product notes. * - * Increases the sale count of a download. + * @since 1.2.1 * - * @access public - * @since 1.0 - * @return void -*/ + * @param int $download_id Download ID. + * @return string|false Product notes, false if invalid data was passed. + */ +function edd_get_product_notes( $download_id = 0 ) { + + // Bail if download ID was not passed. + if ( empty( $download_id ) ) { + return false; + } -function edd_increase_purchase_count($download_id) { - $sales = edd_get_download_sales_stats($download_id); - $sales = $sales + 1; - if(update_post_meta($download_id, '_edd_download_sales', $sales)) - return $sales; + $download = edd_get_download( $download_id ); - return false; + return $download + ? $download->notes + : false; } /** - * Decrease Purchase Count + * Retrieves a download SKU by ID. * - * Decreases the sale count of a download. Primarily for when a purchase is refunded. + * @since 1.6 * - * @access public - * @since 1.0.8.1 - * @return void -*/ + * @param int $download_id Download ID. + * @return string|false Download SKU, false if invalid data was passed. + */ +function edd_get_download_sku( $download_id = 0 ) { + + // Bail if download ID was not passed. + if ( empty( $download_id ) ) { + return false; + } -function edd_decrease_purchase_count($download_id) { - $sales = edd_get_download_sales_stats($download_id); - if($sales > 0) // only decrease if not already zero - $sales = $sales - 1; - - if(update_post_meta($download_id, '_edd_download_sales', $sales)) - return $sales; + $download = edd_get_download( $download_id ); - return false; + return $download + ? $download->sku + : false; } - /** - * Increase Earnings + * Retrieve the download button behavior: either add to cart or direct. * - * Increases the total earnings of a download. + * @since 1.7 * - * @access public - * @since 1.0 - * @return void -*/ + * @param int $download_id Download ID. + * @return string|false `add_to_cart` or `direct`, false if invalid data was passed. + */ +function edd_get_download_button_behavior( $download_id = 0 ) { + + // Bail if download ID was not passed. + if ( empty( $download_id ) ) { + return false; + } -function edd_increase_earnings($download_id, $amount) { - $earnings = edd_get_download_earnings_stats($download_id); - $earnings = $earnings + $amount; - - if(update_post_meta($download_id, '_edd_download_earnings', $earnings)) - return $earnings; - - return false; + $download = edd_get_download( $download_id ); + + return $download + ? $download->button_behavior + : false; } +/** + * Is quantity input disabled on this product? + * + * @since 2.7 + * + * @param int $download_id Download ID. + * @return bool + */ +function edd_download_quantities_disabled( $download_id = 0 ) { + + // Bail if download ID was not passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + + return $download + ? $download->quantities_disabled() + : false; +} /** - * Decrease Earnings + * Get the file download method. * - * Decreases the total earnings of a download. Primarily for when a purchase is refunded. + * @since 1.6 * - * @access public - * @since 1.0.8.1 - * @return void -*/ + * @return string File download method. + */ +function edd_get_file_download_method() { + $method = edd_get_option( 'download_method', 'direct' ); -function edd_decrease_earnings($download_id, $amount) { - $earnings = edd_get_download_earnings_stats($download_id); - - if($earnings > 0) // only decrease if greater than zero - $earnings = $earnings - $amount; - - if(update_post_meta($download_id, '_edd_download_earnings', $earnings)) - return $earnings; - - return false; + return apply_filters( 'edd_file_download_method', $method ); } +/** + * Returns a random download. + * + * @since 1.7 + * + * @param bool $post_ids Optional. True for of download IDs, false for WP_Post + * objects. Default true. + * @return array Download IDs/WP_Post objects. + */ +function edd_get_random_download( $post_ids = true ) { + return edd_get_random_downloads( 1, $post_ids ); +} /** - * Gets all download files for a product + * Returns random downloads. * - * Can retrieve files specific to price ID + * @since 1.7 * - * @access public - * @since 1.0 - * @return array -*/ + * @param int $num Number of downloads to return. Default 3. + * @param bool $post_ids Optional. True for array of WP_Post objects, else + * array of IDs. Default true. + * + * @return array Download IDs/WP_Post objects. + */ +function edd_get_random_downloads( $num = 3, $post_ids = true ) { + $args = array( + 'post_type' => 'download', + 'orderby' => 'rand', + 'numberposts' => $num, + ); -function edd_get_download_files( $download_id, $variable_price_id = null ) { - - $files = array(); - $download_files = get_post_meta($download_id, 'edd_download_files', true); - if( $download_files ) { - if( !is_null( $variable_price_id ) ) { - foreach( $download_files as $key => $file_info ) { - if( isset( $file_info['condition'] ) ) { - if( $file_info['condition'] == $variable_price_id || $file_info['condition'] == 'all' ) { - $files[$key] = $file_info; - } - } - } - } else { - $files = $download_files; - } + if ( $post_ids ) { + $args['fields'] = 'ids'; } - return $files; + $args = apply_filters( 'edd_get_random_downloads', $args ); + + return get_posts( $args ); } /** - * Gets the Price ID that can download a file + * Generates a token for a given URL. * - * @access public - * @since 1.0.9 - * @return string - the price ID if restricted, "all" otherwise -*/ + * An 'o' query parameter on a URL can include optional variables to test + * against when verifying a token without passing those variables around in + * the URL. For example, downloads can be limited to the IP that the URL was + * generated for by adding 'o=ip' to the query string. + * + * Or suppose when WordPress requested a URL for automatic updates, the user + * agent could be tested to ensure the URL is only valid for requests from + * that user agent. + * + * @since 2.3 + * + * @param string $url URL to generate a token for. + * @return string Token for the URL. + */ +function edd_get_download_token( $url = '' ) { + $args = array(); + $hash = apply_filters( 'edd_get_url_token_algorithm', 'sha256' ); + $secret = apply_filters( 'edd_get_url_token_secret', hash( $hash, wp_salt() ) ); -function edd_get_file_price_condition( $download_id, $file_key ) { - $files = edd_get_download_files( $download_id ); - if( ! $files ) - return false; - - $condition = isset($files[$file_key]['condition']) ? $files[$file_key]['condition'] : 'all'; - - return $condition; - -} + /* + * Add additional args to the URL for generating the token. + * Allows for restricting access to IP and/or user agent. + */ + $parts = wp_parse_url( $url ); + $options = array(); + + if ( isset( $parts['query'] ) ) { + wp_parse_str( $parts['query'], $query_args ); + + // o = option checks (ip, user agent). + if ( ! empty( $query_args['o'] ) ) { + + // Multiple options can be checked by separating them with a colon in the query parameter. + $options = explode( ':', rawurldecode( $query_args['o'] ) ); + + if ( in_array( 'ip', $options, true ) ) { + $args['ip'] = edd_get_ip(); + } + + if ( in_array( 'ua', $options, true ) ) { + $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) + ? $_SERVER['HTTP_USER_AGENT'] + : ''; + + $args['user_agent'] = rawurlencode( $ua ); + } + } + } + + /* + * Filter to modify arguments and allow custom options to be tested. + * Be sure to rawurlencode any custom options for consistent results. + */ + $args = apply_filters( 'edd_get_url_token_args', $args, $url, $options ); + + $args['secret'] = $secret; + $args['token'] = false; // Removes a token if present. + $url = add_query_arg( $args, $url ); + $parts = wp_parse_url( $url ); + + // In the event there isn't a path, set an empty one so we can MD5 the token + if ( ! isset( $parts['path'] ) ) { + $parts['path'] = ''; + } + + $token = hash_hmac( 'sha256', $parts['path'] . '?' . $parts['query'], wp_salt( 'edd_file_download_link' ) ); + return $token; +} /** - * Get Download File Url + * Generate a token for a URL and match it against the existing token to make + * sure the URL hasn't been tampered with. * - * Constructs the file download url for a specific file. + * @since 2.3 * - * @access public - * @since 1.0 - * @return string -*/ + * @param string $url URL to test. + * @return bool + */ +function edd_validate_url_token( $url = '' ) { + $ret = false; + $parts = parse_url( $url ); + $query_args = array(); + $original_url = $url; + + if ( isset( $parts['query'] ) ) { + wp_parse_str( $parts['query'], $query_args ); + + // If the TTL is in the past, die out before we go any further. + if ( isset( $query_args['ttl'] ) && current_time( 'timestamp' ) > $query_args['ttl'] ) { + wp_die( apply_filters( 'edd_download_link_expired_text', esc_html__( 'Sorry but your download link has expired.', 'easy-digital-downloads' ) ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } -function edd_get_download_file_url($key, $email, $filekey, $download_id) { - - global $edd_options; - - $hours = isset( $edd_options['download_link_expiration'] ) - && is_numeric( $edd_options['download_link_expiration'] ) - ? absint($edd_options['download_link_expiration']) : 24; - - if( ! ( $date = strtotime( '+' . $hours . 'hours' ) ) ) - $date = 2147472000; // highest possible date, January 19, 2038 - - $params = array( - 'download_key' => $key, - 'email' => rawurlencode( $email ), - 'file' => $filekey, - 'download' => $download_id, - 'expire' => rawurlencode( base64_encode( $date ) ) - ); + // These are the only URL parameters that are allowed to affect the token validation. + $allowed_args = edd_get_url_token_parameters(); - $params = apply_filters('edd_download_file_url_args', $params); - - $download_url = add_query_arg($params, home_url()); - - return $download_url; -} + // Collect the allowed tags in proper order, remove all tags, and re-add only the allowed ones. + $validated_query_args = array(); + + foreach ( $allowed_args as $key ) { + if ( true === array_key_exists( $key, $query_args ) ) { + $validated_query_args[ $key ] = $query_args[ $key ]; + } + } + // strtok allows a quick clearing of existing query string parameters, so we can re-add the allowed ones. + $url = add_query_arg( $validated_query_args, strtok( $url, '?' ) ); + + if ( isset( $query_args['token'] ) && hash_equals( $query_args['token'], edd_get_download_token( $url ) ) ) { + $ret = true; + } + } + + /** + * Filters the URL token validation. + * + * @param bool $ret Whether the URL has validated or not. + * @param string $url The URL used for validation. + * @param array $query_args The array of query parameters. + * @param string $original_url The original URL (added 2.11.3). + */ + return apply_filters( 'edd_validate_url_token', $ret, $url, $query_args, $original_url ); +} /** - * Verify Download Link + * Allows parsing of the values saved by the product drop down. * - * Verifies a download purchase using a purchase key and email. + * @since 2.6.9 * - * @access public - * @since 1.0 - * @return boolean -*/ - -function edd_verify_download_link($download_id, $key, $email, $expire, $file_key) { - - $meta_query = array( - 'relation' => 'AND', - array( - 'key' => '_edd_payment_purchase_key', - 'value' => $key - ), - array( - 'key' => '_edd_payment_user_email', - 'value' => $email - ) - ); + * @param array $values Parse the values from the product dropdown into a readable array. + * @return array A parsed set of values for download_id and price_id. + */ +function edd_parse_product_dropdown_values( $values = array() ) { + $parsed_values = array(); - $payments = get_posts(array('meta_query' => $meta_query, 'post_type' => 'edd_payment')); - if($payments) { - foreach($payments as $payment) { - $payment_meta = get_post_meta($payment->ID, '_edd_payment_meta', true); - $downloads = maybe_unserialize($payment_meta['downloads']); - $cart_details = unserialize( $payment_meta['cart_details'] ); - if( $payment->post_status != 'publish' && $payment->post_status != 'complete' ) - return false; + if ( is_array( $values ) ) { + foreach ( $values as $value ) { + $value = edd_parse_product_dropdown_value( $value ); - if($downloads) { - foreach($downloads as $key => $download) { - - $id = isset($payment_meta['cart_details']) ? $download['id'] : $download; - - $price_options = $cart_details[$key]['item_number']['options']; - - $file_condition = edd_get_file_price_condition( $id, $file_key ); - - $variable_prices_enabled = get_post_meta($id, '_variable_pricing', true); - - // if this download has variable prices, we have to confirm that this file was included in their purchase - if( !empty( $price_options ) && $file_condition != 'all' && $variable_prices_enabled) { - - if( $file_condition !== $price_options['price_id'] ) - return false; - } - - if($id == $download_id) { - if(time() < $expire) { - return true; // payment has been verified and link is still valid - } - return false; // payment verified, but link is no longer valid - } - } - } + $parsed_values[] = array( + 'download_id' => $value['download_id'], + 'price_id' => $value['price_id'], + ); } + } else { + $value = edd_parse_product_dropdown_value( $values ); + $parsed_values[] = array( + 'download_id' => $value['download_id'], + 'price_id' => $value['price_id'], + ); } - // payment not verified - return false; + + return $parsed_values; +} + +/** + * Given a value from the product dropdown array, parse its parts. + * + * @since 2.6.9 + * + * @param string $value A value saved in a product dropdown array + * @return array A parsed set of values for download_id and price_id. + */ +function edd_parse_product_dropdown_value( $value ) { + $parts = explode( '_', $value ); + $download_id = $parts[0]; + $price_id = isset( $parts[1] ) + ? $parts[1] + : false; + + return array( + 'download_id' => $download_id, + 'price_id' => $price_id, + ); } /** - * Get product notes + * Get bundle pricing variations + * + * @since 2.7 * - * @access public - * @since 1.2.1 - * @return string + * @param int $download_id Download ID. + * @return array|false Bundle pricing variations, false if invalid data was passed. */ -function edd_get_product_notes( $download_id ) { - $notes = get_post_meta( $download_id, 'edd_product_notes', true ); - if ( $notes ) - return apply_filters( 'edd_product_notes', $notes, $download_id ); - return ''; -} \ No newline at end of file +function edd_get_bundle_pricing_variations( $download_id = 0 ) { + + // Bail if no download ID was passed. + if ( empty( $download_id ) ) { + return false; + } + + $download = edd_get_download( $download_id ); + + return $download + ? $download->get_bundle_pricing_variations() + : false; +} diff --git a/includes/downloads/recalculations.php b/includes/downloads/recalculations.php new file mode 100644 index 00000000000..23b017de1ac --- /dev/null +++ b/includes/downloads/recalculations.php @@ -0,0 +1,158 @@ +status === $data['status'] ) && + ( empty( $data['quantity'] ) || $previous_order_item->quantity === $data['quantity'] ) && + ( ! isset( $data['total'] ) || $previous_order_item->total == $data['total'] ) && + ( ! isset( $data['subtotal'] ) || $previous_order_item->subtotal == $data['subtotal'] ) && + ( ! isset( $data['discount'] ) || $previous_order_item->discount == $data['discount'] ) && + ( ! isset( $data['tax'] ) || $previous_order_item->tax == $data['tax'] ) && + ( ! isset( $data['rate'] ) || $previous_order_item->rate == $data['rate'] ) && + ( empty( $data['product_id'] ) || $previous_order_item->product_id == $data['product_id'] ) + ) { + return; + } + + // Recalculate the previous product values if the product ID has changed. + if ( ! empty( $data['product_id'] ) && $previous_order_item->product_id != $data['product_id'] ) { + edd_maybe_schedule_download_recalculation( $previous_order_item->product_id ); + } + } + + $order_item = edd_get_order_item( $order_item_id ); + if ( empty( $order_item->product_id ) ) { + return; + } + + edd_maybe_schedule_download_recalculation( $order_item->product_id ); +} + +add_action( 'edd_order_adjustment_added', 'edd_recalculate_order_adjustment_download', 10, 2 ); +add_action( 'edd_order_adjustment_updated', 'edd_recalculate_order_adjustment_download', 10, 3 ); +/** + * Attempts to reschedule download recalculations when an order adjustment is added or updated. + * + * @since 3.1 + * @param int $order_adjustment_id The order adjustment ID. + * @param array $data The array of data for the new/updated order adjustment. + * @param bool|EDD\Orders\Order_Adjustment $previous_order_adjustment The previous order adjustment object. + * @return void + */ +function edd_recalculate_order_adjustment_download( $order_adjustment_id, $data = array(), $previous_order_adjustment = false ) { + if ( get_option( '_edd_v30_doing_order_migration', false ) ) { + return; + } + + if ( $previous_order_adjustment instanceof EDD\Orders\Order_Adjustment ) { + $columns_affecting_stats = array( 'total', 'subtotal', 'object_id', 'object_type' ); + + // If the data being updated isn't one of these columns then we don't need to recalculate. + if ( empty( array_intersect( array_keys( $data ), $columns_affecting_stats ) ) ) { + return; + } + + // If the data exists but matches, we don't need to recalculate. + if ( + ( ! isset( $data['total'] ) || $previous_order_adjustment->total == $data['total'] ) && + ( ! isset( $data['subtotal'] ) || $previous_order_adjustment->subtotal == $data['subtotal'] ) && + ( empty( $data['object_id'] ) || $previous_order_adjustment->object_id == $data['object_id'] ) && + ( empty( $data['object_type'] ) || $previous_order_adjustment->object_type == $data['object_type'] ) + ) { + return; + } + } + + $order_adjustment = edd_get_order_adjustment( $order_adjustment_id ); + if ( empty( $order_adjustment->object_type ) || 'order_item' !== $order_adjustment->object_type ) { + return; + } + + $order_item = edd_get_order_item( $order_adjustment->object_id ); + if ( ! empty( $order_item->product_id ) ) { + edd_maybe_schedule_download_recalculation( $order_item->product_id ); + } +} diff --git a/includes/email-actions.php b/includes/email-actions.php deleted file mode 100644 index ffe9e2f4cca..00000000000 --- a/includes/email-actions.php +++ /dev/null @@ -1,27 +0,0 @@ - 'email_sent', 'edd-action' => false, 'purchase_id' => false ) ) ); exit; -} -add_action('edd_email_links', 'edd_resend_purchase_receipt'); diff --git a/includes/email-functions.php b/includes/email-functions.php deleted file mode 100755 index 76b349bbbfe..00000000000 --- a/includes/email-functions.php +++ /dev/null @@ -1,84 +0,0 @@ - 0) { - $user_data = get_userdata($user_info['id']); - $name = $user_data->display_name; - } elseif( isset( $user_info['first_name'] ) && isset($user_info['last_name'] ) ) { - $name = $user_info['first_name'] . ' ' . $user_info['last_name']; - } else { - $name = $user_info['email']; - } - - $message = edd_get_email_body_header(); - - $message .= edd_get_email_body_content( $payment_id, $payment_data ); - - $message .= edd_get_email_body_footer(); - - $from_name = isset($edd_options['from_name']) ? $edd_options['from_name'] : get_bloginfo('name'); - $from_email = isset($edd_options['from_email']) ? $edd_options['from_email'] : get_option('admin_email'); - - $subject = isset( $edd_options['purchase_subject'] ) && strlen( trim( $edd_options['purchase_subject'] ) ) > 0 ? $edd_options['purchase_subject'] : __('Purchase Receipt', 'edd'); - - $headers = "From: " . stripslashes_deep( html_entity_decode( $from_name, ENT_COMPAT, 'UTF-8' ) ) . " <$from_email>\r\n"; - $headers .= "Reply-To: ". $from_email . "\r\n"; - $headers .= "MIME-Version: 1.0\r\n"; - $headers .= "Content-Type: text/html; charset=utf-8\r\n"; - - // allow add-ons to add file attachments - $attachments = apply_filters( 'edd_receipt_attachments', array(), $payment_id, $payment_data ); - - wp_mail( $payment_data['email'], $subject, $message, $headers, $attachments ); - - if($admin_notice) { - /* send an email notification to the admin */ - $admin_email = isset($edd_options['from_email']) ? $edd_options['from_email'] : get_option('admin_email'); - $admin_message = __('Hello', 'edd') . "\n\n" . __('A download purchase has been made', 'edd') . ".\n\n"; - $admin_message .= __('Downloads sold:', 'edd') . "\n\n"; - - $download_list = ''; - $downloads = maybe_unserialize($payment_data['downloads']); - if( is_array( $downloads ) ) { - foreach( $downloads as $download ) { - $id = isset($payment_data['cart_details']) ? $download['id'] : $download; - $download_list .= html_entity_decode(get_the_title($id), ENT_COMPAT, 'UTF-8') . "\n"; - } - } - $gateway = edd_get_gateway_admin_label( get_post_meta($payment_id, '_edd_payment_gateway', true) ); - - $admin_message .= $download_list . "\n"; - $admin_message .= __('Purchased by: ', 'edd') . " " . html_entity_decode( $name, ENT_COMPAT, 'UTF-8' ) . "\n"; - $admin_message .= __('Amount: ', 'edd') . " " . html_entity_decode(edd_currency_filter($payment_data['amount']), ENT_COMPAT, 'UTF-8') . "\n\n"; - $admin_message .= __('Payment Method: ', 'edd') . " " . $gateway . "\n\n"; - $admin_message .= __('Thank you', 'edd'); - $admin_message = apply_filters('edd_admin_purchase_notification', $admin_message, $payment_id, $payment_data); - - wp_mail( $admin_email, __('New download purchase', 'edd'), $admin_message ); - } -} diff --git a/includes/email-template.php b/includes/email-template.php deleted file mode 100644 index 75db2fdaebc..00000000000 --- a/includes/email-template.php +++ /dev/null @@ -1,325 +0,0 @@ - __('Default Template', 'edd'), - 'none' => __('No template, plain text only', 'edd') - ); - return apply_filters( 'edd_email_templates', $templates ); -} - - -/** - * Email Template Tags - * - * @access private - * @since 1.0 - * @return string -*/ - -function edd_email_templage_tags($message, $payment_data, $payment_id) { - - $user_info = maybe_unserialize($payment_data['user_info']); - - if(isset($user_info['id']) && $user_info['id'] > 0) { - $user_data = get_userdata($user_info['id']); - $name = $user_data->display_name; - } elseif(isset($user_info['first_name'])) { - $name = $user_info['first_name']; - } else { - $name = $user_info['email']; - } - - $download_list = '
      '; - $downloads = edd_get_downloads_of_purchase( $payment_id, $payment_data ); - if( $downloads ) { - - $show_names = apply_filters( 'edd_email_show_names', true ); - - foreach( $downloads as $download) { - - $id = isset($payment_data['cart_details']) ? $download['id'] : $download; - - if( $show_names ) { - $download_list .= '
    • ' . get_the_title($id) . '
      '; - $download_list .= '
        '; - } - - $price_id = isset($download['options']['price_id']) ? $download['options']['price_id'] : null; - - $files = edd_get_download_files( $id, $price_id ); - - if($files) { - foreach($files as $filekey => $file) { - $download_list .= '
      • '; - $file_url = edd_get_download_file_url($payment_data['key'], $payment_data['email'], $filekey, $id); - $download_list .= '' . $file['name'] . ''; - - $download_list .= '
      • '; - } - } - if( $show_names ) { - $download_list .= '
      '; - } - - if ( '' != edd_get_product_notes( $id ) ) - $download_list .= ' — ' . edd_get_product_notes( $id ) . ''; - - if( $show_names ) { - $download_list .= '
    • '; - } - } - } - $download_list .= '
    '; - - $price = edd_currency_filter( $payment_data['amount'] ); - - $gateway = edd_get_gateway_checkout_label( get_post_meta($payment_id, '_edd_payment_gateway', true) ); - - $receipt_id = $payment_data['key']; - - $message = str_replace('{name}', $name, $message); - $message = str_replace('{download_list}', $download_list, $message); - $message = str_replace('{date}', date( get_option('date_format'), strtotime( $payment_data['date'] ) ), $message); - $message = str_replace('{sitename}', get_bloginfo('name'), $message); - $message = str_replace('{price}', $price, $message); - $message = str_replace('{payment_method}', $gateway, $message); - $message = str_replace('{receipt_id}', $receipt_id, $message); - $message = apply_filters('edd_email_template_tags', $message, $payment_data, $payment_id); - - return $message; -} - - -/** - * Email Preview Template Tags - * - * @access private - * @since 1.0 - * @return string -*/ - -function edd_email_preview_templage_tags( $message ) { - - $download_list = '
      '; - $download_list .= '
    • ' . __('Sample Product Title', 'edd') . '
      '; - $download_list .= '
    • '; - $download_list .= '
    '; - - $price = edd_currency_filter(9.50); - - $gateway = 'PayPal'; - - $receipt_id = strtolower( md5( uniqid() ) ); - - $notes = __( 'These are some sample notes added to a product.', 'edd' ); - - $message = str_replace('{name}', 'John Doe', $message); - $message = str_replace('{download_list}', $download_list, $message); - $message = str_replace('{date}', date( get_option('date_format'), time() ), $message); - $message = str_replace('{sitename}', get_bloginfo('name'), $message); - $message = str_replace('{price}', $price, $message); - $message = str_replace('{payment_method}', $gateway, $message); - $message = str_replace('{receipt_id}', $receipt_id, $message); - - return wpautop($message); - -} - -/** - * Email Default Formatting - * - * @access private - * @since 1.0 - * @return string -*/ - -function edd_email_default_formatting($message) { - return wpautop($message); -} -add_filter('edd_purchase_receipt', 'edd_email_default_formatting'); - - -/** - * Email Template Preview - * - * @access private - * @since 1.0.8.2 - * @echo string -*/ - -function edd_email_template_preview() { - global $edd_options; - ob_start(); ?> - - - - - - - '; - echo '
    '; - echo '{email}'; // this tag is required in order for the contents of the email to be shown - echo '
    '; - echo ''; - -} -add_action('edd_email_template_default', 'edd_default_email_template'); - - -/** - * Default Email Template Styling Extras - * - * @access private - * @since 1.0.9.1 - * @return string -*/ - -function edd_default_email_styling( $email_body ) { - - $first_p = strpos($email_body, '

    '); - $email_body = substr_replace($email_body, '

    ', $first_p, 3); - - return $email_body; -} -add_filter('edd_purchase_receipt_default', 'edd_default_email_styling'); - diff --git a/includes/emails/actions.php b/includes/emails/actions.php new file mode 100755 index 00000000000..0d70b8b50c8 --- /dev/null +++ b/includes/emails/actions.php @@ -0,0 +1,123 @@ +order instanceof \EDD\Orders\Order && 'refund' === $payment->order->type ) { + return; + } + + // Send email with secure download link + edd_email_purchase_receipt( $payment_id, true, '', $payment, $customer ); +} +add_action( 'edd_complete_purchase', 'edd_trigger_purchase_receipt', 999, 3 ); + +/** + * Resend the Email Purchase Receipt. (This can be done from the Payment History page) + * + * @since 1.0 + * @param array $data Payment Data + * @return void + */ +function edd_resend_purchase_receipt( $data ) { + + $purchase_id = absint( $data['purchase_id'] ); + + if( empty( $purchase_id ) ) { + return; + } + + if( ! current_user_can( 'edit_shop_payments' ) ) { + wp_die( __( 'You do not have permission to edit this payment record', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $email = ! empty( $data['email'] ) ? sanitize_email( $data['email'] ) : ''; + + if( empty( $email ) ) { + $customer = new EDD_Customer( edd_get_payment_customer_id( $purchase_id ) ); + $email = $customer->email; + } + + $sent = edd_email_purchase_receipt( $purchase_id, false, $email ); + + // Grab all downloads of the purchase and update their file download limits, if needed + // This allows admins to resend purchase receipts to grant additional file downloads + $downloads = edd_get_payment_meta_cart_details( $purchase_id, true ); + + if ( is_array( $downloads ) ) { + foreach ( $downloads as $download ) { + $limit = edd_get_file_download_limit( $download['id'] ); + if ( ! empty( $limit ) ) { + edd_set_file_download_limit_override( $download['id'], $purchase_id ); + } + } + } + + edd_redirect( + add_query_arg( + array( + 'edd-message' => $sent ? 'email_sent' : 'email_send_failed', + 'edd-action' => false, + 'purchase_id' => false, + 'email' => false, + ) + ) + ); +} +add_action( 'edd_email_links', 'edd_resend_purchase_receipt' ); + +/** + * Trigger the sending of a Test Email + * + * @since 1.5 + * @param array $data Parameters sent from Settings page + * @return void + */ +function edd_send_test_email( $data ) { + if ( ! wp_verify_nonce( $data['_wpnonce'], 'edd-test-email' ) ) { + return; + } + + // Send a test email + edd_email_test_purchase_receipt(); + + $url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'emails', + 'section' => 'purchase_receipts', + 'edd-message' => 'test-purchase-email-sent', + ) + ); + edd_redirect( $url ); +} +add_action( 'edd_send_test_email', 'edd_send_test_email' ); diff --git a/includes/emails/class-edd-email-tags.php b/includes/emails/class-edd-email-tags.php new file mode 100644 index 00000000000..e609b9d96c7 --- /dev/null +++ b/includes/emails/class-edd-email-tags.php @@ -0,0 +1,149 @@ +tags[ $tag ] = array( + 'tag' => $tag, + 'label' => ! empty( $label ) ? $label : ucwords( str_replace( '_', ' ', $tag ) ), + 'description' => $description, + 'func' => $func, + ); + } + } + + /** + * Remove an email tag + * + * @since 1.9 + * + * @param string $tag Email tag to remove hook from + */ + public function remove( $tag ) { + unset( $this->tags[ $tag ] ); + } + + /** + * Check if $tag is a registered email tag + * + * @since 1.9 + * + * @param string $tag Email tag that will be searched + * + * @return bool + */ + public function email_tag_exists( $tag ) { + return array_key_exists( $tag, $this->tags ); + } + + /** + * Returns a list of all email tags + * + * @since 1.9 + * + * @return array + */ + public function get_tags() { + return (array) $this->tags; + } + + /** + * Search content for email tags and filter email tags through their hooks + * + * @param string $content Content to search for email tags + * @param int $payment_id The payment id + * + * @since 1.9 + * + * @return string Content with email tags filtered out. + */ + public function do_tags( $content, $payment_id ) { + + // Check if there is at least one tag added. + if ( empty( $this->tags ) || ! is_array( $this->tags ) ) { + return $content; + } + + $this->payment_id = $payment_id; + + $new_content = preg_replace_callback( '/{([A-z0-9\-\_]+)}/s', array( $this, 'do_tag' ), $content ); + + $this->payment_id = null; + + return $new_content; + } + + /** + * Do a specific tag, this function should not be used. Please use edd_do_email_tags instead. + * + * @since 1.9 + * + * @param $m message + * + * @return mixed + */ + public function do_tag( $m ) { + + // Get tag + $tag = $m[1]; + + // Return tag if tag not set + if ( ! $this->email_tag_exists( $tag ) ) { + return $m[0]; + } + + return call_user_func( $this->tags[ $tag ]['func'], $this->payment_id, $tag ); + } +} diff --git a/includes/emails/class-edd-emails.php b/includes/emails/class-edd-emails.php new file mode 100644 index 00000000000..0607cb628de --- /dev/null +++ b/includes/emails/class-edd-emails.php @@ -0,0 +1,381 @@ +get_template() ) { + $this->html = false; + } + + add_action( 'edd_email_send_before', array( $this, 'send_before' ) ); + add_action( 'edd_email_send_after', array( $this, 'send_after' ) ); + + } + + /** + * Set a property + * + * @since 2.1 + */ + public function __set( $key, $value ) { + $this->$key = $value; + } + + /** + * Get a property + * + * @since 2.6.9 + */ + public function __get( $key ) { + return $this->$key; + } + + /** + * Get the email from name + * + * @since 2.1 + */ + public function get_from_name() { + if ( ! $this->from_name ) { + $this->from_name = edd_get_option( 'from_name', get_bloginfo( 'name' ) ); + } + + return apply_filters( 'edd_email_from_name', wp_specialchars_decode( $this->from_name ), $this ); + } + + /** + * Get the email from address + * + * @since 2.1 + */ + public function get_from_address() { + if ( ! $this->from_address ) { + $this->from_address = edd_get_option( 'from_email' ); + } + + if( empty( $this->from_address ) || ! is_email( $this->from_address ) ) { + $this->from_address = get_option( 'admin_email' ); + } + + return apply_filters( 'edd_email_from_address', $this->from_address, $this ); + } + + /** + * Get the email content type + * + * @since 2.1 + */ + public function get_content_type() { + if ( ! $this->content_type && $this->html ) { + $this->content_type = apply_filters( 'edd_email_default_content_type', 'text/html', $this ); + } else if ( ! $this->html ) { + $this->content_type = 'text/plain'; + } + + return apply_filters( 'edd_email_content_type', $this->content_type, $this ); + } + + /** + * Get the email headers + * + * @since 2.1 + */ + public function get_headers() { + if ( ! $this->headers ) { + $this->headers = "From: {$this->get_from_name()} <{$this->get_from_address()}>\r\n"; + $this->headers .= "Reply-To: {$this->get_from_address()}\r\n"; + $this->headers .= "Content-Type: {$this->get_content_type()}; charset=utf-8\r\n"; + } + + return apply_filters( 'edd_email_headers', $this->headers, $this ); + } + + /** + * Retrieve email templates + * + * @since 2.1 + */ + public function get_templates() { + $templates = array( + 'default' => __( 'Default Template', 'easy-digital-downloads' ), + 'none' => __( 'No template, plain text only', 'easy-digital-downloads' ) + ); + + return apply_filters( 'edd_email_templates', $templates ); + } + + /** + * Get the enabled email template + * + * @since 2.1 + * + * @return string|null + */ + public function get_template() { + if ( ! $this->template ) { + $this->template = edd_get_option( 'email_template', 'default' ); + } + + return apply_filters( 'edd_email_template', $this->template ); + } + + /** + * Get the header text for the email + * + * @since 2.1 + */ + public function get_heading() { + return apply_filters( 'edd_email_heading', $this->heading ); + } + + /** + * Parse email template tags + * + * @since 2.1 + * @param string $content + */ + public function parse_tags( $content ) { + + // The email tags are parsed during setup for purchase receipts and sale notifications + // Onoce tags are not restricted to payments, we'll expand this. See https://github.com/easydigitaldownloads/Easy-Digital-Downloads/issues/2151 + + return $content; + } + + /** + * Build the final email + * + * @since 2.1 + * @param string $message + * + * @return string + */ + public function build_email( $message ) { + + if ( false === $this->html ) { + return apply_filters( 'edd_email_message', wp_strip_all_tags( $message ), $this ); + } + + $message = $this->text_to_html( $message ); + + ob_start(); + + edd_get_template_part( 'emails/header', $this->get_template(), true ); + + /** + * Hooks into the email header + * + * @since 2.1 + */ + do_action( 'edd_email_header', $this ); + + if ( has_action( 'edd_email_template_' . $this->get_template() ) ) { + /** + * Hooks into the template of the email + * + * @param string $this->template Gets the enabled email template + * @since 2.1 + */ + do_action( 'edd_email_template_' . $this->get_template() ); + } else { + edd_get_template_part( 'emails/body', $this->get_template(), true ); + } + + /** + * Hooks into the body of the email + * + * @since 2.1 + */ + do_action( 'edd_email_body', $this ); + + edd_get_template_part( 'emails/footer', $this->get_template(), true ); + + /** + * Hooks into the footer of the email + * + * @since 2.1 + */ + do_action( 'edd_email_footer', $this ); + + $body = ob_get_clean(); + $message = str_replace( '{email}', $message, $body ); + + return apply_filters( 'edd_email_message', $message, $this ); + } + + /** + * Send the email + * @param string $to The To address to send to. + * @param string $subject The subject line of the email to send. + * @param string $message The body of the email to send. + * @param string|array $attachments Attachments to the email in a format supported by wp_mail() + * + * @return bool Whether the email was sent successfully. + * @since 2.1 + */ + public function send( $to, $subject, $message, $attachments = '' ) { + + if ( ! did_action( 'init' ) && ! did_action( 'admin_init' ) ) { + _doing_it_wrong( __FUNCTION__, __( 'You cannot send email with EDD_Emails until init/admin_init has been reached', 'easy-digital-downloads' ), null ); + return false; + } + + /** + * Hooks before the email is sent + * + * @since 2.1 + */ + do_action( 'edd_email_send_before', $this ); + + $subject = $this->parse_tags( $subject ); + $message = $this->parse_tags( $message ); + + $message = $this->build_email( $message ); + + $attachments = apply_filters( 'edd_email_attachments', $attachments, $this ); + + $sent = wp_mail( $to, $subject, $message, $this->get_headers(), $attachments ); + $log_errors = apply_filters( 'edd_log_email_errors', true, $to, $subject, $message ); + + if( ! $sent && true === $log_errors ) { + if ( is_array( $to ) ) { + $to = implode( ',', $to ); + } + + $log_message = sprintf( + __( "Email from Easy Digital Downloads failed to send.\nSend time: %s\nTo: %s\nSubject: %s\n\n", 'easy-digital-downloads' ), + date_i18n( 'F j Y H:i:s', current_time( 'timestamp' ) ), + $to, + $subject + ); + + error_log( $log_message ); + } + + /** + * Hooks after the email is sent + * + * @since 2.1 + */ + do_action( 'edd_email_send_after', $this ); + + return $sent; + + } + + /** + * Add filters / actions before the email is sent + * + * @since 2.1 + */ + public function send_before() { + add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) ); + add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) ); + add_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) ); + } + + /** + * Remove filters / actions after the email is sent + * + * @since 2.1 + */ + public function send_after() { + remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) ); + remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) ); + remove_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) ); + + // Reset heading to an empty string + $this->heading = ''; + } + + /** + * Converts text to formatted HTML. This is primarily for turning line breaks into

    and
    tags. + * + * @since 2.1 + */ + public function text_to_html( $message ) { + + if ( 'text/html' == $this->content_type || true === $this->html ) { + $message = apply_filters( 'edd_email_template_wpautop', true ) ? wpautop( $message ) : $message; + $message = apply_filters( 'edd_email_template_make_clickable', true ) ? make_clickable( $message ) : $message; + $message = str_replace( '&', '&', $message ); + } + + return $message; + } + +} diff --git a/includes/emails/email-summary/class-edd-email-summary-blurb.php b/includes/emails/email-summary/class-edd-email-summary-blurb.php new file mode 100644 index 00000000000..1c33d3d0936 --- /dev/null +++ b/includes/emails/email-summary/class-edd-email-summary-blurb.php @@ -0,0 +1,297 @@ +environment_checker = new EnvironmentChecker(); + + } + + /** + * Fetch all blurbs from remote endpoint. + * + * @since 3.1 + * + * @return array + */ + public function fetch_blurbs() { + $blurbs = array(); + $request_body = false; + + $request = wp_safe_remote_get( self::BLURBS_ENDPOINT_URL ); + + // @todo - Detect first response code, before redirect! + if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) { + $request_body = wp_remote_retrieve_body( $request ); + $blurbs = json_decode( $request_body, true ); + } + + if ( empty( $request_body ) ) { + // HTTP Request for blurbs is empty, fallback to local .json file. + $fallback_json = wp_json_file_decode( EDD_PLUGIN_DIR . 'includes/admin/promos/email-summary/blurbs.json', array( 'associative' => true ) ); + if ( ! empty( $fallback_json ) ) { + $blurbs = $fallback_json; + } + } + + return $blurbs; + } + + + /** + * Get the next blurb that can be sent. + * + * @since 3.1 + * + * @return array + */ + public function get_next() { + $all_data = $this->fetch_blurbs(); + $blurbs = array(); + $blurbs_sent = get_option( 'email_summary_blurbs_sent', array() ); + $next_blurb = false; + + if ( empty( $all_data['blurbs'] ) ) { + return false; + } + + // Loop through the fetched blurbs and filter out all that meet the conditions. + foreach ( $all_data['blurbs'] as $key => $blurb ) { + if ( $this->does_blurb_meet_conditions( $blurb ) ) { + $blurbs[] = $blurb; + } + } + + // Find first blurb that was not yet sent. + foreach ( $blurbs as $blurb ) { + $blurb_hash = $this->get_blurb_hash( $blurb ); + if ( is_array( $blurbs_sent ) && ! in_array( $blurb_hash, $blurbs_sent, true ) ) { + $next_blurb = $blurb; + break; + } + } + + // If all of the available blurbs were already sent out, choose random blurb. + if ( ! $next_blurb && ! empty( $blurbs ) ) { + $next_blurb = $blurbs[ array_rand( $blurbs ) ]; + } + + return $next_blurb; + } + + /** + * Save blurb as sent to the options. + * + * @since 3.1 + * + * @param array $blurb Blurb data. + * @return void + */ + public function mark_blurb_sent( $blurb ) { + $blurbs_sent = get_option( 'email_summary_blurbs_sent', array() ); + if ( ! empty( $blurb ) ) { + $blurb_hash = $this->get_blurb_hash( $blurb ); + if ( ! in_array( $blurb_hash, $blurbs_sent, true ) ) { + $blurbs_sent[] = $blurb_hash; + } + } + + update_option( 'email_summary_blurbs_sent', $blurbs_sent ); + } + + + /** + * Hash blurb array + * + * @since 3.1 + * + * @param array $blurb Blurb data. + * @return string MD5 hashed blurb. + */ + public function get_blurb_hash( $blurb ) { + if ( empty( $blurb ) ) { + return false; + } + // We want to sort the array, so that we can get reliable hash everytime even if array properties order changed. + array_multisort( $blurb ); + return md5( wp_json_encode( $blurb ) ); + } + + /** + * Check if store pass matches the condition from the blurb. + * + * @since 3.1 + * + * @param string $condition Pass details. + * @return bool + */ + public function check_blurb_current_pass( $condition ) { + return $this->environment_checker->meetsCondition( $condition ); + } + + /** + * Check if store has all requested plugins active. + * + * @since 3.1 + * + * @param array $active_plugins An array of plugins that all need to be active. + * @return bool + */ + public function check_blurb_active_plugins( $active_plugins ) { + foreach ( $active_plugins as $plugin_name ) { + if ( ! is_plugin_active( $plugin_name ) ) { + return false; + } + } + + return true; + } + + /** + * Check if store has certain plugins inactive. + * + * @since 3.1 + * + * @param array $inactive_plugins An array of plugins that needs to be inactive. + * @return bool + */ + public function check_blurb_inactive_plugins( $inactive_plugins ) { + foreach ( $inactive_plugins as $plugin_name ) { + if ( is_plugin_active( $plugin_name ) ) { + return false; + } + } + + return true; + } + + /** + * Check if store has specific products/downloads active. + * + * @since 3.1 + * + * @param array $conditions An array of predefined conditions. + * @return bool + */ + public function check_blurb_has_downloads( $conditions ) { + foreach ( $conditions as $condition_name ) { + // Check if store has any products that are free. + if ( 'free' === $condition_name ) { + $args = array( + 'post_type' => 'download', + 'posts_per_page' => 1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'meta_query' => array( + array( + 'key' => 'edd_price', + 'value' => '0.00', + ), + ), + ); + + $downloads = new WP_Query( $args ); + if ( 0 === $downloads->post_count ) { + return false; + } + } + } + + return true; + } + + /** + * Check if blurb meets conditions. + * + * @since 3.1 + * + * @param array $blurb Blurb data. + * @return bool + */ + public function does_blurb_meet_conditions( $blurb ) { + if ( isset( $blurb['conditions'] ) && ! empty( $blurb['conditions'] ) ) { + foreach ( $blurb['conditions'] as $condition_name => $condition ) { + if ( empty( $condition ) ) { + continue; + } + + // Pass check. + if ( 'current_pass' === $condition_name ) { + if ( ! $this->check_blurb_current_pass( $condition ) ) { + return false; + } + } + + // Active plugins check. + if ( 'active_plugins' === $condition_name ) { + if ( ! $this->check_blurb_active_plugins( $condition ) ) { + return false; + } + } + + // Inactive plugins check. + if ( 'inactive_plugins' === $condition_name ) { + if ( ! $this->check_blurb_inactive_plugins( $condition ) ) { + return false; + } + } + + // Check for specific product/downloads. + if ( 'has_downloads' === $condition_name ) { + if ( ! $this->check_blurb_has_downloads( $condition ) ) { + return false; + } + } + } + } + + return true; + } + +} diff --git a/includes/emails/email-summary/class-edd-email-summary-cron.php b/includes/emails/email-summary/class-edd-email-summary-cron.php new file mode 100644 index 00000000000..e39593ed4c4 --- /dev/null +++ b/includes/emails/email-summary/class-edd-email-summary-cron.php @@ -0,0 +1,164 @@ +is_enabled() || wp_next_scheduled( self::CRON_EVENT_NAME ) ) { + return; + } + + // Get the event date based on user settings. + $days = EDD()->utils->date()->getDays(); + $email_frequency = edd_get_option( 'email_summary_frequency', 'weekly' ); + $week_start_day = $days[ (int) get_option( 'start_of_week' ) ]; + + if ( 'monthly' === $email_frequency ) { + $next_time_string = 'first day of next month 8am'; + } else { + $next_time_string = "next {$week_start_day} 8am"; + } + + $date = new \DateTime( $next_time_string, new DateTimeZone( edd_get_timezone_id() ) ); + wp_schedule_single_event( $date->getTimestamp(), self::CRON_EVENT_NAME ); + } + + /** + * Clear all cron events related to email summary. + * + * @since 3.1 + * + * @return void + */ + public function clear_cron_events() { + wp_clear_scheduled_hook( self::CRON_EVENT_NAME ); + } + + /** + * Detect when settings that affect the + * schedule of email summaries are updated. + * + * @since 3.1 + * + * @param string $option_name WordPress option that was changed. + * @param string $old_value Old option value. + * @param string $new_value New option value. + * + * @return void + */ + public function settings_changed( $option_name, $old_value, $new_value ) { + if ( ! in_array( $option_name, array( 'edd_settings', 'start_of_week', 'timezone_string', 'gmt_offset' ), true ) ) { + return; + } + + // If `edd_settings` were changed, listen + // only to changes in specific fields. + if ( 'edd_settings' === $option_name ) { + $change_detected = false; + $field_listeners = array( 'email_summary_frequency', 'disable_email_summary' ); + foreach ( $field_listeners as $field ) { + if ( ( empty( $old_value[ $field ] ) || empty( $new_value[ $field ] ) ) || ( $old_value[ $field ] !== $new_value[ $field ] ) ) { + $change_detected = true; + break; + } + } + + if ( ! $change_detected ) { + return; + } + + // Reload EDD options so that we have the newest values in class methods. + global $edd_options; + $edd_options = get_option( 'edd_settings' ); + } + + $this->clear_cron_events(); + $this->schedule_cron_events(); + } + + /** + * Initialize the cron with all the proper checks. + * + * @since 3.1 + * + * @return void + */ + public function run_cron() { + // This is not cron, abort! + if ( ! wp_doing_cron() ) { + return; + } + + $email = new EDD_Email_Summary(); + $email->send_email(); + + // Schedule the next event. + $this->schedule_cron_events(); + } + +} diff --git a/includes/emails/email-summary/class-edd-email-summary.php b/includes/emails/email-summary/class-edd-email-summary.php new file mode 100644 index 00000000000..4aa82d7ec64 --- /dev/null +++ b/includes/emails/email-summary/class-edd-email-summary.php @@ -0,0 +1,418 @@ +test_mode = $test_mode; + $this->email_options = array( + 'email_summary_frequency' => edd_get_option( 'email_summary_frequency', 'weekly' ), + ); + } + + /** + * Get site URL. + * + * @since 3.1 + * + * @return string Host of the site url. + */ + public function get_site_url() { + $site_url = get_site_url(); + $site_url_parsed = wp_parse_url( $site_url ); + $site_url = isset( $site_url_parsed['host'] ) ? $site_url_parsed['host'] : $site_url; + + return $site_url; + } + + /** + * Get email subject. + * + * @since 3.1 + * + * @return string Email subject. + */ + public function get_email_subject() { + /* Translators: Site domain name */ + $email_subject = sprintf( __( 'Easy Digital Downloads Summary - %s', 'easy-digital-downloads' ), $this->get_site_url() ); + + if ( $this->test_mode ) { + $email_subject = '[TEST] ' . $email_subject; + } + + return $email_subject; + } + + /** + * Get email recipients. + * + * @since 3.1 + * + * @return array Recipients to receive the email. + */ + public function get_email_recipients() { + $recipients = array(); + + if ( 'admin' === edd_get_option( 'email_summary_recipient', 'admin' ) ) { + $recipients[] = get_option( 'admin_email' ); + } else { + $emails = edd_get_option( 'email_summary_custom_recipients', '' ); + $emails = array_map( 'trim', explode( "\n", $emails ) ); + foreach ( $emails as $email ) { + if ( is_email( $email ) ) { + $recipients[] = $email; + } + } + } + + if ( empty( $recipients ) ) { + edd_debug_log( __( 'Missing email recipients for Email Summary', 'easy-digital-downloads' ), true ); + } + + return apply_filters( 'edd_email_summary_recipients', $recipients ); + } + + /** + * Get report start date. + * + * @since 3.1 + * + * @return EDD\Utils\Date An array of start date and its relative counterpart as the EDD date object set at the UTC equivalent time. + */ + public function get_report_start_date() { + $date = EDD()->utils->date( 'now', edd_get_timezone_id(), false ); + + if ( 'monthly' === $this->email_options['email_summary_frequency'] ) { + $start_date = $date->copy()->subMonth( 1 )->startOfMonth(); + $relative_start_date = $date->copy()->subMonth( 2 )->startOfMonth(); + } else { + $start_date = $date->copy()->subDay( 7 )->startOfDay(); + $relative_start_date = $date->copy()->subDay( 14 )->startOfDay(); + } + + return array( + 'start_date' => $start_date, + 'relative_start_date' => $relative_start_date, + ); + } + + /** + * Get report end date. + * + * @since 3.1 + * + * @return EDD\Utils\Date An array of end date and its relative counterpart as the EDD date object set at the UTC equivalent time. + */ + public function get_report_end_date() { + $date = EDD()->utils->date( 'now', edd_get_timezone_id(), false ); + + if ( 'monthly' === $this->email_options['email_summary_frequency'] ) { + $end_date = $date->copy()->subMonth( 1 )->endOfMonth(); + $relative_end_date = $date->copy()->subMonth( 2 )->endOfMonth(); + } else { + $end_date = $date->copy()->endOfDay(); + $relative_end_date = $date->copy()->subDay( 7 )->endOfDay(); + } + + return array( + 'end_date' => $end_date, + 'relative_end_date' => $relative_end_date, + ); + } + + /** + * Get report date range. + * + * @since 3.1 + * + * @return array Array of start and end date objects in \EDD\Utils\Date[] format. + */ + public function get_report_date_range() { + // @todo - Check if we have to convert this to UTC because of DB? + return array_merge( + $this->get_report_start_date(), + $this->get_report_end_date() + ); + } + /** + * Retrieve ! TEST ! dataset for email content. + * + * @since 3.1 + * + * @return array Data and statistics for the period. + */ + public function get_test_report_dataset() { + $stats = new EDD\Stats(); + $args = array( + 'post_type' => 'download', + 'posts_per_page' => 5, + 'fields' => 'ids', + 'no_found_rows' => true, + ); + + $downloads = new WP_Query( $args ); + $top_selling_products = array(); + + foreach ( $downloads->posts as $post ) { + $download = new EDD_Download( $post ); + + $product = new stdClass(); + $product->object = $download; + $product->total = 100; + + $top_selling_products[] = $product; + } + + $data = array( + 'earnings_gross' => array( + 'value' => 5000, + 'relative_data' => $stats->generate_relative_data( 5000, 4000 ), + ), + 'earnings_net' => array( + 'value' => 4500, + 'relative_data' => $stats->generate_relative_data( 4500, 3500 ), + ), + 'average_order_value' => array( + 'value' => 29, + 'relative_data' => $stats->generate_relative_data( 20, 35 ), + ), + 'new_customers' => array( + 'value' => 25, + 'relative_data' => $stats->generate_relative_data( 25, 20 ), + ), + 'top_selling_products' => $top_selling_products, + 'order_count' => array( 'value' => 172 ), + ); + + return $data; + } + + /** + * Retrieve dataset for email content. + * + * @since 3.1 + * + * @return array Data and statistics for the period. + */ + public function get_report_dataset() { + if ( $this->test_mode ) { + return $this->get_test_report_dataset(); + } + + $date_range = $this->get_report_date_range(); + $start_date = $date_range['start_date']->format( 'Y-m-d H:i:s' ); + $end_date = $date_range['end_date']->format( 'Y-m-d H:i:s' ); + $relative_start_date = $date_range['relative_start_date']->format( 'Y-m-d H:i:s' ); + $relative_end_date = $date_range['relative_end_date']->format( 'Y-m-d H:i:s' ); + $stats = new EDD\Stats( + array( + 'output' => 'array', + 'start' => $start_date, + 'end' => $end_date, + 'relative' => true, + 'relative_start' => $relative_start_date, + 'relative_end' => $relative_end_date, + ) + ); + + $earnings_gross = $stats->get_order_earnings( + array( + 'function' => 'SUM', + 'exclude_taxes' => false, + 'revenue_type' => 'gross', + ) + ); + + $earnings_net = $stats->get_order_earnings( + array( + 'function' => 'SUM', + 'exclude_taxes' => true, + 'revenue_type' => 'net', + ) + ); + + $average_order_value = $stats->get_order_earnings( + array( + 'function' => 'AVG', + 'exclude_taxes' => false, + ) + ); + + $new_customers = $stats->get_customer_count( + array( + 'purchase_count' => true, + ) + ); + + $top_selling_products = $stats->get_most_valuable_order_items( + array( + 'number' => 5, + ) + ); + + $order_count = $stats->get_order_count(); + + return compact( + 'earnings_gross', + 'earnings_net', + 'average_order_value', + 'new_customers', + 'top_selling_products', + 'order_count' + ); + } + + /** + * Generate HTML for relative markup. + * + * @since 3.1 + * + * @param array $relative_data Calculated relative data. + * + * @return string HTML for relative markup. + */ + private function build_relative_markup( $relative_data ) { + $arrow = $relative_data['positive_change'] ? 'icon-arrow-up.png' : 'icon-arrow-down.png'; + $output = __( 'No data to compare', 'easy-digital-downloads' ); + if ( $relative_data['no_change'] ) { + $output = __( 'No Change', 'easy-digital-downloads' ); + } elseif ( $relative_data['comparable'] ) { + $output = $relative_data['formatted_percentage_change'] . '%'; + } + ob_start(); + ?> + + + + + + + get_report_dataset(); + // If there were no sales, do not build an email template. + if ( empty( $dataset['order_count'] ) || 0 === $dataset['order_count'] ) { + return false; + } + + $date_range = $this->get_report_date_range(); + $site_url = get_site_url(); + $view_more_url = edd_get_admin_url( + array( + 'page' => 'edd-reports', + 'range' => ( 'monthly' === $this->email_options['email_summary_frequency'] ) ? 'last_month' : 'last_week', + 'relative_range' => 'previous_period', + ) + ); + $wp_date_format = get_option( 'date_format' ); + $period_name = ( 'monthly' === $this->email_options['email_summary_frequency'] ) ? __( 'month', 'easy-digital-downloads' ) : __( 'week', 'easy-digital-downloads' ); + /* Translators: period name (e.g. week) */ + $relative_text = sprintf( __( 'vs previous %s', 'easy-digital-downloads' ), $period_name ); + + ob_start(); + include EDD_PLUGIN_DIR . 'includes/emails/email-summary/edd-email-summary-template.php'; + + return ob_get_clean(); + } + + /** + * Prepare and send email. + * + * @since 3.1 + * + * @return bool True if email was sent, false if there was an error. + */ + public function send_email() { + // Get next blurb. + $email_blurbs = new EDD_Email_Summary_Blurb(); + $next_blurb = false; + + if ( ! $this->test_mode ) { + $next_blurb = $email_blurbs->get_next(); + } + + // Prepare email. + $email_body = $this->build_email_template( $next_blurb ); + // If there is no email body, we cannot continue. + if ( ! $email_body ) { + edd_debug_log( __( 'Email body for Email Summary was empty.', 'easy-digital-downloads' ), true ); + return false; + } + + $email_subject = $this->get_email_subject(); + $email_recipients = $this->get_email_recipients(); + $email_headers = array( 'Content-Type: text/html; charset=UTF-8' ); + + // Everything is ok, send email. + $email_sent = wp_mail( $email_recipients, $email_subject, $email_body, $email_headers ); + if ( $email_sent ) { + $email_blurbs->mark_blurb_sent( $next_blurb ); + } + + return $email_sent; + } +} diff --git a/includes/emails/email-summary/edd-email-summary-template.php b/includes/emails/email-summary/edd-email-summary-template.php new file mode 100644 index 00000000000..4eddf0e21d0 --- /dev/null +++ b/includes/emails/email-summary/edd-email-summary-template.php @@ -0,0 +1,418 @@ + + + + <?php echo esc_html( $this->get_email_subject() ); ?> + + + + + + + + + + + + + +

    + format( $wp_date_format ) ); ?> - format( $wp_date_format ) ); ?> +
    + + + + + + + + + + + + + + + + diff --git a/includes/emails/functions.php b/includes/emails/functions.php new file mode 100755 index 00000000000..ad6cd114ac1 --- /dev/null +++ b/includes/emails/functions.php @@ -0,0 +1,410 @@ +get_meta( '_edd_payment_meta', true ); + + $from_name = edd_get_option( 'from_name', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ); + $from_name = apply_filters( 'edd_purchase_from_name', $from_name, $payment_id, $payment_data ); + + $from_email = edd_get_option( 'from_email', get_bloginfo( 'admin_email' ) ); + $from_email = apply_filters( 'edd_purchase_from_address', $from_email, $payment_id, $payment_data ); + + if ( empty( $to_email ) ) { + $to_email = $payment->email; + } + + $subject = edd_get_option( 'purchase_subject', __( 'Purchase Receipt', 'easy-digital-downloads' ) ); + $subject = apply_filters( 'edd_purchase_subject', wp_strip_all_tags( $subject ), $payment_id ); + $subject = wp_specialchars_decode( edd_do_email_tags( $subject, $payment_id ) ); + + $heading = edd_get_option( 'purchase_heading', __( 'Purchase Receipt', 'easy-digital-downloads' ) ); + $heading = apply_filters( 'edd_purchase_heading', $heading, $payment_id, $payment_data ); + $heading = edd_do_email_tags( $heading, $payment_id ); + + $attachments = apply_filters( 'edd_receipt_attachments', array(), $payment_id, $payment_data ); + + $message = edd_do_email_tags( edd_get_email_body_content( $payment_id, $payment_data ), $payment_id ); + + $emails = EDD()->emails; + + $emails->__set( 'from_name', $from_name ); + $emails->__set( 'from_email', $from_email ); + $emails->__set( 'heading', $heading ); + + $headers = apply_filters( 'edd_receipt_headers', $emails->get_headers(), $payment_id, $payment_data ); + $emails->__set( 'headers', $headers ); + + $sent = $emails->send( $to_email, $subject, $message, $attachments ); + + if ( $admin_notice && ! edd_admin_notices_disabled( $payment_id ) ) { + do_action( 'edd_admin_sale_notice', $payment_id, $payment_data ); + } + + return $sent; +} + +/** + * Email the download link(s) and payment confirmation to the admin accounts for testing. + * + * @since 1.5 + * @return void + */ +function edd_email_test_purchase_receipt() { + + $from_name = edd_get_option( 'from_name', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ); + $from_name = apply_filters( 'edd_purchase_from_name', $from_name, 0, array() ); + + $from_email = edd_get_option( 'from_email', get_bloginfo( 'admin_email' ) ); + $from_email = apply_filters( 'edd_test_purchase_from_address', $from_email, 0, array() ); + + $subject = edd_get_option( 'purchase_subject', __( 'Purchase Receipt', 'easy-digital-downloads' ) ); + $subject = apply_filters( 'edd_purchase_subject', wp_strip_all_tags( $subject ), 0 ); + $subject = wp_specialchars_decode( edd_do_email_tags( $subject, 0 ) ); + + $heading = edd_get_option( 'purchase_heading', __( 'Purchase Receipt', 'easy-digital-downloads' ) ); + $heading = edd_email_preview_template_tags( apply_filters( 'edd_purchase_heading', $heading, 0, array() ) ); + + $attachments = apply_filters( 'edd_receipt_attachments', array(), 0, array() ); + + $message = edd_email_preview_template_tags( edd_get_email_body_content( 0, array() ), 0 ); + + $emails = EDD()->emails; + $emails->__set( 'from_name' , $from_name ); + $emails->__set( 'from_email', $from_email ); + $emails->__set( 'heading' , $heading ); + + $headers = apply_filters( 'edd_receipt_headers', $emails->get_headers(), 0, array() ); + $emails->__set( 'headers', $headers ); + + $emails->send( edd_get_admin_notice_emails(), $subject, $message, $attachments ); + +} + +/** + * Sends the Admin Sale Notification Email + * + * @since 1.4.2 + * @param int $payment_id Payment ID (default: 0) + * @param array $payment_data Payment Meta and Data + * @return void + */ +function edd_admin_email_notice( $payment_id = 0, $payment_data = array() ) { + + $payment_id = absint( $payment_id ); + + if( empty( $payment_id ) ) { + return; + } + + if( ! edd_get_payment_by( 'id', $payment_id ) ) { + return; + } + + $from_name = edd_get_option( 'from_name', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ); + $from_name = apply_filters( 'edd_purchase_from_name', $from_name, $payment_id, $payment_data ); + + $from_email = edd_get_option( 'from_email', get_bloginfo( 'admin_email' ) ); + $from_email = apply_filters( 'edd_admin_sale_from_address', $from_email, $payment_id, $payment_data ); + + $subject = edd_get_option( 'sale_notification_subject', sprintf( __( 'New download purchase - Order #%1$s', 'easy-digital-downloads' ), $payment_id ) ); + $subject = apply_filters( 'edd_admin_sale_notification_subject', wp_strip_all_tags( $subject ), $payment_id ); + $subject = wp_specialchars_decode( edd_do_email_tags( $subject, $payment_id ) ); + + $heading = edd_get_option( 'sale_notification_heading', __( 'New Sale!', 'easy-digital-downloads' ) ); + $heading = apply_filters( 'edd_admin_sale_notification_heading', $heading, $payment_id, $payment_data ); + $heading = edd_do_email_tags( $heading, $payment_id ); + + $attachments = apply_filters( 'edd_admin_sale_notification_attachments', array(), $payment_id, $payment_data ); + + $message = edd_get_sale_notification_body_content( $payment_id, $payment_data ); + + $emails = EDD()->emails; + + $emails->__set( 'from_name', $from_name ); + $emails->__set( 'from_email', $from_email ); + $emails->__set( 'heading', $heading ); + + $headers = apply_filters( 'edd_admin_sale_notification_headers', $emails->get_headers(), $payment_id, $payment_data ); + $emails->__set( 'headers', $headers ); + + $emails->send( edd_get_admin_notice_emails(), $subject, $message, $attachments ); + +} +add_action( 'edd_admin_sale_notice', 'edd_admin_email_notice', 10, 2 ); + +/** + * Retrieves the emails for which admin notifications are sent to (these can be + * changed in the EDD Settings) + * + * @since 1.0 + * @return mixed + */ +function edd_get_admin_notice_emails() { + $emails = edd_get_option( 'admin_notice_emails', false ); + $emails = strlen( trim( $emails ) ) > 0 ? $emails : get_bloginfo( 'admin_email' ); + $emails = array_map( 'trim', explode( "\n", $emails ) ); + + return apply_filters( 'edd_admin_notice_emails', $emails ); +} + +/** + * Checks whether admin sale notices are disabled + * + * @since 1.5.2 + * + * @param int $payment_id + * @return mixed + */ +function edd_admin_notices_disabled( $payment_id = 0 ) { + $ret = edd_get_option( 'disable_admin_notices', false ); + return (bool) apply_filters( 'edd_admin_notices_disabled', $ret, $payment_id ); +} + +/** + * Get sale notification email text + * + * Returns the stored email text if available, the standard email text if not + * + * @since 1.7 + * @author Daniel J Griffiths + * @return string $message + */ +function edd_get_default_sale_notification_email() { + $default_email_body = __( 'Hello', 'easy-digital-downloads' ) . "\n\n" . sprintf( __( 'A %s purchase has been made', 'easy-digital-downloads' ), edd_get_label_plural() ) . ".\n\n"; + $default_email_body .= sprintf( __( '%s sold:', 'easy-digital-downloads' ), edd_get_label_plural() ) . "\n\n"; + $default_email_body .= '{download_list}' . "\n\n"; + $default_email_body .= __( 'Purchased by: ', 'easy-digital-downloads' ) . ' {name}' . "\n"; + $default_email_body .= __( 'Amount: ', 'easy-digital-downloads' ) . ' {price}' . "\n"; + $default_email_body .= __( 'Payment Method: ', 'easy-digital-downloads' ) . ' {payment_method}' . "\n\n"; + $default_email_body .= __( 'Thank you', 'easy-digital-downloads' ); + + $message = edd_get_option( 'sale_notification', false ); + $message = ! empty( $message ) ? $message : $default_email_body; + + return $message; +} + +/** + * Get various correctly formatted names used in emails + * + * @since 1.9 + * @param $user_info + * @param $payment EDD_Payment for getting the names + * + * @return array $email_names + */ +function edd_get_email_names( $user_info, $payment = false ) { + $email_names = array(); + $email_names['fullname'] = ''; + + if ( $payment instanceof EDD_Payment ) { + + $email_names['name'] = $payment->email; + $email_names['username'] = $payment->email; + if ( $payment->user_id > 0 ) { + + $user_data = get_userdata( $payment->user_id ); + $email_names['name'] = $payment->first_name; + $email_names['fullname'] = trim( $payment->first_name . ' ' . $payment->last_name ); + if ( ! empty( $user_data->user_login ) ) { + $email_names['username'] = $user_data->user_login; + } + + } elseif ( ! empty( $payment->first_name ) ) { + + $email_names['name'] = $payment->first_name; + $email_names['fullname'] = trim( $payment->first_name . ' ' . $payment->last_name ); + $email_names['username'] = $payment->first_name; + + } + } else { + + if ( is_serialized( $user_info ) ) { + + preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $user_info, $matches ); + if ( ! empty( $matches ) ) { + return array( + 'name' => '', + 'fullname' => '', + 'username' => '', + ); + } else { + $user_info = maybe_unserialize( $user_info ); + } + + } + + if ( isset( $user_info['id'] ) && $user_info['id'] > 0 && isset( $user_info['first_name'] ) ) { + $user_data = get_userdata( $user_info['id'] ); + $email_names['name'] = $user_info['first_name']; + $email_names['fullname'] = $user_info['first_name'] . ' ' . $user_info['last_name']; + $email_names['username'] = $user_data->user_login; + } elseif ( isset( $user_info['first_name'] ) ) { + $email_names['name'] = $user_info['first_name']; + $email_names['fullname'] = $user_info['first_name'] . ' ' . $user_info['last_name']; + $email_names['username'] = $user_info['first_name']; + } else { + $email_names['name'] = $user_info['email']; + $email_names['username'] = $user_info['email']; + } + + } + + return $email_names; +} + +/** + * Handle installation and connection for Recapture via ajax + * + * @since 2.10.2 + */ +function edd_recapture_remote_install_handler () { + + if ( ! current_user_can( 'manage_shop_settings' ) || ! current_user_can( 'install_plugins' ) ) { + wp_send_json_error( array( + 'error' => __( 'You do not have permission to do this.', 'easy-digital-downloads' ) + ) ); + } + + include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + include_once ABSPATH . 'wp-admin/includes/file.php'; + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + $plugins = get_plugins(); + + if( ! array_key_exists( 'recapture-for-edd/recapture.php', $plugins ) ) { + + /* + * Use the WordPress Plugins API to get the plugin download link. + */ + $api = plugins_api( 'plugin_information', array( + 'slug' => 'recapture-for-edd', + ) ); + + if ( is_wp_error( $api ) ) { + wp_send_json_error( array( + 'error' => $api->get_error_message(), + 'debug' => $api + ) ); + } + + /* + * Use the AJAX Upgrader skin to quietly install the plugin. + */ + $upgrader = new Plugin_Upgrader( new WP_Ajax_Upgrader_Skin() ); + $install = $upgrader->install( $api->download_link ); + if ( is_wp_error( $install ) ) { + wp_send_json_error( array( + 'error' => $install->get_error_message(), + 'debug' => $api + ) ); + } + + $activated = activate_plugin( $upgrader->plugin_info() ); + + } else { + + $activated = activate_plugin( 'recapture-for-edd/recapture.php' ); + + } + + /* + * Final check to see if Recapture is available. + */ + if ( is_wp_error( $activated ) ) { + wp_send_json_error( array( + 'error' => __( 'Something went wrong. Recapture for EDD was not installed correctly.', 'easy-digital-downloads' ) + ) ); + } + + wp_send_json_success(); +} +add_action( 'wp_ajax_edd_recapture_remote_install', 'edd_recapture_remote_install_handler' ); + +/** + * Maybe adds a notice to abandoned payments if Recapture isn't installed. + * + * @since 2.10.2 + * + * @param int $payment_id The ID of the abandoned payment, for which a Recapture notice is being thrown. + */ +function maybe_add_recapture_notice_to_abandoned_payment( $payment_id ) { + + if ( ! class_exists( 'Recapture' ) + && 'abandoned' === edd_get_payment_status( $payment_id ) + && ! get_user_meta( get_current_user_id(), '_edd_try_recapture_dismissed', true ) + ) { + ?> +
    +

    + tag, %2$s - tag, %3$s - tag, %4$s - tag */ + __( '%1$sRecover abandoned purchases like this one.%2$s %3$sTry Recapture for free%4$s.', 'easy-digital-downloads' ), + '', + '', + '', + '' + ) + ); + ?> +

    + 'dismiss_notices', + 'edd_notice' => 'try_recapture', + ) + ), + 'edd_notice_nonce' + ) + ), + '" type="button" class="notice-dismiss">', + '', + ' + ' + ) + ); + ?> +
    + + + + + + $tag['label'] ? $tag['label'] : $tag['tag'], + 'tag' => $tag['tag'], + 'keywords' => array_merge( + explode( ' ', $tag['description'] ), + array( $tag['tag'] ) + ), + ); + } + + wp_localize_script( + 'edd-admin-email-tags', + 'eddEmailTagsInserter', + array( + 'items' => $items, + ) + ); +} + +/** + * Output Thickbox content. + * + * @since 3.0 + */ +function edd_email_tags_inserter_thickbox_content() { + $tags = edd_get_email_tags(); + ?> + + email_tags->add( $tag, $description, $func, $label ); +} + +/** + * Remove an email tag + * + * @since 1.9 + * + * @param string $tag Email tag to remove hook from + */ +function edd_remove_email_tag( $tag ) { + EDD()->email_tags->remove( $tag ); +} + +/** + * Check if $tag is a registered email tag + * + * @since 1.9 + * + * @param string $tag Email tag that will be searched + * + * @return bool + */ +function edd_email_tag_exists( $tag ) { + return EDD()->email_tags->email_tag_exists( $tag ); +} + +/** + * Get all email tags + * + * @since 1.9 + * + * @return array + */ +function edd_get_email_tags() { + return EDD()->email_tags->get_tags(); +} + +/** + * Get a formatted HTML list of all available email tags. + * + * @since 1.9 + * + * @return string + */ +function edd_get_emails_tags_list() { + + // Begin with empty list. + $list = ''; + + // Get all tags. + $email_tags = (array) edd_get_email_tags(); + + // Check. + if ( count( $email_tags ) > 0 ) { + + // Loop. + foreach ( $email_tags as $email_tag ) { + + // Add email tag to list. + $list .= '{' . $email_tag['tag'] . '} - ' . $email_tag['description'] . '
    '; + } + } + + // Return the list. + return $list; +} + +/** + * Search content for email tags and filter email tags through their hooks. + * + * @since 1.9 + * @since 3.0 Renamed `$payment_id` parameter to `$order_id`. + * Set default value of `$order_id` to 0. + * Set default value of `$content` to empty string. + * + * @param string $content Content to search for email tags. + * @param int $order_id Order ID. + * + * @return string Content with email tags filtered out. + */ +function edd_do_email_tags( $content = '', $order_id = 0 ) { + + // Replace all tags. + $content = EDD()->email_tags->do_tags( $content, $order_id ); + + // Maintaining backwards compatibility. + $content = apply_filters( 'edd_email_template_tags', $content, edd_get_payment_meta( $order_id ), $order_id ); + + // Return content. + return $content; +} + +/** + * Load email tags. + * + * @since 1.9 + */ +function edd_load_email_tags() { + do_action( 'edd_add_email_tags' ); +} +add_action( 'init', 'edd_load_email_tags', -999 ); + +/** + * Add default EDD email template tags. + * + * @since 1.9 + */ +function edd_setup_email_tags() { + $email_tags = array( + array( + 'tag' => 'download_list', + 'label' => __( 'Download List', 'easy-digital-downloads' ), + 'description' => __( 'A list of download links for each download purchased.', 'easy-digital-downloads' ), + 'function' => 'text/html' === EDD()->emails->get_content_type() + ? 'edd_email_tag_download_list' + : 'edd_email_tag_download_list_plain', + ), + array( + 'tag' => 'file_urls', + 'label' => __( 'File URLs', 'easy-digital-downloads' ), + 'description' => __( 'A plain-text list of download URLs for each download purchased.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_file_urls', + ), + array( + 'tag' => 'name', + 'label' => __( 'First Name', 'easy-digital-downloads' ), + 'description' => __( "The buyer's first name.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_first_name', + ), + array( + 'tag' => 'fullname', + 'label' => __( 'Full Name', 'easy-digital-downloads' ), + 'description' => __( "The buyer's full name: first and last.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_fullname', + ), + array( + 'tag' => 'username', + 'label' => __( 'Username', 'easy-digital-downloads' ), + 'description' => __( "The buyer's user name on the site, if they registered an account.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_username', + ), + array( + 'tag' => 'user_email', + 'label' => __( 'Email', 'easy-digital-downloads' ), + 'description' => __( "The buyer's email address.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_user_email', + ), + array( + 'tag' => 'billing_address', + 'label' => __( 'Billing Address', 'easy-digital-downloads' ), + 'description' => __( "The buyer's billing address.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_billing_address', + ), + array( + 'tag' => 'date', + 'label' => __( 'Purchase Date', 'easy-digital-downloads' ), + 'description' => __( 'The date of the purchase.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_date', + ), + array( + 'tag' => 'subtotal', + 'label' => __( 'Subtotal', 'easy-digital-downloads' ), + 'description' => __( 'The price of the purchase before taxes.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_subtotal', + ), + array( + 'tag' => 'tax', + 'label' => __( 'Tax', 'easy-digital-downloads' ), + 'description' => __( 'The taxed amount of the purchase', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_tax', + ), + array( + 'tag' => 'price', + 'label' => __( 'Price', 'easy-digital-downloads' ), + 'description' => __( 'The total price of the purchase', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_price', + ), + array( + 'tag' => 'payment_id', + 'label' => __( 'Payment ID', 'easy-digital-downloads' ), + 'description' => __( 'The unique identifier for this purchase.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_payment_id', + ), + array( + 'tag' => 'receipt_id', + 'label' => __( 'Receipt ID', 'easy-digital-downloads' ), + 'description' => __( 'The unique identifier for the receipt of this purchase.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_receipt_id', + ), + array( + 'tag' => 'payment_method', + 'label' => __( 'Payment Method', 'easy-digital-downloads' ), + 'description' => __( 'The method of payment used for this purchase.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_payment_method', + ), + array( + 'tag' => 'sitename', + 'label' => __( 'Site Name', 'easy-digital-downloads' ), + 'description' => __( 'Your site name.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_sitename', + ), + array( + 'tag' => 'receipt', + 'label' => __( 'Receipt', 'easy-digital-downloads' ), + 'description' => __( 'Links to the EDD success page with the text "View Receipt".', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_receipt', + ), + array( + 'tag' => 'receipt_link', + 'label' => __( 'Receipt Link', 'easy-digital-downloads' ), + 'description' => __( 'Adds a link so users can view their receipt directly on a simplified page on your site if they are unable to view it in the browser correctly.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_receipt_link', + ), + array( + 'tag' => 'discount_codes', + 'label' => __( 'Discount Codes', 'easy-digital-downloads' ), + 'description' => __( 'Adds a list of any discount codes applied to this purchase.', 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_discount_codes', + ), + array( + 'tag' => 'ip_address', + 'label' => __( 'IP Address', 'easy-digital-downloads' ), + 'description' => __( "The buyer's IP Address.", 'easy-digital-downloads' ), + 'function' => 'edd_email_tag_ip_address', + ), + ); + + // Apply edd_email_tags filter + $email_tags = apply_filters( 'edd_email_tags', $email_tags ); + + // Add email tags + foreach ( $email_tags as $email_tag ) { + $label = isset( $email_tag['label'] ) ? $email_tag['label'] : ''; + edd_add_email_tag( $email_tag['tag'], $email_tag['description'], $email_tag['function'], $label ); + } + +} +add_action( 'edd_add_email_tags', 'edd_setup_email_tags' ); + +/** + * Email template tag: download_list + * A list of download links for each download purchased + * + * @param int $payment_id + * + * @return string download_list + */ +function edd_email_tag_download_list( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $order = edd_get_order( $payment_id ); + + if ( ! $order ) { + return ''; + } + + $download_list = '
      '; + $cart_items = $payment->cart_details; + $needs_notes = array(); + + if ( $order->get_items() ) { + $show_names = apply_filters( 'edd_email_show_names', true ); + $show_links = apply_filters( 'edd_email_show_links', true ); + + foreach ( $order->get_items() as $item ) { + + if ( edd_use_skus() ) { + $sku = edd_get_download_sku( $item->product_id ); + } + + if ( edd_item_quantities_enabled() ) { + $quantity = $item->quantity; + } + + if ( $show_names ) { + + $title = '' . $item->product_name . ''; + + if ( ! empty( $quantity ) && $quantity > 1 ) { + $title .= " – " . __( 'Quantity', 'easy-digital-downloads' ) . ': ' . $quantity; + } + + if ( ! empty( $sku ) ) { + $title .= " – " . __( 'SKU', 'easy-digital-downloads' ) . ': ' . $sku; + } + + $download_list .= '
    • ' . apply_filters( 'edd_email_receipt_download_title', $title, $cart_items[ $item->cart_index ], $item->price_id, $payment_id ) . '
      '; + } + + $files = edd_get_download_files( $item->product_id, $item->price_id ); + + if ( ! empty( $files ) ) { + + foreach ( $files as $filekey => $file ) { + + if ( $show_links ) { + $download_list .= '
      '; + $file_url = edd_get_download_file_url( $order, $order->email, $filekey, $item->product_id, $item->price_id ); + $download_list .= '' . edd_get_file_name( $file ) . ''; + $download_list .= '
      '; + } else { + $download_list .= '
      '; + $download_list .= edd_get_file_name( $file ); + $download_list .= '
      '; + } + + } + + } elseif ( edd_is_bundled_product( $item->product_id ) ) { + + $bundled_products = apply_filters( 'edd_email_tag_bundled_products', edd_get_bundled_products( $item->product_id, $item->price_id ), $item, $payment_id, 'download_list' ); + + foreach ( $bundled_products as $bundle_item ) { + + $download_list .= '
      ' . get_the_title( $bundle_item ) . '
      '; + + $bundle_item_id = edd_get_bundle_item_id( $bundle_item ); + $bundle_item_price_id = edd_get_bundle_item_price_id( $bundle_item ); + $download_files = edd_get_download_files( $bundle_item_id, $bundle_item_price_id ); + + foreach ( $download_files as $filekey => $file ) { + if ( $show_links ) { + $download_list .= '
      '; + $file_url = edd_get_download_file_url( $order, $order->email, $filekey, $bundle_item_id, $bundle_item_price_id ); + $download_list .= '' . edd_get_file_name( $file ) . ''; + $download_list .= '
      '; + } else { + $download_list .= '
      '; + $download_list .= edd_get_file_name( $file ); + $download_list .= '
      '; + } + } + } + + } else { + + $no_downloads_message = apply_filters( 'edd_receipt_no_files_found_text', __( 'No downloadable files found.', 'easy-digital-downloads' ), $item->product_id ); + $no_downloads_message = apply_filters( 'edd_email_receipt_no_downloads_message', $no_downloads_message, $item->product_id, $item->price_id, $payment_id ); + + if ( ! empty( $no_downloads_message ) ){ + $download_list .= '
      '; + $download_list .= $no_downloads_message; + $download_list .= '
      '; + } + } + + if ( ! array_key_exists( $item->product_id, $needs_notes ) ) { + $item_notes = edd_get_product_notes( $item->product_id ); + if ( $item_notes ) { + $needs_notes[ $item->product_id ] = array( + 'item_name' => get_the_title( $item->product_id ), + 'item_notes' => $item_notes, + ); + } + } + + if ( $show_names ) { + $download_list .= '
    • '; + } + } + } + $download_list .= '
    '; + + // Remove any empty values. + $needs_notes = array_filter( $needs_notes ); + if ( ! empty( $needs_notes ) ) { + $download_list .= __( 'Additional information about your purchase:', 'easy-digital-downloads' ); + + $download_list .= '
      '; + foreach ( $needs_notes as $note ) { + $download_list .= '
    • ' . $note['item_name'] . "\n" . '' . $note['item_notes'] . '
    • '; + } + $download_list .= '
    '; + } + + return $download_list; +} + +/** + * Email template tag: download_list + * A list of download links for each download purchased in plaintext + * + * @since 2.1.1 + * @param int $payment_id + * + * @return string download_list + */ +function edd_email_tag_download_list_plain( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $order = edd_get_order( $payment_id ); + + $payment_data = $payment->get_meta(); + $cart_items = $payment->cart_details; + $download_list = ''; + + if ( $order->get_items() ) { + $show_names = apply_filters( 'edd_email_show_names', true ); + $show_links = apply_filters( 'edd_email_show_links', true ); + + foreach ( $order->get_items() as $item ) { + + if ( edd_use_skus() ) { + $sku = edd_get_download_sku( $item->product_id ); + } + + if ( edd_item_quantities_enabled() ) { + $quantity = $item->quantity; + } + + if ( $show_names ) { + + $title = $item->product_name; + + if ( ! empty( $quantity ) && $quantity > 1 ) { + $title .= __( 'Quantity', 'easy-digital-downloads' ) . ': ' . $quantity; + } + + if ( ! empty( $sku ) ) { + $title .= __( 'SKU', 'easy-digital-downloads' ) . ': ' . $sku; + } + + $download_list .= "\n"; + + $download_list .= apply_filters( 'edd_email_receipt_download_title', $title, $cart_items[ $item->cart_index ], $item->price_id, $payment_id ) . "\n"; + } + + $files = edd_get_download_files( $item->product_id, $item->price_id ); + + if ( ! empty( $files ) ) { + + foreach ( $files as $filekey => $file ) { + if( $show_links ) { + $download_list .= "\n"; + $file_url = edd_get_download_file_url( $order, $order->email, $filekey, $item->product_id, $item->price_id ); + $download_list .= edd_get_file_name( $file ) . ': ' . $file_url . "\n"; + } else { + $download_list .= "\n"; + $download_list .= edd_get_file_name( $file ) . "\n"; + } + } + + } elseif ( edd_is_bundled_product( $item->product_id ) ) { + + $bundled_products = apply_filters( 'edd_email_tag_bundled_products', edd_get_bundled_products( $item->product_id ), $cart_items[ $item->cart_index ], $payment_id, 'download_list' ); + + foreach ( $bundled_products as $bundle_item ) { + + $download_list .= '
    ' . get_the_title( $bundle_item ) . '
    '; + + $files = edd_get_download_files( $bundle_item ); + + foreach ( $files as $filekey => $file ) { + if( $show_links ) { + $file_url = edd_get_download_file_url( $order, $order->email, $filekey, $bundle_item, $item->price_id ); + $download_list .= edd_get_file_name( $file ) . ': ' . $file_url . "\n"; + } else { + $download_list .= edd_get_file_name( $file ) . "\n"; + } + } + } + } + + + if ( '' != edd_get_product_notes( $item->product_id ) ) { + $download_list .= "\n"; + $download_list .= edd_get_product_notes( $item->product_id ) . "\n"; + } + } + } + + return $download_list; +} + +/** + * Email template tag: file_urls + * A plain-text list of download URLs for each download purchased + * + * @param int $payment_id + * + * @return string $file_urls + */ +function edd_email_tag_file_urls( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + + $payment_data = $payment->get_meta(); + $file_urls = ''; + $cart_items = $payment->cart_details; + $email = $payment->email; + + foreach ( $cart_items as $item ) { + + $price_id = edd_get_cart_item_price_id( $item ); + $files = edd_get_download_files( $item['id'], $price_id ); + + if ( $files ) { + foreach ( $files as $filekey => $file ) { + $file_url = edd_get_download_file_url( $payment_data['key'], $email, $filekey, $item['id'], $price_id ); + + $file_urls .= esc_html( $file_url ) . '
    '; + } + } + elseif ( edd_is_bundled_product( $item['id'] ) ) { + + $bundled_products = apply_filters( 'edd_email_tag_bundled_products', edd_get_bundled_products( $item['id'] ), $item, $payment_id, 'file_urls' ); + + foreach ( $bundled_products as $bundle_item ) { + + $files = edd_get_download_files( $bundle_item ); + foreach ( $files as $filekey => $file ) { + $file_url = edd_get_download_file_url( $payment_data['key'], $email, $filekey, $bundle_item, $price_id ); + $file_urls .= esc_html( $file_url ) . '
    '; + } + + } + } + + } + + return $file_urls; +} + +/** + * Email template tag: name + * The buyer's first name + * + * @param int $payment_id + * + * @return string name + */ +function edd_email_tag_first_name( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $user_info = $payment->user_info; + + if( empty( $user_info) ) { + return ''; + } + + $email_name = edd_get_email_names( $user_info, $payment ); + + return $email_name['name']; +} + +/** + * Email template tag: fullname + * The buyer's full name, first and last + * + * @param int $payment_id + * + * @return string fullname + */ +function edd_email_tag_fullname( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $user_info = $payment->user_info; + + if( empty( $user_info ) ) { + return ''; + } + + $email_name = edd_get_email_names( $user_info, $payment ); + return $email_name['fullname']; +} + +/** + * Email template tag: username + * The buyer's user name on the site, if they registered an account + * + * @param int $payment_id + * + * @return string username + */ +function edd_email_tag_username( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $user_info = $payment->user_info; + + if( empty( $user_info ) ) { + return ''; + } + + $email_name = edd_get_email_names( $user_info, $payment ); + return $email_name['username']; +} + +/** + * Email template tag: user_email + * The buyer's email address + * + * @param int $payment_id + * + * @return string user_email + */ +function edd_email_tag_user_email( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + + return $payment->email; +} + +/** + * Email template tag: billing_address + * The buyer's billing address + * + * @param int $payment_id + * + * @return string billing_address + */ +function edd_email_tag_billing_address( $payment_id ) { + + $user_info = edd_get_payment_meta_user_info( $payment_id ); + $user_address = ! empty( $user_info['address'] ) ? $user_info['address'] : array( 'line1' => '', 'line2' => '', 'city' => '', 'country' => '', 'state' => '', 'zip' => '' ); + + $return = $user_address['line1'] . "\n"; + if( ! empty( $user_address['line2'] ) ) { + $return .= $user_address['line2'] . "\n"; + } + $return .= $user_address['city'] . ' ' . $user_address['zip'] . ' ' . $user_address['state'] . "\n"; + $return .= $user_address['country']; + + return $return; +} + +/** + * Email template tag: date + * Date of purchase + * + * @param int $payment_id + * + * @return string date + */ +function edd_email_tag_date( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + return date_i18n( get_option( 'date_format' ), strtotime( $payment->date ) ); +} + +/** + * Email template tag: subtotal + * Price of purchase before taxes + * + * @param int $payment_id + * + * @return string subtotal + */ +function edd_email_tag_subtotal( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $subtotal = edd_currency_filter( edd_format_amount( $payment->subtotal ), $payment->currency ); + return html_entity_decode( $subtotal, ENT_COMPAT, 'UTF-8' ); +} + +/** + * Email template tag: tax + * The taxed amount of the purchase + * + * @param int $payment_id + * + * @return string tax + */ +function edd_email_tag_tax( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $tax = edd_currency_filter( edd_format_amount( $payment->tax ), $payment->currency ); + return html_entity_decode( $tax, ENT_COMPAT, 'UTF-8' ); +} + +/** + * Email template tag: price + * The total price of the purchase + * + * @param int $payment_id + * + * @return string price + */ +function edd_email_tag_price( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + $price = edd_currency_filter( edd_format_amount( $payment->total ), $payment->currency ); + return html_entity_decode( $price, ENT_COMPAT, 'UTF-8' ); +} + +/** + * Email template tag: payment_id + * The unique ID number for this purchase + * + * @param int $payment_id + * + * @return int payment_id + */ +function edd_email_tag_payment_id( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + return $payment->number; +} + +/** + * Email template tag: receipt_id + * The unique ID number for this purchase receipt + * + * @param int $payment_id + * + * @return string receipt_id + */ +function edd_email_tag_receipt_id( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + return $payment->key; +} + +/** + * Email template tag: payment_method + * The method of payment used for this purchase + * + * @param int $payment_id + * + * @return string gateway + */ +function edd_email_tag_payment_method( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + return edd_get_gateway_checkout_label( $payment->gateway ); +} + +/** + * Email template tag: sitename + * Your site name + * + * @param int $payment_id + * + * @return string sitename + */ +function edd_email_tag_sitename( $payment_id ) { + return wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ); +} + +/** + * Email template tag: receipt + * + * Adds a link to the user's receipt page on the website. + * + * @param [type] $order_id + * @return void + */ +function edd_email_tag_receipt( $order_id ) { + + return sprintf( + '%s', + esc_url( edd_get_receipt_page_uri( $order_id ) ), + __( 'View Receipt', 'easy-digital-downloads' ) + ); +} + +/** + * Email template tag: receipt_link + * Adds a link so users can view their receipt directly on your website if they are unable to view it in the browser correctly + * + * @param $payment_id int + * + * @return string receipt_link + */ +function edd_email_tag_receipt_link( $payment_id ) { + $receipt_url = esc_url( + add_query_arg( + array( + 'payment_key' => urlencode( edd_get_payment_key( $payment_id ) ), + 'edd_action' => 'view_receipt', + ), + home_url() + ) + ); + $formatted = sprintf( __( '%1$sView it in your browser %2$s', 'easy-digital-downloads' ), '', '»' ); + + if ( edd_get_option( 'email_template' ) !== 'none' ) { + return $formatted; + } else { + return $receipt_url; + } +} + +/** + * Email template tag: discount_codes + * Adds a list of any discount codes applied to this purchase + * + * @since 2.0 + * @param int $payment_id + * @return string $discount_codes + */ +function edd_email_tag_discount_codes( $payment_id ) { + $user_info = edd_get_payment_meta_user_info( $payment_id ); + + $discount_codes = ''; + + if( isset( $user_info['discount'] ) && $user_info['discount'] !== 'none' ) { + $discount_codes = $user_info['discount']; + } + + return $discount_codes; +} + +/** + * Email template tag: IP address + * IP address of the customer + * + * @since 2.3 + * @param int $payment_id + * @return string IP address + */ +function edd_email_tag_ip_address( $payment_id ) { + $payment = new EDD_Payment( $payment_id ); + return $payment->ip; +} diff --git a/includes/emails/template.php b/includes/emails/template.php new file mode 100755 index 00000000000..9c280182508 --- /dev/null +++ b/includes/emails/template.php @@ -0,0 +1,306 @@ +get_templates() + * + * @since 1.0.8.2 + * @return array $templates All the registered email templates + */ +function edd_get_email_templates() { + $templates = new EDD_Emails; + return $templates->get_templates(); +} + +/** + * Email Template Tags + * + * @since 1.0 + * + * @param string $message Message with the template tags + * @param array $payment_data Payment Data + * @param int $payment_id Payment ID + * @param bool $admin_notice Whether or not this is a notification email + * + * @return string $message Fully formatted message + */ +function edd_email_template_tags( $message, $payment_data, $payment_id, $admin_notice = false ) { + return edd_do_email_tags( $message, $payment_id ); +} + +/** + * Email Preview Template Tags + * + * @since 1.0 + * @param string $message Email message with template tags + * @return string $message Fully formatted message + */ +function edd_email_preview_template_tags( $message ) { + $download_list = '
      '; + $download_list .= '
    • ' . __( 'Sample Product Title', 'easy-digital-downloads' ) . '
      '; + $download_list .= '
      '; + $download_list .= '' . __( 'Sample Download File Name', 'easy-digital-downloads' ) . ' - ' . __( 'Optional notes about this download.', 'easy-digital-downloads' ) . ''; + $download_list .= '
      '; + $download_list .= '
    • '; + $download_list .= '
    '; + + $file_urls = esc_html( trailingslashit( get_site_url() ) . 'test.zip?test=key&key=123' ); + + $price = edd_currency_filter( edd_format_amount( 10.50 ) ); + + $gateway = edd_get_gateway_admin_label( edd_get_default_gateway() ); + + $receipt_id = strtolower( md5( uniqid() ) ); + + $notes = __( 'These are some sample notes added to a product.', 'easy-digital-downloads' ); + + $tax = edd_currency_filter( edd_format_amount( 1.00 ) ); + + $sub_total = edd_currency_filter( edd_format_amount( 9.50 ) ); + + $payment_id = rand(1, 100); + + $user = wp_get_current_user(); + + $message = str_replace( '{download_list}', $download_list, $message ); + $message = str_replace( '{file_urls}', $file_urls, $message ); + $message = str_replace( '{name}', $user->display_name, $message ); + $message = str_replace( '{fullname}', $user->display_name, $message ); + $message = str_replace( '{username}', $user->user_login, $message ); + $message = str_replace( '{date}', edd_date_i18n( current_time( 'timestamp' ) ), $message ); + $message = str_replace( '{subtotal}', $sub_total, $message ); + $message = str_replace( '{tax}', $tax, $message ); + $message = str_replace( '{price}', $price, $message ); + $message = str_replace( '{receipt_id}', $receipt_id, $message ); + $message = str_replace( '{payment_method}', $gateway, $message ); + $message = str_replace( '{sitename}', get_bloginfo( 'name' ), $message ); + $message = str_replace( '{product_notes}', $notes, $message ); + $message = str_replace( '{payment_id}', $payment_id, $message ); + $message = str_replace( '{receipt_link}', edd_email_tag_receipt_link( $payment_id ), $message ); + $message = str_replace( '{receipt}', edd_email_tag_receipt( $payment_id ), $message ); + + $message = apply_filters( 'edd_email_preview_template_tags', $message ); + + return apply_filters( 'edd_email_template_wpautop', true ) ? wpautop( $message ) : $message; +} + +/** + * Email Template Preview + * + * @access private + * @since 1.0.8.2 + */ +function edd_email_template_preview() { + if( ! current_user_can( 'manage_shop_settings' ) ) { + return; + } + + ob_start(); + ?> + + + emails->heading = edd_email_preview_template_tags( edd_get_option( 'purchase_heading', __( 'Purchase Receipt', 'easy-digital-downloads' ) ) ); + + echo EDD()->emails->build_email( edd_email_preview_template_tags( edd_get_email_body_content( 0, array() ) ) ); + + exit; + +} +add_action( 'template_redirect', 'edd_display_email_template_preview' ); + +/** + * Email Template Body + * + * @since 1.0.8.2 + * @param int $payment_id Payment ID + * @param array $payment_data Payment Data + * @return string $email_body Body of the email + */ +function edd_get_email_body_content( $payment_id = 0, $payment_data = array() ) { + $default_email_body = __( "Dear", "easy-digital-downloads" ) . " {name},\n\n"; + $default_email_body .= __( "Thank you for your purchase. Please click on the link(s) below to download your files.", "easy-digital-downloads" ) . "\n\n"; + $default_email_body .= "{download_list}\n\n"; + $default_email_body .= "{sitename}"; + + $email = edd_get_option( 'purchase_receipt', false ); + $email = $email ? stripslashes( $email ) : $default_email_body; + + $email_body = apply_filters( 'edd_email_template_wpautop', true ) ? wpautop( $email ) : $email; + + $email_body = apply_filters( 'edd_purchase_receipt_' . EDD()->emails->get_template(), $email_body, $payment_id, $payment_data ); + + return apply_filters( 'edd_purchase_receipt', $email_body, $payment_id, $payment_data ); +} + +/** + * Sale Notification Template Body + * + * @since 1.7 + * @author Daniel J Griffiths + * @param int $payment_id Payment ID + * @param array $payment_data Payment Data + * @return string $email_body Body of the email + */ +function edd_get_sale_notification_body_content( $payment_id = 0, $payment_data = array() ) { + $payment = edd_get_payment( $payment_id ); + $order = edd_get_order( $payment_id ); + + $name = $payment->email; + if ( $payment->user_id > 0 ) { + $user_data = get_userdata( $payment->user_id ); + if ( ! empty( $user_data->display_name ) ) { + $name = $user_data->display_name; + } + } elseif ( ! empty( $payment->first_name ) && ! empty( $payment->last_name ) ) { + $name = $payment->first_name . ' ' . $payment->last_name; + } + + $download_list = ''; + + $order_items = $order->get_items(); + if( ! empty( $order_items ) ) { + foreach( $order_items as $item ) { + $download_list .= html_entity_decode( $item->product_name, ENT_COMPAT, 'UTF-8' ) . "\n"; + } + } + + $gateway = edd_get_gateway_checkout_label( $payment->gateway ); + + $default_email_body = __( 'Hello', 'easy-digital-downloads' ) . "\n\n" . sprintf( __( 'A %s purchase has been made', 'easy-digital-downloads' ), edd_get_label_plural() ) . ".\n\n"; + $default_email_body .= sprintf( __( '%s sold:', 'easy-digital-downloads' ), edd_get_label_plural() ) . "\n\n"; + $default_email_body .= $download_list . "\n\n"; + $default_email_body .= __( 'Purchased by: ', 'easy-digital-downloads' ) . " " . html_entity_decode( $name, ENT_COMPAT, 'UTF-8' ) . "\n"; + $default_email_body .= __( 'Amount: ', 'easy-digital-downloads' ) . " " . html_entity_decode( edd_currency_filter( edd_format_amount( $payment->total ) ), ENT_COMPAT, 'UTF-8' ) . "\n"; + $default_email_body .= __( 'Payment Method: ', 'easy-digital-downloads' ) . " " . $gateway . "\n\n"; + $default_email_body .= __( 'Thank you', 'easy-digital-downloads' ); + + $message = edd_get_option( 'sale_notification', false ); + $message = $message ? stripslashes( $message ) : $default_email_body; + + //$email_body = edd_email_template_tags( $email, $payment_data, $payment_id, true ); + $email_body = edd_do_email_tags( $message, $payment_id ); + + $email_body = apply_filters( 'edd_email_template_wpautop', true ) ? wpautop( $email_body ) : $email_body; + + return apply_filters( 'edd_sale_notification', $email_body, $payment_id, $payment_data ); +} + +/** + * Render Receipt in the Browser + * + * A link is added to the Purchase Receipt to view the email in the browser and + * this function renders the Purchase Receipt in the browser. It overrides the + * Purchase Receipt template and provides its only styling. + * + * @since 1.5 + * @author Sunny Ratilal + * @param array $data The request data. + */ +function edd_render_receipt_in_browser( $data ) { + if ( ! isset( $data['payment_key'] ) ) { + wp_die( __( 'Missing purchase key.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ) ); + } + + if ( ! empty( $_POST['edd_action'] ) && ! empty( $_POST['edd_user_login'] ) && ! empty( $_POST['edd_login_nonce'] ) ) { + return; + } + + $key = urlencode( $data['payment_key'] ); + + ob_start(); + + // Disallows caching of the page + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1 + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); // HTTP/1.0 + header("Expires: Sat, 23 Oct 1977 05:00:00 PST"); // Date in the past +?> + + + + <?php esc_html_e( 'Receipt', 'easy-digital-downloads' ); ?> + + + + + + +
    + + + +
    + + + +'; - // Loop error codes and display errors - foreach($errors as $error_id => $error){ - echo '

    ' . __('Error', 'edd') . ': ' . $error . '

    '; - } - echo ''; + $errors = edd_get_errors(); + $successes = EDD()->session->get( 'edd_success_errors' ); + if ( $errors || $successes ) { + + echo edd_build_errors_html( $errors ); + echo edd_build_successes_html( $successes ); + edd_clear_errors(); } } -add_action('edd_payment_mode_bottom', 'edd_print_errors'); -add_action('edd_before_purchase_form', 'edd_print_errors'); -add_action('edd_before_checkout_register_form', 'edd_print_errors'); +add_action( 'edd_purchase_form_before_submit', 'edd_print_errors' ); +add_action( 'edd_ajax_checkout_errors', 'edd_print_errors' ); +add_action( 'edd_print_errors', 'edd_print_errors' ); + +/** + * Formats error messages and returns an HTML string. + * + * @param array $errors + * + * @since 2.11 + * @return string + */ +function edd_build_errors_html( $errors ) { + $error_html = ''; + + $classes = apply_filters( 'edd_error_class', array( + 'edd_errors', 'edd-alert', 'edd-alert-error' + ) ); + + if ( ! empty( $errors ) && is_array( $errors ) ) { + $error_html .= '
    '; + // Loop error codes and display errors + foreach ( $errors as $error_id => $error ) { + $error_html .= '

    ' . __( 'Error', 'easy-digital-downloads' ) . ': ' . $error . '

    '; + + } + $error_html .= '
    '; + } + + return $error_html; +} +/** + * Builds the HTML output for the sucess messages. + * + * @since 3.1 + * @param array $successes + * @return string + */ +function edd_build_successes_html( $successes ) { + if ( empty( $successes ) || ! is_array( $successes ) ) { + return ''; + } + + $html = '
    '; + foreach ( $successes as $id => $message ) { + $html .= '

    '; + $html .= '' . esc_html__( 'Success', 'easy-digital-downloads' ) . ': '; + $html .= $message; + $html .= '

    '; + } + $html .= '
    '; + + return $html; +} /** * Get Errors @@ -44,52 +96,113 @@ function edd_print_errors() { * Retrieves all error messages stored during the checkout process. * If errors exist, they are returned. * - * @access public - * @since 1.0 - * @return mixed - array if errors are present, false if none found -*/ - + * @since 1.0 + * @uses EDD_Session::get() + * @return mixed array if errors are present, false if none found + */ function edd_get_errors() { - if(isset($_SESSION['edd-errors'])) { - $errors = $_SESSION['edd-errors']; - return $errors; - } - return false; + $errors = EDD()->session->get( 'edd_errors' ); + $errors = apply_filters( 'edd_errors', $errors ); + return $errors; } - /** * Set Error * * Stores an error in a session var. * - * @access public - * @since 1.0 - * @param $error_id string - the ID of the error being set - * @param $error_message - the message to store with the error - * @return void -*/ - -function edd_set_error($error_id, $error_message) { + * @since 1.0 + * @uses EDD_Session::get() + * @param int $error_id ID of the error being set + * @param string $error_message Message to store with the error + * @return void + */ +function edd_set_error( $error_id, $error_message ) { $errors = edd_get_errors(); - if(!$errors) { + if ( ! $errors ) { $errors = array(); } - $errors[$error_id] = $error_message; - $_SESSION['edd-errors'] = $errors; + $errors[ $error_id ] = $error_message; + EDD()->session->set( 'edd_errors', $errors ); } - /** - * Clear Errors + * Stores an array of success messages in a session variable. * + * @since 3.1 + * @uses EDD_Session::set() + * @param string $error_id + * @param string $error_message + * @return void + */ +function edd_set_success( $error_id, $error_message ) { + $successes = EDD()->session->get( 'edd_success_errors' ); + if ( ! $successes ) { + $successes = array(); + } + $successes[ $error_id ] = $error_message; + + EDD()->session->set( 'edd_success_errors', $successes ); +} + +/** * Clears all stored errors. * - * @access public - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @uses EDD_Session::set() + * @return void + */ function edd_clear_errors() { - if(isset($_SESSION['edd-errors'])) $_SESSION['edd-errors'] = null; -} \ No newline at end of file + EDD()->session->set( 'edd_errors', null ); + EDD()->session->set( 'edd_success_errors', null ); +} + +/** + * Removes (unsets) a stored error + * + * @since 1.3.4 + * @uses EDD_Session::set() + * @param int $error_id ID of the error being set + * @return string + */ +function edd_unset_error( $error_id ) { + $errors = edd_get_errors(); + + if ( $errors && isset( $errors[ $error_id ] ) ) { + unset( $errors[ $error_id ] ); + EDD()->session->set( 'edd_errors', $errors ); + } +} + +/** + * Register die handler for edd_die() + * + * @author Sunny Ratilal + * @since 1.6 + * + * @return void + */ +function _edd_die_handler() { + die(); +} + +/** + * Wrapper function for wp_die(). + * + * This function adds filters for wp_die() which kills execution of the script + * using wp_die(). This allows us to then to work with functions using edd_die() + * in the unit tests. + * + * @author Sunny Ratilal + * @since 1.6 + * @return void + */ +function edd_die( $message = '', $title = '', $status = 400 ) { + if ( ! defined( 'EDD_UNIT_TESTS' ) ) { + add_filter( 'wp_die_ajax_handler', '_edd_die_handler', 10, 3 ); + add_filter( 'wp_die_handler' , '_edd_die_handler', 10, 3 ); + add_filter( 'wp_die_json_handler', '_edd_die_handler', 10, 3 ); + } + + wp_die( $message, $title, array( 'response' => $status ) ); +} diff --git a/includes/extensions/licensing-functions.php b/includes/extensions/licensing-functions.php new file mode 100644 index 00000000000..db7eb54f173 --- /dev/null +++ b/includes/extensions/licensing-functions.php @@ -0,0 +1,114 @@ + strtotime( '+1 day' ), + 'products' => get_licensed_products(), + ) ), false ); + } +}, 200 ); + +/** + * Returns licensed EDD extensions that are active on this site. + * Array values are the `$item_shortname` from `\EDD_License` + * + * @see \EDD_License::$item_shortname + * + * @since 2.11.4 + * @return array + */ +function get_licensed_extension_slugs() { + $products = get_option( 'edd_licensed_extensions' ); + + /* + * If this isn't set for some reason, fall back to trying the global. There are + * probably a very limited number of cases where the option would be empty when + * the global is not, but worth a shot. + */ + if ( empty( $products ) ) { + return get_licensed_products(); + } + + $products = json_decode( $products, true ); + + return isset( $products['products'] ) && is_array( $products['products'] ) + ? $products['products'] + : array(); +} + +/** + * Triggers our hook for registering extensions. + * This needs to run after all plugins have definitely been loaded. + * + * @since 2.11.4 + */ +add_action( 'plugins_loaded', function() { + /** + * Extensions should hook in here to register themselves. + * + * @since 2.11.4 + * + * @param ExtensionRegistry + */ + do_action( 'edd_extension_license_init', EDD()->extensionRegistry ); +}, PHP_INT_MAX ); + +/** + * Helper function to get the actually licensed products from the global. + * In 3.1.1.2, all products using the licensing class add their slug to the global, + * but we are now tracking unlicensed products as well as licensed ones. + * + * @return void + */ +function get_licensed_products() { + $products = array(); + global $edd_licensed_products; + if ( empty( $edd_licensed_products ) || ! is_array( $edd_licensed_products ) ) { + return $products; + } + foreach ( $edd_licensed_products as $slug => $is_licensed ) { + if ( $is_licensed ) { + $products[] = $slug; + } + } + + return $products; +} diff --git a/includes/formatting.php b/includes/formatting.php old mode 100644 new mode 100755 index c286f3d59d0..ed7cfe40206 --- a/includes/formatting.php +++ b/includes/formatting.php @@ -2,118 +2,348 @@ /** * Formatting functions for taking care of proper number formats and such * - * @package Easy Digital Downloads - * @subpackage Formatting functions - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Functions/Formatting + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License * @since 1.2 */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** - * Sanitize Amount + * Sanitize a numeric value. * - * Returns a sanitized amount by stripping out thousands separators. + * Use this function to "unformat" a previously formatted numeric value. * - * @access public - * @since 1.0 - * @param $amount string the price amount to format - * @return string - the newly sanitize amount -*/ + * (Most commonly, this is when accepting input from a form field where the + * value is likely to derived from the site or user preferences.) + * + * @since 1.0 + * + * @param mixed $amount Default 0. Numeric amount to sanitize. + * + * @return string $amount Newly sanitized amount. + */ +function edd_sanitize_amount( $amount = 0 ) { + + // Get separators + $decimal_sep = edd_get_option( 'decimal_separator', '.' ); + $thousands_sep = edd_get_option( 'thousands_separator', ',' ); -function edd_sanitize_amount( $amount ) { + // Look for separators in amount + $found_decimal = strpos( $amount, $decimal_sep ); + $found_thousands = strpos( $amount, $thousands_sep ); - global $edd_options; - $thousands_sep = isset($edd_options['thousands_separator']) ? $edd_options['thousands_separator'] : ','; - $decimal_sep = isset($edd_options['decimal_separator']) ? $edd_options['decimal_separator'] : '.'; + // Amount contains comma as decimal separator + if ( ( $decimal_sep === ',' ) && ( false !== $found_decimal ) ) { - // sanitize the amount - if( $thousands_sep == '.' && false !== ( $found = strpos( $amount, $thousands_sep ) ) ) { + // Amount contains period or space as thousands separator + if ( in_array( $thousands_sep, array( '.', ' ' ), true ) && ( false !== $found_thousands ) ) { + $amount = str_replace( $thousands_sep, '', $amount ); + + // Amount contains period + } elseif ( empty( $thousands_sep ) && ( false !== strpos( $amount, '.' ) ) ) { + $amount = str_replace( '.', '', $amount ); + } + + $amount = str_replace( $decimal_sep, '.', $amount ); + + // Amount contains comma as thousands separator + } elseif ( ( $thousands_sep === ',' ) && ( false !== $found_thousands ) ) { $amount = str_replace( $thousands_sep, '', $amount ); } - if( $decimal_sep == ',' && false !== ( $found = strpos( $amount, $decimal_sep ) ) ) { - $amount = str_replace( $decimal_sep, '.', $amount ); + + // Remove anything that's not a number, period, or negative sign. + $amount = preg_replace( '/[^0-9\.\-]/', '', $amount ); + + // Check if negative. + $negative_exponent = ( $amount < 0 ) + ? -1 + : 1; + + // Cast the amount to an absolute value. + $amount = '' === $amount ? 0 : abs( (float) $amount ); + + /** + * Filter number of decimals to use for sanitized amount + * + * @since unknown + * + * @param int $number Default 2. Number of decimals. + * @param int|string $amount Amount being sanitized. + */ + $decimals = apply_filters( 'edd_sanitize_amount_decimals', 2, $amount ); + + // Flip back to negative + $sanitized = $amount * $negative_exponent; + + // Format amount using decimals and a period for the decimal separator + // (no thousands separator; also rounds up or down) + $sanitized = number_format( (float) $sanitized, $decimals, '.', '' ); + + /** + * Filter the sanitized amount before returning + * + * @since unknown + * + * @param mixed $sanitized Sanitized amount. + * @param mixed $amount Original amount. + * @param int $decimals Default 2. Number of decimals. + * @param string $decimal_sep Default '.'. Decimal separator. + * @param string $thousands_sep Default ','. Thousands separator. + */ + return apply_filters( 'edd_sanitize_amount', $sanitized, $amount, $decimals, $decimal_sep, $thousands_sep ); +} + +/** + * Format a numeric value. + * + * Uses the decimal & thousands separator settings, and the number of decimals, + * to format any numeric value. + * + * (Most commonly, this is used to apply site or user preferences to a numeric + * value for output to the page.) + * + * @since 1.0 + * @since 3.0 Added `$currency` parameter. + * + * @param mixed $amount Default 0. Numeric amount to format. + * @param string $decimals Default true. Whether or not to use decimals. Useful when set to false for non-currency numbers. + * @param string $currency Currency code to format the amount for. This determines how many decimals are used. + * If omitted, site-wide currency is used. + * @param string $context Defines the context in which we are formatting the data (formatted), for display or for data useage like API (typed). + * + * @return string $amount Newly formatted amount or Price Not Available + */ +function edd_format_amount( $amount = 0, $decimals = true, $currency = '', $context = 'display' ) { + if ( empty( $currency ) ) { + $currency = edd_get_currency(); + } + + $formatter = new \EDD\Currency\Money_Formatter( $amount, new \EDD\Currency\Currency( $currency ) ); + + switch ( $context ) { + case 'typed': + $return_value = $formatter->format_for_typed( $decimals )->typed_amount; + break; + case 'display': + default: + $return_value = $formatter->format_for_display( $decimals )->amount; + break; } - return apply_filters( 'edd_sanitize_amount', $amount ); + return $return_value; } +/** + * Formats the currency display + * + * @since 1.0 + * + * @param string $price Price. This should already be formatted. + * @param string $currency Currency code. When this function is used on an order's amount, the order's currency + * should always be provided here. If omitted, the store currency is used instead. + * But to ensure immutability with orders, the currency should always be explicitly provided + * if known and tied to an existing order. + * + * @return string $currency Currencies displayed correctly + */ +function edd_currency_filter( $price = '', $currency = '' ) { + + // Fallback to default currency + if ( empty( $currency ) ) { + $currency = edd_get_currency(); + } + + $currency = new \EDD\Currency\Currency( $currency ); + if ( '' === $price ) { + return $currency->symbol; + } + + $formatter = new \EDD\Currency\Money_Formatter( $price, $currency ); + + return $formatter->apply_symbol(); +} /** - * Format Amount + * Set the number of decimal places per currency + * + * @since 1.4.2 + * @since 3.0 Updated to allow currency to be passed in. * - * Returns a nicely formatted amount. + * @param int $decimals Number of decimal places. + * @param string $currency Currency. * - * @access public - * @since 1.0 - * @param $amount string the price amount to format - * @return string - the newly formatted amount + * @return int $decimals Number of decimal places for currency. */ +function edd_currency_decimal_filter( $decimals = 2, $currency = '' ) { + $currency = empty( $currency ) + ? edd_get_currency() + : $currency; -function edd_format_amount($amount) { - global $edd_options; - $thousands_sep = isset($edd_options['thousands_separator']) ? $edd_options['thousands_separator'] : ','; - $decimal_sep = isset($edd_options['decimal_separator']) ? $edd_options['decimal_separator'] : '.'; + switch ( $currency ) { + case 'RIAL' : + case 'JPY' : + case 'TWD' : + case 'HUF' : + $decimals = 0; + break; + } + return apply_filters( 'edd_currency_decimal_count', $decimals, $currency ); +} +add_filter( 'edd_sanitize_amount_decimals', 'edd_currency_decimal_filter' ); +add_filter( 'edd_format_amount_decimals', 'edd_currency_decimal_filter', 10, 2 ); - // format the amount - if( $decimal_sep == ',' && false !== ( $found = strpos( $amount, $decimal_sep ) ) ) { - $whole = substr( $amount, 0, $sep_found ); - $part = substr( $amount, $sep_found + 1, ( strlen( $amount ) - 1 ) ); - $amount = $whole . '.' . $part; - } - return number_format( $amount, 2, $decimal_sep, $thousands_sep ); +/** + * Sanitizes a string key for EDD Settings + * + * Keys are used as internal identifiers. Alphanumeric characters, dashes, + * underscores, stops, colons and slashes are allowed. + * + * This differs from `sanitize_key()` in that it allows uppercase letters, + * stops, colons, and slashes. + * + * @since 2.5.8 + * @param string $key String key + * @return string Sanitized key + */ +function edd_sanitize_key( $key = '' ) { + $raw_key = $key; + $key = preg_replace( '/[^a-zA-Z0-9_\-\.\:\/]/', '', $key ); + + /** + * Filter a sanitized key string. + * + * @since 2.5.8 + * @param string $key Sanitized key. + * @param string $raw_key The key prior to sanitization. + */ + return apply_filters( 'edd_sanitize_key', $key, $raw_key ); } +/** + * Never let a numeric value be less than zero. + * + * Adapted from bbPress. + * + * @since 3.0 + * + * @param int $number Default 0. + * @return int. + */ +function edd_number_not_negative( $number = 0 ) { + + // Protect against formatted strings + if ( is_string( $number ) ) { + $number = strip_tags( $number ); // No HTML + $number = preg_replace( '/[^0-9-]/', '', $number ); // No number-format + // Protect against objects, arrays, scalars, etc... + } elseif ( ! is_numeric( $number ) ) { + $number = 0; + } + + // Make the number an integer + $casted_number = is_float( $number ) + ? floatval( $number ) + : intval( $number ); + + $max_value = is_float( $number ) + ? 0.00 + : 0; + // Pick the maximum value, never less than zero + $not_less_than_zero = max( $max_value, $casted_number ); + + // Filter & return + return (int) apply_filters( 'edd_number_not_negative', $not_less_than_zero, $casted_number, $number ); +} /** - * Formats the currency display + * Return array of allowed HTML tags. * - * @access public - * @since 1.0 - * @return array -*/ + * Used with wp_kses() to filter unsafe HTML out of settings and notes. + * + * @since 3.0 + * + * @return array + */ +function edd_get_allowed_tags() { + return (array) apply_filters( 'edd_allowed_html_tags', array( + 'p' => array( + 'class' => array(), + 'id' => array(), + ), + 'span' => array( + 'class' => array(), + 'id' => array(), + ), + 'a' => array( + 'href' => array(), + 'target' => array(), + 'title' => array(), + 'class' => array(), + 'id' => array(), + ), + 'code' => array(), + 'strong' => array(), + 'em' => array(), + 'br' => array(), + 'img' => array( + 'src' => array(), + 'title' => array(), + 'alt' => array(), + 'id' => array(), + ), + 'div' => array( + 'class' => array(), + 'id' => array(), + ), + 'ul' => array( + 'class' => array(), + 'id' => array(), + ), + 'li' => array( + 'class' => array(), + 'id' => array(), + ), + ) ); +} -function edd_currency_filter( $price ) { - global $edd_options; - $currency = isset($edd_options['currency']) ? $edd_options['currency'] : 'USD'; - $position = isset($edd_options['currency_position']) ? $edd_options['currency_position'] : 'before'; - if($position == 'before') : - switch ($currency) : - case "GBP" : return '£' . $price; break; - case "USD" : - case "AUD" : - case "BRL" : - case "CAD" : - case "HKD" : - case "MXN" : - case "SGD" : - return '$' . $price; - break; - case "JPY" : return '¥' . $price; break; - default : - $formatted = $currency . ' ' . $price; - return apply_filters('edd_' . strtolower($currency) . '_currency_filter_before', $formatted, $currency, $price); - break; - endswitch; - else : - switch ($currency) : - case "GBP" : return $price . '£'; break; - case "USD" : - case "AUD" : - case "BRL" : - case "CAD" : - case "HKD" : - case "MXN" : - case "SGD" : - return $price . '$'; - break; - case "JPY" : return $price . '¥'; break; - default : - $formatted = $price . ' ' . $currency; - return apply_filters('edd_' . strtolower($currency) . '_currency_filter_after', $formatted, $currency, $price); - break; - endswitch; - endif; -} \ No newline at end of file +/** + * Return a translatable and display ready string for an address type. + * + * @since 3.0 + * @param string $address_type The type of address to get the display label for. + * + * @return string The translatable string for the display type, in lowercase. + */ +function edd_get_address_type_label( $address_type = 'billing' ) { + + // Core default address types and their labels. + $address_type_labels = array( + 'billing' => __( 'Billing', 'easy-digital-downloads' ), + ); + + /** + * Physical address type labels. + * + * A key/value array of billing types found in the 'type' column of the customer address table, and their translatable + * strings for output. + * + * @since 3.0 + * @param array $address_type_labels + * Array of the address type labels, in key/value form. The key should match the database entry for the + * wp_edd_customer_addresses table in the 'type' column. The value of each array entry should be a translatable + * string for output in the UI. + */ + $address_type_labels = apply_filters( 'edd_address_type_labels', $address_type_labels ); + + // Fallback to just applying an upper case to any words not in the filter. + return array_key_exists( $address_type, $address_type_labels ) ? + $address_type_labels[ $address_type ] : + $address_type; + +} diff --git a/includes/gateway-functions.php b/includes/gateway-functions.php deleted file mode 100755 index d4dd92f4545..00000000000 --- a/includes/gateway-functions.php +++ /dev/null @@ -1,126 +0,0 @@ - array('admin_label' => 'PayPal', 'checkout_label' => 'PayPal'), - 'manual' => array('admin_label' => __('Test Payment', 'edd'), 'checkout_label' => __('Test Payment', 'edd')), - ); - - return apply_filters('edd_payment_gateways', $gateways); - -} - - -/** - * Get Enabled Payment Gateways - * - * Returns a list of all enabled gateways. - * - * @access public - * @since 1.0 - * @return array -*/ - -function edd_get_enabled_payment_gateways() { - global $edd_options; - $gateways = edd_get_payment_gateways(); - $enabled_gateways = isset( $edd_options['gateways'] ) ? $edd_options['gateways'] : ''; - $gateway_list = array(); - foreach($gateways as $key => $gateway) : - if(isset($enabled_gateways[$key]) && $enabled_gateways[$key] == 1) : - $gateway_list[$key] = $gateway; - endif; - endforeach; - return $gateway_list; -} - - -/** - * Is Gateway Active - * - * Checks whether a specified gateway is activated. - * - * @access public - * @since 1.0 - * @param string - The ID name of the gateway to check for - * @return boolean - true if enabled, false otherwise -*/ - -function edd_is_gateway_active($gateway) { - $gateways = edd_get_enabled_payment_gateways(); - if(array_key_exists($gateway, $gateways)) { - return true; - } - return false; -} - - -/** - * Get gateway admin label - * - * Returns the admin label for the specified gateway. - * - * @access public - * @since 1.0.8.3 - * @param string - The ID name of the gateway to retrieve a label for - * @return string -*/ - -function edd_get_gateway_admin_label($gateway) { - $gateways = edd_get_enabled_payment_gateways(); - return isset( $gateways[$gateway] ) ? $gateways[$gateway]['admin_label'] : $gateway; -} - - -/** - * Get gateway checkout label - * - * Returns the checkout label for the specified gateway. - * - * @access public - * @since 1.0.8.5 - * @param string - The ID name of the gateway to retrieve a label for - * @return string -*/ - -function edd_get_gateway_checkout_label($gateway) { - $gateways = edd_get_enabled_payment_gateways(); - return isset( $gateways[$gateway] ) ? $gateways[$gateway]['checkout_label'] : $gateway; -} - -/** - * Send to Gateway - * - * Sends the registration data to the specified gateway. - * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_send_to_gateway($gateway, $payment_data) { - // $gateway must match the ID used when registering the gateway - do_action('edd_gateway_' . $gateway, $payment_data); -} \ No newline at end of file diff --git a/includes/gateways/.stripe-hash b/includes/gateways/.stripe-hash new file mode 100644 index 00000000000..4d98cc91cc6 --- /dev/null +++ b/includes/gateways/.stripe-hash @@ -0,0 +1 @@ +feb67d63562231e67ed6184c353b2be4dbf3bc1d diff --git a/includes/gateways/actions.php b/includes/gateways/actions.php new file mode 100755 index 00000000000..20663cf191d --- /dev/null +++ b/includes/gateways/actions.php @@ -0,0 +1,80 @@ + 0 ) { + remove_action( 'edd_after_cc_fields', 'edd_default_cc_address_fields' ); + remove_action( 'edd_cc_form', 'edd_get_cc_form' ); + if ( current_user_can( 'manage_shop_settings' ) ) { + $error_message = __( 'You must enable a payment gateway to use Easy Digital Downloads', 'easy-digital-downloads' ); + } else { + $error_message = __( 'Your order cannot be completed at this time. Please try again or contact site support.', 'easy-digital-downloads' ); + } + edd_set_error( 'no_gateways', $error_message ); + } else { + edd_unset_error( 'no_gateways' ); + } +} +add_action( 'init', 'edd_no_gateway_error' ); diff --git a/includes/gateways/amazon-payments.php b/includes/gateways/amazon-payments.php new file mode 100644 index 00000000000..cd9270bf110 --- /dev/null +++ b/includes/gateways/amazon-payments.php @@ -0,0 +1,1174 @@ +reference_id = ! empty( $_REQUEST['amazon_reference_id'] ) + ? sanitize_text_field( $_REQUEST['amazon_reference_id'] ) + : ''; + + // Run this separate so we can ditch as early as possible + $this->register(); + + if ( ! edd_is_gateway_active( $this->gateway_id ) ) { + return; + } + + $this->config(); + $this->includes(); + $this->setup_client(); + $this->filters(); + $this->actions(); + } + + /** + * Retrieve current instance + * + * @access private + * @since 2.4 + * @return EDD_Amazon_Payments instance + */ + public static function getInstance() { + if ( ! isset( self::$instance ) && ! ( self::$instance instanceof EDD_Amazon_Payments ) ) { + self::$instance = new EDD_Amazon_Payments; + } + + return self::$instance; + } + + /** + * Register the payment gateway + * + * @access private + * @since 2.4 + * @return void + */ + private function register() { + add_filter( 'edd_payment_gateways', array( $this, 'register_gateway' ), 1, 1 ); + } + + /** + * Setup constant configuration for file paths + * + * @access private + * @since 2.4 + * @return void + */ + private function config() { + if ( ! defined( 'EDD_AMAZON_CLASS_DIR' ) ) { + $path = trailingslashit( plugin_dir_path( EDD_PLUGIN_FILE ) ) . 'includes/gateways/libs/amazon'; + define( 'EDD_AMAZON_CLASS_DIR', trailingslashit( $path ) ); + } + } + + /** + * Method to check if all the required settings have been filled out, allowing us to not output information without it. + * + * @since 2.7 + * @return bool + */ + public function is_setup() { + if ( null !== $this->is_setup ) { + return $this->is_setup; + } + + $required_items = array( 'merchant_id', 'client_id', 'access_key', 'secret_key' ); + + $current_values = array( + 'merchant_id' => edd_get_option( 'amazon_seller_id', '' ), + 'client_id' => edd_get_option( 'amazon_client_id', '' ), + 'access_key' => edd_get_option( 'amazon_mws_access_key', '' ), + 'secret_key' => edd_get_option( 'amazon_mws_secret_key', '' ), + ); + + $this->is_setup = true; + + foreach ( $required_items as $key ) { + if ( empty( $current_values[ $key ] ) ) { + $this->is_setup = false; + break; + } + } + + return $this->is_setup; + } + + /** + * Load additional files + * + * @access private + * @since 2.4 + * @return void + */ + private function includes() { + require_once EDD_AMAZON_CLASS_DIR . 'Client.php'; // Requires the other files itself + require_once EDD_AMAZON_CLASS_DIR . 'IpnHandler.php'; + } + + /** + * Add filters + * + * @since 2.4 + * @return void + */ + private function filters() { + + add_filter( 'edd_accepted_payment_icons', array( $this, 'register_payment_icon' ), 10, 1 ); + add_filter( 'edd_show_gateways', array( $this, 'maybe_hide_gateway_select' ) ); + + // Since the Amazon Gateway loads scripts on page, it needs the scripts to load in the header. + add_filter( 'edd_load_scripts_in_footer', '__return_false' ); + + if ( is_admin() ) { + add_filter( 'edd_settings_sections_gateways', array( $this, 'register_gateway_section' ), 1, 1 ); + add_filter( 'edd_settings_gateways', array( $this, 'register_gateway_settings' ), 1, 1 ); + add_filter( 'edd_payment_details_transaction_id-' . $this->gateway_id, array( $this, 'link_transaction_id' ), 10, 2 ); + } + } + + /** + * Add actions + * + * @access private + * @since 2.4 + * @return void + */ + private function actions() { + add_action( 'wp_enqueue_scripts', array( $this, 'print_client' ), 10 ); + add_action( 'wp_enqueue_scripts', array( $this, 'load_scripts' ), 11 ); + add_action( 'edd_pre_process_purchase', array( $this, 'check_config' ), 1 ); + add_action( 'init', array( $this, 'capture_oauth' ), 9 ); + add_action( 'init', array( $this, 'signin_redirect' ) ); + add_action( 'edd_purchase_form_before_register_login', array( $this, 'login_form' ) ); + add_action( 'edd_checkout_error_check', array( $this, 'checkout_errors' ), 10, 2 ); + add_action( 'edd_gateway_amazon', array( $this, 'process_purchase' ) ); + add_action( 'wp_ajax_edd_amazon_get_address', array( $this, 'ajax_get_address' ) ); + add_action( 'wp_ajax_nopriv_edd_amazon_get_address', array( $this, 'ajax_get_address' ) ); + add_action( 'edd_pre_process_purchase', array( $this, 'disable_address_requirement' ), 99999 ); + add_action( 'init', array( $this, 'process_ipn' ) ); + + if ( empty( $this->reference_id ) ) { + return; + } + + add_action( 'edd_amazon_cc_form', array( $this, 'wallet_form' ) ); + } + + /** + * Show an error message on checkout if Amazon is enabled but not setup. + * + * @since 2.7 + */ + public function check_config() { + $is_enabled = edd_is_gateway_active( $this->gateway_id ); + if ( ( ! $is_enabled || false === $this->is_setup() ) && 'amazon' == edd_get_chosen_gateway() ) { + edd_set_error( 'amazon_gateway_not_configured', __( 'There is an error with the Amazon Payments configuration.', 'easy-digital-downloads' ) ); + } + } + + /** + * Retrieve the client object + * + * @access private + * @since 2.4 + * @return PayWithAmazon\Client + */ + private function get_client() { + + if ( ! $this->is_setup() ) { + return false; + } + + if ( ! is_null( $this->client ) ) { + return $this->client; + } + + $this->setup_client(); + + return $this->client; + } + + /** + * Setup the client object + * + * @access private + * @since 2.4 + * @return void + */ + private function setup_client() { + + if ( ! $this->is_setup() ) { + return; + } + + $region = edd_get_shop_country(); + + if ( 'GB' === $region ) { + $region = 'UK'; + } + + $config = array( + 'merchant_id' => edd_get_option( 'amazon_seller_id', '' ), + 'client_id' => edd_get_option( 'amazon_client_id', '' ), + 'access_key' => edd_get_option( 'amazon_mws_access_key', '' ), + 'secret_key' => edd_get_option( 'amazon_mws_secret_key', '' ), + 'region' => $region, + 'sandbox' => edd_is_test_mode(), + ); + + $config = apply_filters( 'edd_amazon_client_config', $config ); + + $this->client = new Client( $config ); + } + + /** + * Register the gateway + * + * @since 2.4 + * @param $gateways array + * @return array + */ + public function register_gateway( $gateways ) { + + $default_amazon_info = array( + $this->gateway_id => array( + 'admin_label' => __( 'Amazon', 'easy-digital-downloads' ), + 'checkout_label' => __( 'Amazon', 'easy-digital-downloads' ), + 'supports' => array(), + 'icons' => array( 'amazon' ), + ), + ); + + $default_amazon_info = apply_filters( 'edd_register_amazon_gateway', $default_amazon_info ); + $gateways = array_merge( $gateways, $default_amazon_info ); + + return $gateways; + } + + /** + * Register the payment icon + * + * @since 2.4 + * @param array $payment_icons Array of payment icons + * @return array The array of icons with Amazon Added + */ + public function register_payment_icon( $payment_icons ) { + $payment_icons['amazon'] = 'Amazon'; + + return $payment_icons; + } + + /** + * Hides payment gateway select options after return from Amazon + * + * @since 2.7.6 + * @param bool $show Should gateway select be shown + * @return bool + */ + public function maybe_hide_gateway_select( $show ) { + + if ( ! empty( $_REQUEST['payment-mode'] ) && 'amazon' == $_REQUEST['payment-mode'] && ! empty( $_REQUEST['amazon_reference_id'] ) && ! empty( $_REQUEST['state'] ) && 'authorized' == $_REQUEST['state'] ) { + $show = false; + } + + return $show; + } + + /** + * Register the payment gateways setting section + * + * @since 2.5 + * @param array $gateway_sections Array of sections for the gateways tab + * @return array Added Amazon Payments into sub-sections + */ + public function register_gateway_section( $gateway_sections ) { + $gateway_sections['amazon'] = __( 'Amazon Payments', 'easy-digital-downloads' ); + + return $gateway_sections; + } + + /** + * Register the gateway settings + * + * @since 2.4 + * @param $gateway_settings array + * @return array + */ + public function register_gateway_settings( $gateway_settings ) { + $default_amazon_settings = array( + 'amazon_register' => array( + 'id' => 'amazon_register', + 'name' => __( 'Register with Amazon', 'easy-digital-downloads' ), + 'desc' => '

    ' . + __( 'Connect Easy Digital Downloads to Amazon', 'easy-digital-downloads' ) . + '

    ' . + '

    ' . + __( 'Once registration is complete, enter your API credentials below.', 'easy-digital-downloads' ) . + '

    ', + 'type' => 'descriptive_text', + ), + 'amazon_seller_id' => array( + 'id' => 'amazon_seller_id', + 'name' => __( 'Seller ID', 'easy-digital-downloads' ), + 'desc' => __( 'Found in the Integration settings. Also called a Merchant ID', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + ), + 'amazon_mws_access_key' => array( + 'id' => 'amazon_mws_access_key', + 'name' => __( 'MWS Access Key', 'easy-digital-downloads' ), + 'desc' => __( 'Found on Seller Central in the MWS Keys section', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + ), + 'amazon_mws_secret_key' => array( + 'id' => 'amazon_mws_secret_key', + 'name' => __( 'MWS Secret Key', 'easy-digital-downloads' ), + 'desc' => __( 'Found on Seller Central in the MWS Keys section', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + ), + 'amazon_client_id' => array( + 'id' => 'amazon_client_id', + 'name' => __( 'Client ID', 'easy-digital-downloads' ), + 'desc' => __( 'The Amazon Client ID. Should look like `amzn1.application-oa2...`', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + ), + 'amazon_mws_callback_url' => array( + 'id' => 'amazon_callback_url', + 'name' => __( 'Amazon MWS Callback URL', 'easy-digital-downloads' ), + 'desc' => __( 'The Return URL to provide in your MWS Application. Enter this under your Login and Pay → Web Settings', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'large', + 'std' => $this->get_amazon_authenticate_redirect(), + 'faux' => true, + ), + 'amazon_mws_ipn_url' => array( + 'id' => 'amazon_ipn_url', + 'name' => __( 'Amazon Merchant IPN URL', 'easy-digital-downloads' ), + 'desc' => sprintf( __( 'The IPN URL to provide in your MWS account. Enter this under your Integration Settings', 'easy-digital-downloads' ), 'https://sellercentral.amazon.com/gp/pyop/seller/account/settings/user-settings-edit.html' ), + 'type' => 'text', + 'size' => 'large', + 'std' => $this->get_amazon_ipn_url(), + 'faux' => true, + ), + ); + + $default_amazon_settings = apply_filters( 'edd_default_amazon_settings', $default_amazon_settings ); + $gateway_settings['amazon'] = $default_amazon_settings; + + return $gateway_settings; + } + + /** + * Load javascript files and localized variables + * + * @since 2.4 + * @return void + */ + public function load_scripts() { + + if ( ! $this->is_setup() ) { + return; + } + + if ( ! edd_is_checkout() ) { + return; + } + + $test_mode = edd_is_test_mode(); + $seller_id = edd_get_option( 'amazon_seller_id', '' ); + $client_id = edd_get_option( 'amazon_client_id', '' ); + + $default_amazon_scope = array( + 'profile', + 'postal_code', + 'payments:widget', + ); + + if ( edd_use_taxes() ) { + $default_amazon_scope[] = 'payments:shipping_address'; + } + + $default_amazon_button_settings = array( + 'type' => 'PwA', + 'color' => 'Gold', + 'size' => 'medium', + 'scope' => implode( ' ', $default_amazon_scope ), + 'popup' => true, + ); + + $amazon_button_settings = apply_filters( 'edd_amazon_button_settings', $default_amazon_button_settings ); + $base_url = ''; + $sandbox = $test_mode ? 'sandbox/' : ''; + + switch ( edd_get_shop_country() ) { + case 'GB': + $base_url = 'https://static-eu.payments-amazon.com/OffAmazonPayments/uk/' . $sandbox . 'lpa/'; + break; + case 'DE': + $base_url = 'https://static-eu.payments-amazon.com/OffAmazonPayments/de/' . $sandbox. 'lpa/'; + break; + default: + $base_url = 'https://static-na.payments-amazon.com/OffAmazonPayments/us/' . $sandbox; + break; + } + + if ( ! empty( $base_url ) ) { + $url = $base_url . 'js/Widgets.js?sellerId=' . $seller_id; + + wp_enqueue_script( 'edd-amazon-widgets', $url, array( 'jquery' ), null, false ); + wp_localize_script( 'edd-amazon-widgets', 'edd_amazon', apply_filters( 'edd_amazon_checkout_vars', array( + 'sellerId' => $seller_id, + 'clientId' => $client_id, + 'referenceID' => $this->reference_id, + 'buttonType' => $amazon_button_settings['type'], + 'buttonColor' => $amazon_button_settings['color'], + 'buttonSize' => $amazon_button_settings['size'], + 'scope' => $amazon_button_settings['scope'], + 'popup' => $amazon_button_settings['popup'], + 'checkoutUri' => $this->get_amazon_checkout_uri(), + 'redirectUri' => $this->get_amazon_authenticate_redirect(), + 'signinUri' => $this->get_amazon_signin_redirect(), + ) ) ); + } + } + + /** + * Print client ID in header + * + * @since 2.4 + * @return void + */ + public function print_client() { + + if ( ! $this->is_setup() ) { + return false; + } + + if ( ! edd_is_checkout() ) { + return; + } + ?> + + client->getUserInfo( $_GET['access_token'] ); + + EDD()->session->set( 'amazon_access_token', $_GET['access_token'] ); + EDD()->session->set( 'amazon_profile', $profile ); + } catch( Exception $e ) { + wp_die( print_r( $e, true ) ); + } + } + + /** + * Set customer details after authentication + * + * @since 2.4 + * @return void + */ + public function signin_redirect() { + + if ( ! isset( $_GET['edd-listener'] ) || $_GET['edd-listener'] !== 'amazon' ) { + return; + } + + if ( ! isset( $_GET['state'] ) || $_GET['state'] !== 'signed-in' ) { + return; + } + + $profile = EDD()->session->get( 'amazon_profile' ); + $reference = $_GET['amazon_reference_id']; + + if ( ! is_user_logged_in() ) { + $user = get_user_by( 'email', $profile['email'] ); + + if ( $user ) { + edd_log_user_in( $user->ID, $user->user_login, '' ); + + $customer = array( + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'email' => $user->user_email + ); + + } else { + $names = explode( ' ', $profile['name'], 2 ); + + $customer = array( + 'first_name' => $names[0], + 'last_name' => isset( $names[1] ) ? $names[1] : '', + 'email' => $profile['email'] + ); + + // Create a customer account if registration is not disabled + if ( 'none' !== edd_get_option( 'show_register_form' ) ) { + $args = array( + 'user_email' => $profile['email'], + 'user_login' => $profile['email'], + 'display_name' => $profile['name'], + 'first_name' => $customer['first_name'], + 'last_name' => $customer['last_name'], + 'user_pass' => wp_generate_password( 20 ), + ); + + $user_id = wp_insert_user( $args ); + + edd_log_user_in( $user_id, $args['user_login'], $args['user_pass'] ); + } + } + + EDD()->session->set( 'customer', $customer ); + } + + edd_redirect( edd_get_checkout_uri( array( 'payment-mode' => 'amazon', 'state' => 'authorized', 'amazon_reference_id' => urlencode( $reference ) ) ) ); + } + + /** + * Display the log in button + * + * @since 2.4 + * @return void + */ + public function login_form() { + + if ( ! $this->is_setup() ) { + return false; + } + + if ( empty( $this->reference_id ) && 'amazon' == edd_get_chosen_gateway() ) : + + remove_all_actions( 'edd_purchase_form_after_cc_form' ); + remove_all_actions( 'edd_purchase_form_after_user_info' ); + remove_all_actions( 'edd_purchase_form_register_fields' ); + remove_all_actions( 'edd_purchase_form_login_fields' ); + remove_all_actions( 'edd_register_fields_before' ); + remove_all_actions( 'edd_cc_form' ); + remove_all_actions( 'edd_checkout_form_top' ); + + ob_start(); ?> +
    + +
    + + +
    + + is_setup() ) { + return false; + } + + $profile = EDD()->session->get( 'amazon_profile' ); + remove_action( 'edd_purchase_form_after_cc_form', 'edd_checkout_tax_fields', 999 ); + ob_start(); ?> + +
    +

    + : + () +

    + +
    + +
    + + +
    + + + + + + + +
    +
    + + is_setup() ) { + return false; + } + + if ( empty( $_POST['reference_id'] ) ) { + die( '-2' ); + } + + $request = $this->client->getOrderReferenceDetails( array( + 'merchant_id' => edd_get_option( 'amazon_seller_id', '' ), + 'amazon_order_reference_id' => $_POST['reference_id'], + 'address_consent_token' => EDD()->session->get( 'amazon_access_token' ) + ) ); + + $address = array(); + $data = new ResponseParser( $request->response ); + $data = $data->toArray(); + + if ( isset( $data['GetOrderReferenceDetailsResult']['OrderReferenceDetails']['Destination']['PhysicalDestination'] ) ) { + + $address = $data['GetOrderReferenceDetailsResult']['OrderReferenceDetails']['Destination']['PhysicalDestination']; + $address = wp_parse_args( $address, array( 'City', 'CountryCode', 'StateOrRegion', 'PostalCode', 'AddressLine1', 'AddressLine2' ) ); + + } + + echo json_encode( $address ); exit; + } + + /** + * Check for errors during checkout + * + * @since 2.4 + * @param $valid_data Customer / product data from checkout + * @param $post_data $_POST + * @return void + */ + public function checkout_errors( $valid_data, $post_data ) { + + // should validate that we have a reference ID here, perhaps even fire the API call here + if ( empty( $post_data['edd_amazon_reference_id'] ) ) { + edd_set_error( 'missing_reference_id', __( 'Missing Reference ID, please try again.', 'easy-digital-downloads' ) ); + } + } + + /** + * Process the purchase and create the charge in Amazon + * + * @since 2.4 + * @param $purchase_data array Cart details + * @return void + */ + public function process_purchase( $purchase_data ) { + + if ( empty( $purchase_data['post_data']['edd_amazon_reference_id'] ) ) { + edd_set_error( 'missing_reference_id', __( 'Missing Reference ID, please try again.', 'easy-digital-downloads' ) ); + } + + $errors = edd_get_errors(); + if ( ! empty( $errors ) ) { + edd_send_back_to_checkout( '?payment-mode=amazon' ); + } + + $args = apply_filters( 'edd_amazon_charge_args', array( + 'merchant_id' => edd_get_option( 'amazon_seller_id', '' ), + 'amazon_reference_id' => $purchase_data['post_data']['edd_amazon_reference_id'], + 'authorization_reference_id' => $purchase_data['purchase_key'], + 'charge_amount' => $purchase_data['price'], + 'currency_code' => edd_get_currency(), + 'charge_note' => html_entity_decode( edd_get_purchase_summary( $purchase_data, false ) ), + 'charge_order_id' => $purchase_data['purchase_key'], + 'store_name' => remove_accents( wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ), + 'transaction_timeout' => 0 + ), $purchase_data ); + + $args['platform_id'] = 'A3JST9YM1SX7LB'; + + $charge = $this->client->charge( $args ); + + if ( 200 == $charge->response['Status'] ) { + $charge = new ResponseParser( $charge->response ); + $charge = $charge->toArray(); + + $status = $charge['AuthorizeResult']['AuthorizationDetails']['AuthorizationStatus']['State']; + + if ( 'Declined' === $status ) { + + $reason = $charge['AuthorizeResult']['AuthorizationDetails']['AuthorizationStatus']['ReasonCode']; + edd_set_error( 'payment_declined', sprintf( __( 'Your payment could not be authorized, please try a different payment method. Reason: %s', 'easy-digital-downloads' ), $reason ) ); + edd_send_back_to_checkout( '?payment-mode=amazon&amazon_reference_id=' . $purchase_data['post_data']['edd_amazon_reference_id'] ); + } + + // Setup payment data to be recorded + $payment_data = array( + 'price' => $purchase_data['price'], + 'date' => $purchase_data['date'], + 'user_email' => $purchase_data['user_email'], + 'purchase_key' => $purchase_data['purchase_key'], + 'currency' => edd_get_currency(), + 'downloads' => $purchase_data['downloads'], + 'user_info' => $purchase_data['user_info'], + 'cart_details' => $purchase_data['cart_details'], + 'gateway' => $this->gateway_id, + 'status' => 'pending', + ); + + $payment_id = edd_insert_payment( $payment_data ); + + $authorization_id = $charge['AuthorizeResult']['AuthorizationDetails']['AmazonAuthorizationId']; + $capture_id = str_replace( '-A', '-C', $authorization_id ); + $reference_id = sanitize_text_field( $_POST['edd_amazon_reference_id'] ); + + // Confirm the capture was completed + $capture = $this->client->getCaptureDetails( array( + 'merchant_id' => edd_get_option( 'amazon_seller_id', '' ), + 'amazon_capture_id' => $capture_id + ) ); + + $capture = new ResponseParser( $capture->response ); + $capture = $capture->toArray(); + + edd_update_payment_meta( $payment_id, '_edd_amazon_authorization_id', $authorization_id ); + edd_update_payment_meta( $payment_id, '_edd_amazon_capture_id', $capture_id ); + + edd_set_payment_transaction_id( $payment_id, $reference_id, $purchase_data['price'] ); + + edd_update_payment_status( $payment_id, 'complete' ); + + // Empty the shopping cart + edd_empty_cart(); + edd_send_to_success_page(); + + // Set an error + } else { + edd_set_error( 'amazon_error',sprintf( __( 'There was an issue processing your payment. Amazon error: %s', 'easy-digital-downloads' ), print_r( $charge, true ) ) ); + edd_send_back_to_checkout( '?payment-mode=amazon&amazon_reference_id=' . $purchase_data['post_data']['edd_amazon_reference_id'] ); + } + } + + /** + * Retrieve the checkout URL for Amazon after authentication is complete + * + * @since 2.4 + * @return string + */ + private function get_amazon_checkout_uri() { + if ( is_null( $this->checkout_uri ) ) { + $this->checkout_uri = esc_url_raw( add_query_arg( array( 'payment-mode' => 'amazon' ), edd_get_checkout_uri() ) ); + } + + return $this->checkout_uri; + } + + /** + * Retrieve the return URL for Amazon after authentication on Amazon is complete + * + * @since 2.4 + * @return string + */ + private function get_amazon_authenticate_redirect() { + if ( is_null( $this->redirect_uri ) ) { + $this->redirect_uri = esc_url_raw( add_query_arg( array( 'edd-listener' => 'amazon', 'state' => 'return_auth' ), edd_get_checkout_uri() ) ); + } + + return $this->redirect_uri; + } + + /** + * Retrieve the URL to send customers too once sign-in is complete + * + * @since 2.4 + * @return string + */ + private function get_amazon_signin_redirect() { + if ( is_null( $this->signin_redirect ) ) { + $this->signin_redirect = esc_url_raw( add_query_arg( array( 'edd-listener' => 'amazon', 'state' => 'signed-in' ), home_url() ) ); + } + + return $this->signin_redirect; + } + + /** + * Retrieve the IPN URL for Amazon + * + * @since 2.4 + * @return string + */ + private function get_amazon_ipn_url() { + return esc_url_raw( add_query_arg( array( 'edd-listener' => 'amazon' ), home_url( 'index.php' ) ) ); + } + + /** + * Removes the requirement for entering the billing address + * + * Address is pulled directly from Amazon + * + * @since 2.4 + * @return void + */ + public function disable_address_requirement() { + if ( ! empty( $_POST['edd-gateway'] ) && $this->gateway_id == $_REQUEST['edd-gateway'] ) { + add_filter( 'edd_require_billing_address', '__return_false', 9999 ); + } + } + + /** + * Given a transaction ID, generate a link to the Amazon transaction ID details + * + * @since 2.4 + * @param string $transaction_id The Transaction ID + * @param int $payment_id The payment ID for this transaction + * @return string A link to the PayPal transaction details + */ + public function link_transaction_id( $transaction_id, $payment_id ) { + $base_url = 'https://sellercentral.amazon.com/hz/me/pmd/payment-details?orderReferenceId='; + $transaction_url = '' . esc_html( $transaction_id ) . ''; + + return apply_filters( 'edd_' . $this->gateway_id . '_link_payment_details_transaction_id', $transaction_url ); + } + + /** + * Process IPN messages from Amazon + * + * @since 2.4 + * @return void + */ + public function process_ipn() { + + if ( ! isset( $_GET['edd-listener'] ) || $_GET['edd-listener'] !== 'amazon' ) { + return; + } + + if ( isset( $_GET['state'] ) ) { + return; + } + + // Get the IPN headers and Message body + $headers = getallheaders(); + $body = file_get_contents( 'php://input' ); + + $this->doing_ipn = true; + + try { + $ipn = new IpnHandler( $headers, $body ); + $data = $ipn->toArray(); + $seller_id = $data['SellerId']; + + if ( $seller_id != edd_get_option( 'amazon_seller_id', '' ) ) { + wp_die( __( 'Invalid Amazon seller ID', 'easy-digital-downloads' ), __( 'IPN Error', 'easy-digital-downloads' ), array( 'response' => 401 ) ); + } + + switch( $data['NotificationType'] ) { + case 'OrderReferenceNotification' : + break; + + case 'PaymentAuthorize' : + break; + + case 'PaymentCapture' : + $key = $data['CaptureDetails']['CaptureReferenceId']; + $status = $data['CaptureDetails']['CaptureStatus']['State']; + + if ( 'Declined' === $status ) { + $payment_id = edd_get_purchase_id_by_key( $key ); + + edd_update_payment_status( $payment_id, 'failed' ); + + edd_insert_payment_note( $payment_id, __( 'Capture declined in Amazon', 'easy-digital-downloads' ) ); + } + + break; + + case 'PaymentRefund' : + $trans_id = substr( $data['RefundDetails']['AmazonRefundId'], 0, 19 ); + $status = $data['RefundDetails']['RefundStatus']['State']; + + if ( 'Completed' === $status ) { + $payment_id = edd_get_purchase_id_by_transaction_id( $trans_id ); + + edd_update_payment_status( $payment_id, 'refunded' ); + + edd_insert_payment_note( $payment_id, sprintf( __( 'Refund completed in Amazon. Refund ID: %s', 'easy-digital-downloads' ), $data['RefundDetails']['AmazonRefundId'] ) ); + } + + break; + } + + } catch( Exception $e ) { + wp_die( $e->getErrorMessage(), __( 'IPN Error', 'easy-digital-downloads' ), array( 'response' => 401 ) ); + } + } + + /** + * Detect a refund action from EDD + * + * @deprecated 3.0 Due to issues with Amazon, refunds must be processed at the gateway. + * @since 2.4 + * @param $payment_id int The ID number of the payment being refunded + * @param $new_status string The new status assigned to the payment + * @param $old_status string The previous status of the payment + * @return void + */ + public function process_refund( $payment_id, $new_status, $old_status ) { + _edd_deprecated_function( __METHOD__, '3.0' ); + + if ( 'complete' !== $old_status && 'revoked' !== $old_status ) { + return; + } + + if ( 'refunded' !== $new_status ) { + return; + } + + if ( ! empty( $this->doing_ipn ) ) { + return; + } + + if ( 'amazon' !== edd_get_payment_gateway( $payment_id ) ) { + return; + } + + $this->refund( $payment_id ); + + } + + /** + * Refund a charge in Amazon + * + * @since 2.4 + * @param $payment_id int The ID number of the payment being refunded + * @return string + */ + private function refund( $payment_id = 0 ) { + + $refund = $this->client->refund( array( + 'merchant_id' => edd_get_option( 'amazon_seller_id', '' ), + 'amazon_capture_id' => edd_get_payment_meta( $payment_id, '_edd_amazon_capture_id', true ), + 'refund_reference_id' => md5( edd_get_payment_key( $payment_id ) . '-refund' ), + 'refund_amount' => edd_get_payment_amount( $payment_id ), + 'currency_code' => edd_get_payment_currency_code( $payment_id ), + ) ); + + if ( 200 == $refund->response['Status'] ) { + $refund = new ResponseParser( $refund->response ); + $refund = $refund->toArray(); + + $reference_id = $refund['RefundResult']['RefundDetails']['RefundReferenceId']; + $status = $refund['RefundResult']['RefundDetails']['RefundStatus']['State']; + + switch( $status ) { + case 'Declined' : + $note = __( 'Refund declined in Amazon. Refund ID: %s', 'easy-digital-downloads' ); + break; + + case 'Completed' : + $refund_id = $refund['RefundResult']['RefundDetails']['AmazonRefundId']; + $note = sprintf( __( 'Refund completed in Amazon. Refund ID: %s', 'easy-digital-downloads' ), $refund_id ); + break; + + case 'Pending' : + $note = sprintf( __( 'Refund initiated in Amazon. Reference ID: %s', 'easy-digital-downloads' ), $reference_id ); + break; + } + edd_insert_payment_note( $payment_id, $note ); + + } else { + edd_insert_payment_note( $payment_id, __( 'Refund request failed in Amazon.', 'easy-digital-downloads' ) ); + } + } + + /** + * Retrieve the URL for connecting Amazon account to EDD + * + * @since 2.4 + * @since 2.9.8 - Updated registration URL per Amazon Reps + * @return string + */ + private function get_registration_url() { + + switch ( edd_get_shop_country() ) { + case 'GB': + $base_url = 'https://payments.amazon.co.uk/preregistration/lpa'; + break; + case 'DE': + $base_url = 'https://payments.amazon.de/preregistration/lpa'; + break; + default: + $base_url = 'https://sellercentral.amazon.com/hz/me/sp/signup'; + break; + } + + $query_args = array( + 'registration_source' => 'SPPD', + 'spId' => 'A3JST9YM1SX7LB', + ); + + return add_query_arg( $query_args, $base_url ); + } +} diff --git a/includes/gateways/functions.php b/includes/gateways/functions.php new file mode 100755 index 00000000000..b48649c8926 --- /dev/null +++ b/includes/gateways/functions.php @@ -0,0 +1,619 @@ + array( + 'admin_label' => __( 'Live', 'easy-digital-downloads' ) + ), + 'test' => array( + 'admin_label' => __( 'Test', 'easy-digital-downloads' ) + ) + ); + } + + return (array) apply_filters( 'edd_payment_modes', $modes ); +} + +/** + * Returns a list of all available gateways. + * + * @since 1.0 + * + * @return array $gateways All the available gateways. + */ +function edd_get_payment_gateways() { + static $gateways = null; + + // Default, built-in gateways + if ( is_null( $gateways ) ) { + $gateways = array( + 'paypal_commerce' => array( + 'admin_label' => __( 'PayPal', 'easy-digital-downloads' ), + 'checkout_label' => __( 'PayPal', 'easy-digital-downloads' ), + 'supports' => array( + 'buy_now', + ), + 'icons' => array( + 'paypal', + ), + ), + /** + * PayPal Standard is available only if it was used prior to 2.11 and the store owner hasn't + * yet been onboarded to PayPal Commerce. + * + * @see \EDD\Gateways\PayPal\maybe_remove_paypal_standard() + */ + 'paypal' => array( + 'admin_label' => __( 'PayPal Standard', 'easy-digital-downloads' ), + 'checkout_label' => __( 'PayPal', 'easy-digital-downloads' ), + 'supports' => array( + 'buy_now', + ), + 'icons' => array( + 'paypal', + ), + ), + 'manual' => array( + 'admin_label' => __( 'Store Gateway', 'easy-digital-downloads' ), + 'checkout_label' => __( 'Store Gateway', 'easy-digital-downloads' ), + ), + ); + } + + $gateways = apply_filters( 'edd_payment_gateways', $gateways ); + + // Since Stripe is added via a filter still, move to the top. + if ( array_key_exists( 'stripe', $gateways ) ) { + $stripe_attributes = $gateways['stripe']; + unset( $gateways['stripe'] ); + + $gateways = array_merge( array( 'stripe' => $stripe_attributes ), $gateways ); + } + + return (array) apply_filters( 'edd_payment_gateways', $gateways ); +} + +/** + * Enforce the gateway order (from the sortable admin area UI). + * + * @since 3.0 + * + * @param array $gateways + * @return array + */ +function edd_order_gateways( $gateways = array() ) { + + // Get the order option + $order = edd_get_option( 'gateways_order', '' ); + + // If order is set, enforce it + if ( ! empty( $order ) ) { + $order = array_flip( explode( ',', $order ) ); + $order = array_intersect_key( $order, $gateways ); + $gateways = array_merge( $order, $gateways ); + } + + // Return ordered gateways + return $gateways; +} +add_filter( 'edd_payment_gateways', 'edd_order_gateways', 99 ); +add_filter( 'edd_enabled_payment_gateways_before_sort', 'edd_order_gateways', 99 ); + +/** + * Returns a list of all enabled gateways. + * + * @since 1.0 + * @param bool $sort If true, the default gateway will be first + * @return array $gateway_list All the available gateways +*/ +function edd_get_enabled_payment_gateways( $sort = false ) { + $gateways = edd_get_payment_gateways(); + $enabled = (array) edd_get_option( 'gateways', false ); + + $gateway_list = array(); + + foreach ( $gateways as $key => $gateway ) { + if ( isset( $enabled[ $key ] ) && 1 === (int) $enabled[ $key ] ) { + $gateway_list[ $key ] = $gateway; + } + } + + /** + * Filter the enabled payment gateways before the default is bumped to the + * front of the array. + * + * @since 3.0 + * + * @param array $gateway_list List of enabled payment gateways + * @return array Array of sorted gateways + */ + $gateway_list = apply_filters( 'edd_enabled_payment_gateways_before_sort', $gateway_list ); + + // Reorder our gateways so the default is first + if ( true === $sort ) { + $default_gateway_id = edd_get_default_gateway(); + + // Only put default on top if it's active + if ( edd_is_gateway_active( $default_gateway_id ) ) { + $default_gateway = array( $default_gateway_id => $gateway_list[ $default_gateway_id ] ); + unset( $gateway_list[ $default_gateway_id ] ); + + $gateway_list = array_merge( $default_gateway, $gateway_list ); + } + } + + return apply_filters( 'edd_enabled_payment_gateways', $gateway_list ); +} + +/** + * Checks whether a specified gateway is activated. + * + * @since 1.0 + * + * @param string $gateway Name of the gateway to check for. + * @return boolean true if enabled, false otherwise. +*/ +function edd_is_gateway_active( $gateway ) { + $gateways = edd_get_enabled_payment_gateways(); + $retval = array_key_exists( $gateway, $gateways ); + + return apply_filters( 'edd_is_gateway_active', $retval, $gateway, $gateways ); +} + +/** + * Gets the default payment gateway selected from the EDD Settings. + * + * @since 1.5 + * + * @return string $default Default gateway ID. + */ +function edd_get_default_gateway() { + $default = edd_get_option( 'default_gateway', 'paypal' ); + + // Use the first enabled one + if ( ! edd_is_gateway_active( $default ) ) { + $gateways = edd_get_enabled_payment_gateways(); + $gateways = array_keys( $gateways ); + $default = reset( $gateways ); + } + + return apply_filters( 'edd_default_gateway', $default ); +} + +/** + * Returns the admin label for the specified gateway + * + * @since 1.0.8.3 + * + * @param string $gateway Name of the gateway to retrieve a label for + * @return string Gateway admin label + */ +function edd_get_gateway_admin_label( $gateway ) { + $gateways = edd_get_payment_gateways(); + + $label = isset( $gateways[ $gateway ] ) + ? $gateways[ $gateway ]['admin_label'] + : ucwords( $gateway ); + + return apply_filters( 'edd_gateway_admin_label', $label, $gateway ); +} + +/** + * Returns the checkout label for the specified gateway. + * + * @since 1.0.8.5 + * + * @param string $gateway Name of the gateway to retrieve a label for. + * @return string Checkout label for the gateway. + */ +function edd_get_gateway_checkout_label( $gateway ) { + $gateways = edd_get_payment_gateways(); + $label = isset( $gateways[ $gateway ] ) ? $gateways[ $gateway ]['checkout_label'] : $gateway; + + return apply_filters( 'edd_gateway_checkout_label', $label, $gateway ); +} + +/** + * Returns the options a gateway supports. + * + * @since 1.8 + * + * @param string $gateway ID of the gateway to retrieve a label for. + * @return array Options the gateway supports. + */ +function edd_get_gateway_supports( $gateway ) { + $gateways = edd_get_enabled_payment_gateways(); + $supports = isset( $gateways[ $gateway ]['supports'] ) ? $gateways[ $gateway ]['supports'] : array(); + + return apply_filters( 'edd_gateway_supports', $supports, $gateway ); +} + +/** + * Checks if a gateway supports buy now. + * + * @since 1.8 + * + * @param string $gateway ID of the gateway to retrieve a label for. + * @return bool True if the gateway supports buy now, false otherwise. + */ +function edd_gateway_supports_buy_now( $gateway ) { + $supports = edd_get_gateway_supports( $gateway ); + $ret = in_array( 'buy_now', $supports, true ); + + return apply_filters( 'edd_gateway_supports_buy_now', $ret, $gateway ); +} + +/** + * Checks if an enabled gateway supports buy now. + * + * @since 1.8 + * + * @return bool True if the shop supports buy now, false otherwise. + */ +function edd_shop_supports_buy_now() { + $gateways = edd_get_enabled_payment_gateways(); + $ret = false; + + if ( ! edd_use_taxes() && $gateways && 1 === count( $gateways ) ) { + foreach ( $gateways as $gateway_id => $gateway ) { + if ( edd_gateway_supports_buy_now( $gateway_id ) ) { + $ret = true; + break; + } + } + } + + return apply_filters( 'edd_shop_supports_buy_now', $ret ); +} + +/** + * Build the purchase data for a straight-to-gateway purchase button + * + * @since 1.7 + * + * @param int $download_id + * @param array $options + * @param int $quantity + * + * @return mixed|void + */ +function edd_build_straight_to_gateway_data( $download_id = 0, $options = array(), $quantity = 1 ) { + $price_options = array(); + + if ( empty( $options ) || ! edd_has_variable_prices( $download_id ) ) { + $price = edd_get_download_price( $download_id ); + } else { + + if ( is_array( $options['price_id'] ) ) { + $price_id = $options['price_id'][0]; + } else { + $price_id = $options['price_id']; + } + + $prices = edd_get_variable_prices( $download_id ); + + // Make sure a valid price ID was supplied + if ( ! isset( $prices[ $price_id ] ) ) { + wp_die( __( 'The requested price ID does not exist.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 404 ) ); + } + + $price_options = array( + 'price_id' => $price_id, + 'amount' => $prices[ $price_id ]['amount'] + ); + $price = $prices[ $price_id ]['amount']; + } + + // Set up Downloads array + $downloads = array( + array( + 'id' => $download_id, + 'options' => $price_options + ) + ); + + // Set up Cart Details array + $cart_details = array( + array( + 'name' => get_the_title( $download_id ), + 'id' => $download_id, + 'item_number' => array( + 'id' => $download_id, + 'options' => $price_options + ), + 'tax' => 0, + 'discount' => 0, + 'item_price' => $price, + 'subtotal' => ( $price * $quantity ), + 'price' => ( $price * $quantity ), + 'quantity' => $quantity, + ) + ); + + if ( is_user_logged_in() ) { + $current_user = wp_get_current_user(); + } + + // Setup user information + $user_info = array( + 'id' => is_user_logged_in() ? get_current_user_id() : -1, + 'email' => is_user_logged_in() ? $current_user->user_email : '', + 'first_name' => is_user_logged_in() ? $current_user->user_firstname : '', + 'last_name' => is_user_logged_in() ? $current_user->user_lastname : '', + 'discount' => 'none', + 'address' => array() + ); + + // Setup purchase information + $purchase_data = array( + 'downloads' => $downloads, + 'fees' => edd_get_cart_fees(), + 'subtotal' => $price * $quantity, + 'discount' => 0, + 'tax' => 0, + 'price' => $price * $quantity, + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ), + 'user_info' => $user_info, + 'post_data' => array(), + 'cart_details' => $cart_details, + 'gateway' => \EDD\Gateways\PayPal\paypal_standard_enabled() ? 'paypal' : 'paypal_commerce', + 'buy_now' => true, + 'card_info' => array() + ); + + return apply_filters( 'edd_straight_to_gateway_purchase_data', $purchase_data ); +} + +/** + * Sends all the payment data to the specified gateway + * + * @since 1.0 + * + * @param string $gateway Name of the gateway. + * @param array $payment_data All the payment data to be sent to the gateway. +*/ +function edd_send_to_gateway( $gateway, $payment_data ) { + $payment_data['gateway_nonce'] = wp_create_nonce( 'edd-gateway' ); + + // $gateway must match the ID used when registering the gateway + do_action( 'edd_gateway_' . $gateway, $payment_data ); +} + +/** + * Determines if the gateway menu should be shown + * + * If the cart amount is zero, no option is shown and the cart uses the manual gateway + * to emulate a no-gateway-setup for a free download + * + * @since 1.3.2 + * + * @return bool $show_gateways Whether or not to show the gateways + */ +function edd_show_gateways() { + $gateways = edd_get_enabled_payment_gateways(); + $show_gateways = false; + + if ( count( $gateways ) > 1 ) { + $show_gateways = true; + + if ( edd_get_cart_total() <= 0 ) { + $show_gateways = false; + } + } + + return apply_filters( 'edd_show_gateways', $show_gateways ); +} + +/** + * Determines what the currently selected gateway is + * + * If the cart amount is zero, no option is shown and the cart uses the manual + * gateway to emulate a no-gateway-setup for a free download + * + * @since 1.3.2 + * @return string $chosen_gateway The slug of the gateway + */ +function edd_get_chosen_gateway() { + + // Use the default gateway by default + $retval = edd_get_default_gateway(); + + // Get the chosen gateway + $chosen = isset( $_REQUEST['payment-mode'] ) + ? $_REQUEST['payment-mode'] + : false; + + // Sanitize the gateway + if ( false !== $chosen ) { + $chosen = preg_replace( '/[^a-zA-Z0-9-_]+/', '', $chosen ); + $chosen = urldecode( $chosen ); + + // Set return value if gateway is active + if ( ! empty( $chosen ) && edd_is_gateway_active( $chosen ) ) { + $retval = $chosen; + } + } + + // Override to manual if no price + if ( edd_get_cart_subtotal() <= 0 ) { + $retval = 'manual'; + } + + return apply_filters( 'edd_chosen_gateway', $retval, $chosen ); +} + +/** + * Record a gateway error + * + * A simple wrapper function for edd_record_log() + * + * @since 1.3.3 + * + * @param string $title Title of the log entry (default: empty) + * @param string $message Message to store in the log entry (default: empty) + * @param int $parent Parent log entry (default: 0) + * + * @return int ID of the new log entry. + */ +function edd_record_gateway_error( $title = '', $message = '', $parent = 0 ) { + return edd_record_log( $title, $message, $parent, 'gateway_error' ); +} + +/** + * Counts the number of orders made with a specific gateway. + * + * @since 1.6 + * @since 3.0 Use edd_count_orders(). + * + * @param string $gateway_label Gateway label. + * @param string $status Order status. + * + * @return int Number of orders placed based on the gateway. + */ +function edd_count_sales_by_gateway( $gateway_label = 'paypal', $status = 'complete' ) { + return edd_count_orders( array( + 'gateway' => $gateway_label, + 'status' => $status, + ) ); +} + +/** + * Determines if a gateway is setup. + * + * @since 3.1.2 + * + * @param string $gateway The gateway to check. + * + * @return bool True if the gateway is setup, false otherwise. + */ +function edd_is_gateway_setup( $gateway = '' ) { + // Return false if no gateway is passed. + if ( empty( $gateway ) ) { + return false; + } + + $gateways = edd_get_payment_gateways(); + + // If the gateway is not registered, return false. + if ( ! array_key_exists( $gateway, $gateways ) ) { + return false; + } + + // Some core gateways, we can just determine here, otherwise we'll use the default case to run the filter. + switch ( $gateway ) { + case 'stripe': + $api_key = edd_is_test_mode() + ? edd_get_option( 'test_publishable_key' ) + : edd_get_option( 'live_publishable_key' ); + + $is_setup = ! empty( $api_key ); + break; + + case 'paypal_commerce': + $is_setup = EDD\Gateways\PayPal\ready_to_accept_payments(); + break; + + default: + /** + * Run a filter to determine if a gateway is setup. + * + * This defaults to 'true' so that gateways that do not have a setup check to + * continue to work. + * + * This hook would fire on the gateway slug, prefixed with `edd_is_gateway_setup_`. + * Example: edd_is_gateway_setup_paypal_express + * + * @since 3.1.2 + * + * @param bool $is_setup Whether or not the gateway is setup. + */ + $is_setup = apply_filters( 'edd_is_gateway_setup_' . $gateway, true ); + break; + } + + return $is_setup; +} + +/** + * Gets the URL to the gateway settings page. + * + * @since 3.1.2 + * + * @param string $gateway The gateway to get the settings URL for. + * + * @return string The URL to the gateway settings page. + */ +function edd_get_gateway_settings_url( $gateway = '' ) { + // Return false if no gateway is passed. + if ( empty( $gateway ) ) { + return ''; + } + + $gateways = edd_get_payment_gateways(); + + // If the gateway is not registered, return false. + if ( ! array_key_exists( $gateway, $gateways ) ) { + return ''; + } + + // Some core gateways, we can just determine here, otherwise we'll use the default case to run the filter. + switch ( $gateway ) { + case 'stripe': + $gateway_settings_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + 'section' => 'edd-stripe', + ) + ); + break; + + case 'paypal_commerce': + $gateway_settings_url = EDD\Gateways\PayPal\Admin\get_settings_url(); + break; + + default: + /** + * Run a filter to assign a settings URL for the gateway. + * + * This defaults to an empty string so that gateways that do not have + * a setup check to continue to work. + * + * This hook would fire on the gateway slug, prefixed with `edd_gateway_settings_url_`. + * Example: edd_gateway_settings_url_paypal_express + * + * @since 3.1.2 + * + * @param string $gateway_settings_url The URL to the gateway settings. + */ + $gateway_settings_url = apply_filters( 'edd_gateway_settings_url_' . $gateway, '' ); + break; + } + + return $gateway_settings_url; +} diff --git a/includes/gateways/libraries/paypal/ipnlistener.php b/includes/gateways/libraries/paypal/ipnlistener.php deleted file mode 100755 index 60275f55995..00000000000 --- a/includes/gateways/libraries/paypal/ipnlistener.php +++ /dev/null @@ -1,300 +0,0 @@ -use_ssl) { - $uri = 'https://'.$this->getPaypalHost().'/cgi-bin/webscr'; - $this->post_uri = $uri; - } else { - $uri = 'http://'.$this->getPaypalHost().'/cgi-bin/webscr'; - $this->post_uri = $uri; - } - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HEADER, true); - - if ($this->force_ssl_v3) { - curl_setopt($ch, CURLOPT_SSLVERSION, 3); - } - - $this->response = curl_exec($ch); - $this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE)); - - if ($this->response === false || $this->response_status == '0') { - $errno = curl_errno($ch); - $errstr = curl_error($ch); - throw new Exception("cURL error: [$errno] $errstr"); - } - } - - /** - * Post Back Using fsockopen() - * - * Sends the post back to PayPal using the fsockopen() function. Called by - * the processIpn() method if the use_curl property is false. Throws an - * exception if the post fails. Populates the response, response_status, - * and post_uri properties on success. - * - * @param string The post data as a URL encoded string - */ - protected function fsockPost($encoded_data) { - - if ($this->use_ssl) { - $uri = 'ssl://'.$this->getPaypalHost(); - $port = '443'; - $this->post_uri = $uri.'/cgi-bin/webscr'; - } else { - $uri = $this->getPaypalHost(); // no "http://" in call to fsockopen() - $port = '80'; - $this->post_uri = 'http://'.$uri.'/cgi-bin/webscr'; - } - - $fp = fsockopen($uri, $port, $errno, $errstr, $this->timeout); - - if (!$fp) { - // fsockopen error - throw new Exception("fsockopen error: [$errno] $errstr"); - } - - $header .= "POST /cgi-bin/webscr HTTP/1.0\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: ".strlen($encoded_data)."\r\n"; - $header .= "Connection: Close\r\n\r\n"; - - fputs($fp, $header.$encoded_data."\r\n\r\n"); - - while(!feof($fp)) { - if (empty($this->response)) { - // extract HTTP status from first line - $this->response .= $status = fgets($fp, 1024); - $this->response_status = trim(substr($status, 9, 4)); - } else { - $this->response .= fgets($fp, 1024); - } - } - - fclose($fp); - } - - private function getPaypalHost() { - if ($this->use_sandbox) return IpnListener::SANDBOX_HOST; - else return IpnListener::PAYPAL_HOST; - } - - /** - * Get POST URI - * - * Returns the URI that was used to send the post back to PayPal. This can - * be useful for troubleshooting connection problems. The default URI - * would be "ssl://www.sandbox.paypal.com:443/cgi-bin/webscr" - * - * @return string - */ - public function getPostUri() { - return $this->post_uri; - } - - /** - * Get Response - * - * Returns the entire response from PayPal as a string including all the - * HTTP headers. - * - * @return string - */ - public function getResponse() { - return $this->response; - } - - /** - * Get Response Status - * - * Returns the HTTP response status code from PayPal. This should be "200" - * if the post back was successful. - * - * @return string - */ - public function getResponseStatus() { - return $this->response_status; - } - - /** - * Get Text Report - * - * Returns a report of the IPN transaction in plain text format. This is - * useful in emails to order processors and system administrators. Override - * this method in your own class to customize the report. - * - * @return string - */ - public function getTextReport() { - - $r = ''; - - // date and POST url - for ($i=0; $i<80; $i++) { $r .= '-'; } - $r .= "\n[".date('m/d/Y g:i A').'] - '.$this->getPostUri(); - if ($this->use_curl) $r .= " (curl)\n"; - else $r .= " (fsockopen)\n"; - - // HTTP Response - for ($i=0; $i<80; $i++) { $r .= '-'; } - $r .= "\n{$this->getResponse()}\n"; - - // POST vars - for ($i=0; $i<80; $i++) { $r .= '-'; } - $r .= "\n"; - - foreach ($this->post_data as $key => $value) { - $r .= str_pad($key, 25)."$value\n"; - } - $r .= "\n\n"; - - return $r; - } - - /** - * Process IPN - * - * Handles the IPN post back to PayPal and parsing the response. Call this - * method from your IPN listener script. Returns true if the response came - * back as "VERIFIED", false if the response came back "INVALID", and - * throws an exception if there is an error. - * - * @param array - * - * @return boolean - */ - public function processIpn($post_data=null) { - - $encoded_data = 'cmd=_notify-validate'; - - if ($post_data === null) { - // use raw POST data - if (!empty($_POST)) { - $this->post_data = $_POST; - $encoded_data .= '&'.file_get_contents('php://input'); - } else { - throw new Exception("No POST data found."); - } - } else { - // use provided data array - $this->post_data = $post_data; - - foreach ($this->post_data as $key => $value) { - $encoded_data .= "&$key=".urlencode($value); - } - } - - if ($this->use_curl) $this->curlPost($encoded_data); - else $this->fsockPost($encoded_data); - - if (strpos($this->response_status, '200') === false) { - throw new Exception("Invalid response status: ".$this->response_status); - } - - if (strpos($this->response, "VERIFIED") !== false) { - return true; - } elseif (strpos($this->response, "INVALID") !== false) { - return false; - } else { - throw new Exception("Unexpected response from PayPal."); - } - } - - /** - * Require Post Method - * - * Throws an exception and sets a HTTP 405 response header if the request - * method was not POST. - */ - public function requirePostMethod() { - // require POST requests - if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') { - header('Allow: POST', true, 405); - throw new Exception("Invalid HTTP request method."); - } - } -} -?> \ No newline at end of file diff --git a/includes/gateways/libs/amazon/Client.php b/includes/gateways/libs/amazon/Client.php new file mode 100755 index 00000000000..62897f7eede --- /dev/null +++ b/includes/gateways/libs/amazon/Client.php @@ -0,0 +1,1582 @@ + null, + 'secret_key' => null, + 'access_key' => null, + 'region' => null, + 'currency_code' => null, + 'sandbox' => false, + 'platform_id' => null, + 'cabundle_file' => null, + 'application_name' => null, + 'application_version' => null, + 'proxy_host' => null, + 'proxy_port' => -1, + 'proxy_username' => null, + 'proxy_password' => null, + 'client_id' => null, + 'handle_throttle' => true + ); + + private $modePath = null; + + // Final URL to where the API parameters POST done, based off the config['region'] and respective $mwsServiceUrls + private $mwsServiceUrl = null; + + private $mwsServiceUrls = array('eu' => 'mws-eu.amazonservices.com', + 'na' => 'mws.amazonservices.com', + 'jp' => 'mws.amazonservices.jp'); + + // Production profile end points to get the user information + private $liveProfileEndpoint = array('uk' => 'https://api.amazon.co.uk', + 'us' => 'https://api.amazon.com', + 'de' => 'https://api.amazon.de', + 'jp' => 'https://api.amazon.co.jp'); + + // Sandbox profile end points to get the user information + private $sandboxProfileEndpoint = array('uk' => 'https://api.sandbox.amazon.co.uk', + 'us' => 'https://api.sandbox.amazon.com', + 'de' => 'https://api.sandbox.amazon.de', + 'jp' => 'https://api.sandbox.amazon.co.jp'); + + private $regionMappings = array('de' => 'eu', + 'uk' => 'eu', + 'us' => 'na', + 'jp' => 'jp'); + + // Boolean variable to check if the API call was a success + public $success = false; + + /* Takes user configuration array from the user as input + * Takes JSON file path with configuration information as input + * Validates the user configuration array against existing config array + */ + + public function __construct($config = null) + { + if (!is_null($config)) { + + if (is_array($config)) { + $configArray = $config; + } elseif (!is_array($config)) { + $configArray = $this->checkIfFileExists($config); + } + + if (is_array($configArray)) { + $this->checkConfigKeys($configArray); + } else { + throw new \Exception('$config is of the incorrect type ' . gettype($configArray) . ' and should be of the type array'); + } + } else { + throw new \Exception('$config cannot be null.'); + } + } + + /* checkIfFileExists - check if the JSON file exists in the path provided */ + + private function checkIfFileExists($config) + { + if(file_exists($config)) + { + $jsonString = file_get_contents($config); + $configArray = json_decode($jsonString, true); + + $jsonError = json_last_error(); + + if ($jsonError != 0) { + $errorMsg = "Error with message - content is not in json format" . $this->getErrorMessageForJsonError($jsonError) . " " . $configArray; + throw new \Exception($errorMsg); + } + } else { + $errorMsg ='$config is not a Json File path or the Json File was not found in the path provided'; + throw new \Exception($errorMsg); + } + return $configArray; + } + + /* Checks if the keys of the input configuration matches the keys in the config array + * if they match the values are taken else throws exception + * strict case match is not performed + */ + + private function checkConfigKeys($config) + { + $config = array_change_key_case($config, CASE_LOWER); + $config = $this->trimArray($config); + + foreach ($config as $key => $value) { + if (array_key_exists($key, $this->config)) { + $this->config[$key] = $value; + } else { + throw new \Exception('Key ' . $key . ' is either not part of the configuration or has incorrect Key name. + check the config array key names to match your key names of your config array', 1); + } + } + } + + /* Convert a json error code to a descriptive error message + * + * @param int $jsonError message code + * + * @return string error message + */ + + private function getErrorMessageForJsonError($jsonError) + { + switch ($jsonError) { + case JSON_ERROR_DEPTH: + return " - maximum stack depth exceeded."; + break; + case JSON_ERROR_STATE_MISMATCH: + return " - invalid or malformed JSON."; + break; + case JSON_ERROR_CTRL_CHAR: + return " - control character error."; + break; + case JSON_ERROR_SYNTAX: + return " - syntax error."; + break; + default: + return "."; + break; + } + } + + /* Setter for sandbox + * Sets the Boolean value for config['sandbox'] variable + */ + + public function setSandbox($value) + { + if (is_bool($value)) { + $this->config['sandbox'] = $value; + } else { + throw new \Exception($value . ' is of type ' . gettype($value) . ' and should be a boolean value'); + } + } + + /* Setter for config['client_id'] + * Sets the value for config['client_id'] variable + */ + + public function setClientId($value) + { + if (!empty($value)) { + $this->config['client_id'] = $value; + } else { + throw new \Exception('setter value for client ID provided is empty'); + } + } + + /* Setter for Proxy + * input $proxy [array] + * @param $proxy['proxy_user_host'] - hostname for the proxy + * @param $proxy['proxy_user_port'] - hostname for the proxy + * @param $proxy['proxy_user_name'] - if your proxy required a username + * @param $proxy['proxy_user_password'] - if your proxy required a password + */ + + public function setProxy($proxy) + { + $proxy = $this->trimArray($proxy); + + if (!empty($proxy['proxy_user_host'])) + $this->config['proxy_user_host'] = $proxy['proxy_user_host']; + + if (!empty($proxy['proxy_user_port'])) + $this->config['proxy_user_port'] = $proxy['proxy_user_port']; + + if (!empty($proxy['proxy_user_name'])) + $this->config['proxy_user_name'] = $proxy['proxy_user_name']; + + if (!empty($proxy['proxy_user_password'])) + $this->config['proxy_user_password'] = $proxy['proxy_user_password']; + } + + /* Setter for $mwsServiceUrl + * Set the URL to which the post request has to be made for unit testing + */ + + public function setMwsServiceUrl($url) + { + $this->mwsServiceUrl = $url; + } + + /* Getter + * Gets the value for the key if the key exists in config + */ + + public function __get($name) + { + if (array_key_exists(strtolower($name), $this->config)) { + return $this->config[strtolower($name)]; + } else { + throw new \Exception('Key ' . $name . ' is either not a part of the configuration array config or the' . $name . 'does not match the key name in the config array', 1); + } + } + + /* Getter for parameters string + * Gets the value for the parameters string for unit testing + */ + + public function getParameters() + { + return trim($this->parameters); + } + + /* Trim the input Array key values */ + + private function trimArray($array) + { + foreach ($array as $key => $value) + { + $array[$key] = trim($value); + } + return $array; + } + + /* GetUserInfo convenience function - Returns user's profile information from Amazon using the access token returned by the Button widget. + * + * @see http://login.amazon.com/website Step 4 + * @param $accessToken [String] + */ + + public function getUserInfo($accessToken) + { + // Get the correct Profile Endpoint URL based off the country/region provided in the config['region'] + $this->profileEndpointUrl(); + + if (empty($accessToken)) { + throw new \InvalidArgumentException('Access Token is a required parameter and is not set'); + } + + // To make sure double encoding doesn't occur decode first and encode again. + $accessToken = urldecode($accessToken); + $url = $this->profileEndpoint . '/auth/o2/tokeninfo?access_token=' . urlEncode($accessToken); + + $httpCurlRequest = new HttpCurl(); + + $response = $httpCurlRequest->httpGet($url); + $data = json_decode($response); + + if ($data->aud != $this->config['client_id']) { + // The access token does not belong to us + throw new \Exception('The Access token entered is incorrect'); + } + + // Exchange the access token for user profile + $url = $this->profileEndpoint . '/user/profile'; + $httpCurlRequest = new HttpCurl(); + + $httpCurlRequest->setAccessToken($accessToken); + $httpCurlRequest->setHttpHeader(true); + $response = $httpCurlRequest->httpGet($url); + + $userInfo = json_decode($response, true); + return $userInfo; + } + + /* setParametersAndPost - sets the parameters array with non empty values from the requestParameters array sent to API calls. + * If Provider Credit Details is present, values are set by setProviderCreditDetails + * If Provider Credit Reversal Details is present, values are set by setProviderCreditDetails + */ + + private function setParametersAndPost($parameters, $fieldMappings, $requestParameters) + { + /* For loop to take all the non empty parameters in the $requestParameters and add it into the $parameters array, + * if the keys are matched from $requestParameters array with the $fieldMappings array + */ + foreach ($requestParameters as $param => $value) { + + if(!is_array($value)) { + $value = trim($value); + } + + if (array_key_exists($param, $fieldMappings) && $value!='') { + + if(is_array($value)) { + // If the parameter is a provider_credit_details or provider_credit_reversal_details, call the respective functions to set the values + if($param === 'provider_credit_details') { + $parameters = $this->setProviderCreditDetails($parameters,$value); + } elseif ($param === 'provider_credit_reversal_details') { + $parameters = $this->setProviderCreditReversalDetails($parameters,$value); + } + + } else{ + // For variables that are boolean values, strtolower them + if($this->checkIfBool($value)) + { + $value = strtolower($value); + } + + $parameters[$fieldMappings[$param]] = $value; + } + } + } + + $parameters = $this->setDefaultValues($parameters, $fieldMappings, $requestParameters); + $responseObject = $this->calculateSignatureAndPost($parameters); + + return $responseObject; + } + + /* checkIfBool - checks if the input is a boolean */ + + private function checkIfBool($string) + { + $string = strtolower($string); + return in_array($string, array('true', 'false')); + } + + /* calculateSignatureAndPost - convert the Parameters array to string and curl POST the parameters to MWS */ + + private function calculateSignatureAndPost($parameters) + { + // Call the signature and Post function to perform the actions. Returns XML in array format + $parametersString = $this->calculateSignatureAndParametersToString($parameters); + + // POST using curl the String converted Parameters + $response = $this->invokePost($parametersString); + + // Send this response as args to ResponseParser class which will return the object of the class. + $responseObject = new ResponseParser($response); + return $responseObject; + } + + /* If merchant_id is not set via the requestParameters array then it's taken from the config array + * + * Set the platform_id if set in the config['platform_id'] array + * + * If currency_code is set in the $requestParameters and it exists in the $fieldMappings array, strtoupper it + * else take the value from config array if set + */ + + private function setDefaultValues($parameters, $fieldMappings, $requestParameters) + { + if (empty($requestParameters['merchant_id'])) + $parameters['SellerId'] = $this->config['merchant_id']; + + if (array_key_exists('platform_id', $fieldMappings)) { + if (empty($requestParameters['platform_id']) && !empty($this->config['platform_id'])) + $parameters[$fieldMappings['platform_id']] = $this->config['platform_id']; + } + + if (array_key_exists('currency_code', $fieldMappings)) { + if (!empty($requestParameters['currency_code'])) { + $parameters[$fieldMappings['currency_code']] = strtoupper($requestParameters['currency_code']); + } else { + $parameters[$fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /* setProviderCreditDetails - sets the provider credit details sent via the Capture or Authorize API calls + * @param provider_id - [String] + * @param credit_amount - [String] + * @optional currency_code - [String] + */ + + private function setProviderCreditDetails($parameters, $providerCreditInfo) + { + $providerIndex = 0; + $providerString = 'ProviderCreditList.member.'; + + $fieldMappings = array( + 'provider_id' => 'ProviderId', + 'credit_amount' => 'CreditAmount.Amount', + 'currency_code' => 'CreditAmount.CurrencyCode' + ); + + foreach ($providerCreditInfo as $key => $value) + { + $value = array_change_key_case($value, CASE_LOWER); + $providerIndex = $providerIndex + 1; + + foreach ($value as $param => $val) + { + if (array_key_exists($param, $fieldMappings) && trim($val)!='') { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings[$param]] = $val; + } + } + + // If currency code is not entered take it from the config array + if(empty($parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']])) + { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /* setProviderCreditReversalDetails - sets the reverse provider credit details sent via the Refund API call. + * @param provider_id - [String] + * @param credit_amount - [String] + * @optional currency_code - [String] + */ + + private function setProviderCreditReversalDetails($parameters, $providerCreditInfo) + { + $providerIndex = 0; + $providerString = 'ProviderCreditReversalList.member.'; + + $fieldMappings = array( + 'provider_id' => 'ProviderId', + 'credit_reversal_amount' => 'CreditReversalAmount.Amount', + 'currency_code' => 'CreditReversalAmount.CurrencyCode' + ); + + foreach ($providerCreditInfo as $key => $value) + { + $value = array_change_key_case($value, CASE_LOWER); + $providerIndex = $providerIndex + 1; + + foreach ($value as $param => $val) + { + if (array_key_exists($param, $fieldMappings) && trim($val)!='') { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings[$param]] = $val; + } + } + + // If currency code is not entered take it from the config array + if(empty($parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']])) + { + $parameters[$providerString.$providerIndex. '.' .$fieldMappings['currency_code']] = strtoupper($this->config['currency_code']); + } + } + + return $parameters; + } + + /* GetOrderReferenceDetails API call - Returns details about the Order Reference object and its current state. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetOrderReferenceDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['address_consent_token'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getOrderReferenceDetails($requestParameters = array()) + { + + $parameters['Action'] = 'GetOrderReferenceDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'address_consent_token' => 'AddressConsentToken', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + return ($responseObject); + } + + /* SetOrderReferenceDetails API call - Sets order reference details such as the order total and a description for the order. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_SetOrderReferenceDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @param requestParameters['amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function setOrderReferenceDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'SetOrderReferenceDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ConfirmOrderReferenceDetails API call - Confirms that the order reference is free of constraints and all required information has been set on the order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_ConfirmOrderReference.html + + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function confirmOrderReference($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ConfirmOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CancelOrderReferenceDetails API call - Cancels a previously confirmed order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CancelOrderReference.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['cancelation_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function cancelOrderReference($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CancelOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'cancelation_reason' => 'CancelationReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CloseOrderReferenceDetails API call - Confirms that an order reference has been fulfilled (fully or partially) + * and that you do not expect to create any new authorizations on this order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CloseOrderReference.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function closeOrderReference($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CloseOrderReference'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CloseAuthorization API call - Closes an authorization. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CloseOrderReference.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] - [String] + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function closeAuthorization($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CloseAuthorization'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Authorize API call - Reserves a specified amount against the payment method(s) stored in the order reference. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_Authorize.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_order_reference_id'] - [String] + * @param requestParameters['authorization_amount'] [String] + * @param requestParameters['currency_code'] - [String] + * @param requestParameters['authorization_reference_id'] [String] + * @optional requestParameters['capture_now'] [String] + * @optional requestParameters['provider_credit_details'] - [array (array())] + * @optional requestParameters['seller_authorization_note'] [String] + * @optional requestParameters['transaction_timeout'] [String] - Defaults to 1440 minutes + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function authorize($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'Authorize'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_order_reference_id' => 'AmazonOrderReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'capture_now' => 'CaptureNow', + 'provider_credit_details' => array(), + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetAuthorizationDetails API call - Returns the status of a particular authorization and the total amount captured on the authorization. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetAuthorizationDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getAuthorizationDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetAuthorizationDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Capture API call - Captures funds from an authorized payment instrument. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_Capture.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_authorization_id'] - [String] + * @param requestParameters['capture_amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @param requestParameters['capture_reference_id'] - [String] + * @optional requestParameters['provider_credit_details'] - [array (array())] + * @optional requestParameters['seller_capture_note'] - [String] + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function capture($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'Capture'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_authorization_id' => 'AmazonAuthorizationId', + 'capture_amount' => 'CaptureAmount.Amount', + 'currency_code' => 'CaptureAmount.CurrencyCode', + 'capture_reference_id' => 'CaptureReferenceId', + 'provider_credit_details' => array(), + 'seller_capture_note' => 'SellerCaptureNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetCaptureDetails API call - Returns the status of a particular capture and the total amount refunded on the capture. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetCaptureDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_capture_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getCaptureDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetCaptureDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Refund API call - Refunds a previously captured amount. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_Refund.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_capture_id'] - [String] + * @param requestParameters['refund_reference_id'] - [String] + * @param requestParameters['refund_amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['provider_credit_reversal_details'] - [array(array())] + * @optional requestParameters['seller_refund_note'] [String] + * @optional requestParameters['soft_descriptor'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function refund($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'Refund'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_capture_id' => 'AmazonCaptureId', + 'refund_reference_id' => 'RefundReferenceId', + 'refund_amount' => 'RefundAmount.Amount', + 'currency_code' => 'RefundAmount.CurrencyCode', + 'provider_credit_reversal_details' => array(), + 'seller_refund_note' => 'SellerRefundNote', + 'soft_descriptor' => 'SoftDescriptor', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetRefundDetails API call - Returns the status of a particular refund. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetRefundDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_refund_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getRefundDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetRefundDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_refund_id' => 'AmazonRefundId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetServiceStatus API Call - Returns the operational status of the Off-Amazon Payments API section + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetServiceStatus.html + * + * The GetServiceStatus operation returns the operational status of the Off-Amazon Payments API + * section of Amazon Marketplace Web Service (Amazon MWS). + * Status values are GREEN, GREEN_I, YELLOW, and RED. + * + * @param requestParameters['merchant_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getServiceStatus($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetServiceStatus'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CreateOrderReferenceForId API Call - Creates an order reference for the given object + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CreateOrderReferenceForId.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['Id'] - [String] + * @optional requestParameters['inherit_shipping_address'] [Boolean] + * @optional requestParameters['ConfirmNow'] - [Boolean] + * @optional Amount (required when confirm_now is set to true) [String] + * @optional requestParameters['currency_code'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function createOrderReferenceForId($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CreateOrderReferenceForId'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'id' => 'Id', + 'id_type' => 'IdType', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'confirm_now' => 'ConfirmNow', + 'amount' => 'OrderReferenceAttributes.OrderTotal.Amount', + 'currency_code' => 'OrderReferenceAttributes.OrderTotal.CurrencyCode', + 'platform_id' => 'OrderReferenceAttributes.PlatformId', + 'seller_note' => 'OrderReferenceAttributes.SellerNote', + 'seller_order_id' => 'OrderReferenceAttributes.SellerOrderAttributes.SellerOrderId', + 'store_name' => 'OrderReferenceAttributes.SellerOrderAttributes.StoreName', + 'custom_information' => 'OrderReferenceAttributes.SellerOrderAttributes.CustomInformation', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetBillingAgreementDetails API Call - Returns details about the Billing Agreement object and its current state. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_GetBillingAgreementDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getBillingAgreementDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetBillingAgreementDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'address_consent_token' => 'AddressConsentToken', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* SetBillingAgreementDetails API call - Sets Billing Agreement details such as a description of the agreement and other information about the seller. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_SetBillingAgreementDetails.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @param requestParameters['amount'] - [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['seller_billing_agreement_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function setBillingAgreementDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'SetBillingAgreementDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'platform_id' => 'BillingAgreementAttributes.PlatformId', + 'seller_note' => 'BillingAgreementAttributes.SellerNote', + 'seller_billing_agreement_id' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.SellerBillingAgreementId', + 'custom_information' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.CustomInformation', + 'store_name' => 'BillingAgreementAttributes.SellerBillingAgreementAttributes.StoreName', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ConfirmBillingAgreement API Call - Confirms that the Billing Agreement is free of constraints and all required information has been set on the Billing Agreement. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_ConfirmBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function confirmBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ConfirmBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ValidateBillignAgreement API Call - Validates the status of the Billing Agreement object and the payment method associated with it. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_ValidateBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function validateBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ValidateBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* AuthorizeOnBillingAgreement API call - Reserves a specified amount against the payment method(s) stored in the Billing Agreement. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_AuthorizeOnBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @param requestParameters['authorization_reference_id'] [String] + * @param requestParameters['authorization_amount'] [String] + * @param requestParameters['currency_code'] - [String] + * @optional requestParameters['seller_authorization_note'] [String] + * @optional requestParameters['transaction_timeout'] - Defaults to 1440 minutes + * @optional requestParameters['capture_now'] [String] + * @optional requestParameters['soft_descriptor'] - - [String] + * @optional requestParameters['seller_note'] - [String] + * @optional requestParameters['platform_id'] - [String] + * @optional requestParameters['custom_information'] - [String] + * @optional requestParameters['seller_order_id'] - [String] + * @optional requestParameters['store_name'] - [String] + * @optional requestParameters['inherit_shipping_address'] [Boolean] - Defaults to true + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function authorizeOnBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'AuthorizeOnBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'authorization_reference_id' => 'AuthorizationReferenceId', + 'authorization_amount' => 'AuthorizationAmount.Amount', + 'currency_code' => 'AuthorizationAmount.CurrencyCode', + 'seller_authorization_note' => 'SellerAuthorizationNote', + 'transaction_timeout' => 'TransactionTimeout', + 'capture_now' => 'CaptureNow', + 'soft_descriptor' => 'SoftDescriptor', + 'seller_note' => 'SellerNote', + 'platform_id' => 'PlatformId', + 'custom_information' => 'SellerOrderAttributes.CustomInformation', + 'seller_order_id' => 'SellerOrderAttributes.SellerOrderId', + 'store_name' => 'SellerOrderAttributes.StoreName', + 'inherit_shipping_address' => 'InheritShippingAddress', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* CloseBillingAgreement API Call - Returns details about the Billing Agreement object and its current state. + * @see http://docs.developer.amazonservices.com/en_US/off_amazon_payments/OffAmazonPayments_CloseBillingAgreement.html + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_billing_agreement_id'] - [String] + * @optional requestParameters['closure_reason'] [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function closeBillingAgreement($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'CloseBillingAgreement'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_billing_agreement_id' => 'AmazonBillingAgreementId', + 'closure_reason' => 'ClosureReason', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* charge convenience method + * Performs the API calls + * 1. SetOrderReferenceDetails / SetBillingAgreementDetails + * 2. ConfirmOrderReference / ConfirmBillingAgreement + * 3. Authorize (with Capture) / AuthorizeOnBillingAgreeemnt (with Capture) + * + * @param requestParameters['merchant_id'] - [String] + * + * @param requestParameters['amazon_reference_id'] - [String] : Order Reference ID /Billing Agreement ID + * If requestParameters['amazon_reference_id'] is empty then the following is required, + * @param requestParameters['amazon_order_reference_id'] - [String] : Order Reference ID + * or, + * @param requestParameters['amazon_billing_agreement_id'] - [String] : Billing Agreement ID + * + * @param $requestParameters['charge_amount'] - [String] : Amount value to be captured + * @param requestParameters['currency_code'] - [String] : Currency Code for the Amount + * @param requestParameters['authorization_reference_id'] - [String]- Any unique string that needs to be passed + * @optional requestParameters['charge_note'] - [String] : Seller Note sent to the buyer + * @optional requestParameters['transaction_timeout'] - [String] : Defaults to 1440 minutes + * @optional requestParameters['charge_order_id'] - [String] : Custom Order ID provided + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function charge($requestParameters = array()) { + + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + $requestParameters= $this->trimArray($requestParameters); + + $setParameters = $authorizeParameters = $confirmParameters = $requestParameters; + + $chargeType = ''; + + if (!empty($requestParameters['amazon_order_reference_id'])) + { + $chargeType = 'OrderReference'; + + } elseif(!empty($requestParameters['amazon_billing_agreement_id'])) { + $chargeType = 'BillingAgreement'; + + } elseif (!empty($requestParameters['amazon_reference_id'])) { + switch (substr(strtoupper($requestParameters['amazon_reference_id']), 0, 1)) { + case 'P': + case 'S': + $chargeType = 'OrderReference'; + $setParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + $authorizeParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + $confirmParameters['amazon_order_reference_id'] = $requestParameters['amazon_reference_id']; + break; + case 'B': + case 'C': + $chargeType = 'BillingAgreement'; + $setParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + $authorizeParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + $confirmParameters['amazon_billing_agreement_id'] = $requestParameters['amazon_reference_id']; + break; + default: + throw new \Exception('Invalid Amazon Reference ID'); + } + } else { + throw new \Exception('key amazon_order_reference_id or amazon_billing_agreement_id is null and is a required parameter'); + } + + // Set the other parameters if the values are present + $setParameters['amount'] = !empty($requestParameters['charge_amount']) ? $requestParameters['charge_amount'] : ''; + $authorizeParameters['authorization_amount'] = !empty($requestParameters['charge_amount']) ? $requestParameters['charge_amount'] : ''; + + $setParameters['seller_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + $authorizeParameters['seller_authorization_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + $authorizeParameters['seller_note'] = !empty($requestParameters['charge_note']) ? $requestParameters['charge_note'] : ''; + + $setParameters['seller_order_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + $setParameters['seller_billing_agreement_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + $authorizeParameters['seller_order_id'] = !empty($requestParameters['charge_order_id']) ? $requestParameters['charge_order_id'] : ''; + + $authorizeParameters['capture_now'] = 'true'; + + $response = $this->makeChargeCalls($chargeType, $setParameters, $confirmParameters, $authorizeParameters); + return $response; + } + + /* makeChargeCalls - makes API calls based off the charge type (OrderReference or BillingAgreement) */ + + private function makeChargeCalls($chargeType, $setParameters, $confirmParameters, $authorizeParameters) + { + switch ($chargeType) { + case 'OrderReference': + $response = $this->setOrderReferenceDetails($setParameters); + if ($this->success) { + $this->confirmOrderReference($confirmParameters); + } + if ($this->success) { + $response = $this->Authorize($authorizeParameters); + } + return $response; + case 'BillingAgreement': + // Get the Billing Agreement details and feed the response object to the ResponseParser + $responseObj = $this->getBillingAgreementDetails($setParameters); + // Call the function GetBillingAgreementDetailsStatus in ResponseParser.php providing it the XML response + // $baStatus is an aray containing the State of the Billing Agreement + $baStatus = $responseObj->getBillingAgreementDetailsStatus($responseObj->toXml()); + if ($baStatus['State'] != 'Open') { + $response = $this->SetBillingAgreementDetails($setParameters); + if ($this->success) { + $response = $this->ConfirmBillingAgreement($confirmParameters); + } + } + // Check the Billing Agreement status again before making the Authorization. + $responseObj = $this->getBillingAgreementDetails($setParameters); + $baStatus = $responseObj->GetBillingAgreementDetailsStatus($responseObj->toXml()); + if ($this->success && $baStatus['State'] === 'Open') { + $response = $this->AuthorizeOnBillingAgreement($authorizeParameters); + } + return $response; + } + } + + /* GetProviderCreditDetails API Call - Get the details of the Provider Credit. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getProviderCreditDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetProviderCreditDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_id' => 'AmazonProviderCreditId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* GetProviderCreditReversalDetails API Call - Get details of the Provider Credit Reversal. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_reversal_id'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function getProviderCreditReversalDetails($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'GetProviderCreditReversalDetails'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_reversal_id' => 'AmazonProviderCreditReversalId', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* ReverseProviderCredit API Call - Reverse the Provider Credit. + * + * @param requestParameters['merchant_id'] - [String] + * @param requestParameters['amazon_provider_credit_id'] - [String] + * @optional requestParameters['credit_reversal_reference_id'] - [String] + * @param requestParameters['credit_reversal_amount'] - [String] + * @optional requestParameters['currency_code'] - [String] + * @optional requestParameters['credit_reversal_note'] - [String] + * @optional requestParameters['mws_auth_token'] - [String] + */ + + public function reverseProviderCredit($requestParameters = array()) + { + $parameters = array(); + $parameters['Action'] = 'ReverseProviderCredit'; + $requestParameters = array_change_key_case($requestParameters, CASE_LOWER); + + $fieldMappings = array( + 'merchant_id' => 'SellerId', + 'amazon_provider_credit_id' => 'AmazonProviderCreditId', + 'credit_reversal_reference_id' => 'CreditReversalReferenceId', + 'credit_reversal_amount' => 'CreditReversalAmount.Amount', + 'currency_code' => 'CreditReversalAmount.CurrencyCode', + 'credit_reversal_note' => 'CreditReversalNote', + 'mws_auth_token' => 'MWSAuthToken' + ); + + $responseObject = $this->setParametersAndPost($parameters, $fieldMappings, $requestParameters); + + return ($responseObject); + } + + /* Create an Array of required parameters, sort them + * Calculate signature and invoke the POST to the MWS Service URL + * + * @param AWSAccessKeyId [String] + * @param Version [String] + * @param SignatureMethod [String] + * @param Timestamp [String] + * @param Signature [String] + */ + + private function calculateSignatureAndParametersToString($parameters = array()) + { + $parameters['AWSAccessKeyId'] = $this->config['access_key']; + $parameters['Version'] = self::SERVICE_VERSION; + $parameters['SignatureMethod'] = 'HmacSHA256'; + $parameters['SignatureVersion'] = 2; + $parameters['Timestamp'] = $this->getFormattedTimestamp(); + uksort($parameters, 'strcmp'); + + $this->createServiceUrl(); + + $parameters['Signature'] = $this->signParameters($parameters); + $parameters = $this->getParametersAsString($parameters); + + // Save these parameters in the parameters variable so that it can be returned for unit testing. + $this->parameters = $parameters; + return $parameters; + } + + /* Computes RFC 2104-compliant HMAC signature for request parameters + * Implements AWS Signature, as per following spec: + * + * If Signature Version is 0, it signs concatenated Action and Timestamp + * + * If Signature Version is 1, it performs the following: + * + * Sorts all parameters (including SignatureVersion and excluding Signature, + * the value of which is being created), ignoring case. + * + * Iterate over the sorted list and append the parameter name (in original case) + * and then its value. It will not URL-encode the parameter values before + * constructing this string. There are no separators. + * + * If Signature Version is 2, string to sign is based on following: + * + * 1. The HTTP Request Method followed by an ASCII newline (%0A) + * 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline. + * 3. The URL encoded HTTP absolute path component of the URI + * (up to but not including the query string parameters); + * if this is empty use a forward '/'. This parameter is followed by an ASCII newline. + * 4. The concatenation of all query string components (names and values) + * as UTF-8 characters which are URL encoded as per RFC 3986 + * (hex characters MUST be uppercase), sorted using lexicographic byte ordering. + * Parameter names are separated from their values by the '=' character + * (ASCII character 61), even if the value is empty. + * Pairs of parameter and values are separated by the '&' character (ASCII code 38). + * + */ + + private function signParameters(array $parameters) + { + $signatureVersion = $parameters['SignatureVersion']; + $algorithm = "HmacSHA1"; + $stringToSign = null; + if (2 === $signatureVersion) { + $algorithm = "HmacSHA256"; + $parameters['SignatureMethod'] = $algorithm; + $stringToSign = $this->calculateStringToSignV2($parameters); + } else { + throw new \Exception("Invalid Signature Version specified"); + } + + return $this->sign($stringToSign, $algorithm); + } + + /* Calculate String to Sign for SignatureVersion 2 + * @param array $parameters request parameters + * @return String to Sign + */ + + private function calculateStringToSignV2(array $parameters) + { + $data = 'POST'; + $data .= "\n"; + $data .= $this->mwsEndpointUrl; + $data .= "\n"; + $data .= $this->mwsEndpointPath; + $data .= "\n"; + $data .= $this->getParametersAsString($parameters); + return $data; + } + + /* Convert paremeters to Url encoded query string */ + + private function getParametersAsString(array $parameters) + { + $queryParameters = array(); + foreach ($parameters as $key => $value) { + $queryParameters[] = $key . '=' . $this->urlEncode($value); + } + + return implode('&', $queryParameters); + } + + private function urlEncode($value) + { + return str_replace('%7E', '~', rawurlencode($value)); + } + + /* Computes RFC 2104-compliant HMAC signature */ + + private function sign($data, $algorithm) + { + if ($algorithm === 'HmacSHA1') { + $hash = 'sha1'; + } else if ($algorithm === 'HmacSHA256') { + $hash = 'sha256'; + } else { + throw new \Exception("Non-supported signing method specified"); + } + + return base64_encode(hash_hmac($hash, $data, $this->config['secret_key'], true)); + } + + /* Formats date as ISO 8601 timestamp */ + + private function getFormattedTimestamp() + { + return gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time()); + } + + /* invokePost takes the parameters and invokes the httpPost function to POST the parameters + * Exponential retries on error 500 and 503 + * The response from the POST is an XML which is converted to Array + */ + + private function invokePost($parameters) + { + $response = array(); + $statusCode = 200; + $this->success = false; + + // Submit the request and read response body + try { + $shouldRetry = true; + $retries = 0; + do { + try { + $this->constructUserAgentHeader(); + + $httpCurlRequest = new HttpCurl($this->config); + $response = $httpCurlRequest->httpPost($this->mwsServiceUrl, $this->userAgent, $parameters); + + // Split the API response into Response Body and the other parts of the response into other + list($other, $responseBody) = explode("\r\n\r\n", $response, 2); + $other = preg_split("/\r\n|\n|\r/", $other); + + list($protocol, $code, $text) = explode(' ', trim(array_shift($other)), 3); + $response = array( + 'Status' => (int) $code, + 'ResponseBody' => $responseBody + ); + + $statusCode = $response['Status']; + + if ($statusCode == 200) { + $shouldRetry = false; + $this->success = true; + } elseif ($statusCode == 500 || $statusCode == 503) { + + $shouldRetry = true; + if ($shouldRetry && strtolower($this->config['handle_throttle'])) { + $this->pauseOnRetry(++$retries, $statusCode); + } + } else { + $shouldRetry = false; + } + } catch (\Exception $e) { + throw $e; + } + } while ($shouldRetry); + } catch (\Exception $se) { + throw $se; + } + + return $response; + } + + /* Exponential sleep on failed request + * @param retries current retry + * @throws Exception if maximum number of retries has been reached + */ + + private function pauseOnRetry($retries, $status) + { + if ($retries <= self::MAX_ERROR_RETRY) { + $delay = (int) (pow(4, $retries) * 100000); + usleep($delay); + } else { + throw new \Exception('Error Code: '. $status.PHP_EOL.'Maximum number of retry attempts - '. $retries .' reached'); + } + } + + /* Create MWS service URL and the Endpoint path */ + + private function createServiceUrl() + { + $this->modePath = strtolower($this->config['sandbox']) ? 'OffAmazonPayments_Sandbox' : 'OffAmazonPayments'; + + if (!empty($this->config['region'])) { + $region = strtolower($this->config['region']); + if (array_key_exists($region, $this->regionMappings)) { + $this->mwsEndpointUrl = $this->mwsServiceUrls[$this->regionMappings[$region]]; + $this->mwsServiceUrl = 'https://' . $this->mwsEndpointUrl . '/' . $this->modePath . '/' . self::SERVICE_VERSION; + $this->mwsEndpointPath = '/' . $this->modePath . '/' . self::SERVICE_VERSION; + } else { + throw new \Exception($region . ' is not a valid region'); + } + } else { + throw new \Exception("config['region'] is a required parameter and is not set"); + } + } + + /* Based on the config['region'] and config['sandbox'] values get the user profile URL */ + + private function profileEndpointUrl() + { + if (!empty($this->config['region'])) { + $region = strtolower($this->config['region']); + + if (array_key_exists($region, $this->sandboxProfileEndpoint) && $this->config['sandbox'] ) { + $this->profileEndpoint = $this->sandboxProfileEndpoint[$region]; + } elseif (array_key_exists($region, $this->liveProfileEndpoint)) { + $this->profileEndpoint = $this->liveProfileEndpoint[$region]; + } else{ + throw new \Exception($region . ' is not a valid region'); + } + } else { + throw new \Exception("config['region'] is a required parameter and is not set"); + } + } + + /* Create the User Agent Header sent with the POST request */ + + private function constructUserAgentHeader() + { + $this->userAgent = $this->quoteApplicationName($this->config['application_name']) . '/' . $this->quoteApplicationVersion($this->config['application_version']); + $this->userAgent .= ' ('; + $this->userAgent .= 'Language=PHP/' . phpversion(); + $this->userAgent .= '; '; + $this->userAgent .= 'Platform=' . php_uname('s') . '/' . php_uname('m') . '/' . php_uname('r'); + $this->userAgent .= '; '; + $this->userAgent .= 'MWSClientVersion=' . self::MWS_CLIENT_VERSION; + $this->userAgent .= ')'; + } + + /* Collapse multiple whitespace characters into a single ' ' and backslash escape '\', + * and '/' characters from a string. + * @param $s + * @return string + */ + + private function quoteApplicationName($s) + { + $quotedString = preg_replace('/ {2,}|\s/', ' ', $s); + $quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString); + $quotedString = preg_replace('/\//', '\\/', $quotedString); + return $quotedString; + } + + /* Collapse multiple whitespace characters into a single ' ' and backslash escape '\', + * and '(' characters from a string. + * + * @param $s + * @return string + */ + + private function quoteApplicationVersion($s) + { + $quotedString = preg_replace('/ {2,}|\s/', ' ', $s); + $quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString); + $quotedString = preg_replace('/\\(/', '\\(', $quotedString); + return $quotedString; + } +} diff --git a/includes/gateways/libs/amazon/HttpCurl.php b/includes/gateways/libs/amazon/HttpCurl.php new file mode 100755 index 00000000000..937329a06a7 --- /dev/null +++ b/includes/gateways/libs/amazon/HttpCurl.php @@ -0,0 +1,129 @@ +config = $config; + } + + /* Setter for boolean header to get the user info */ + + public function setHttpHeader() + { + $this->header = true; + } + + /* Setter for Access token to get the user info */ + + public function setAccessToken($accesstoken) + { + $this->accessToken = $accesstoken; + } + + /* Add the common Curl Parameters to the curl handler $ch + * Also checks for optional parameters if provided in the config + * config['cabundle_file'] + * config['proxy_port'] + * config['proxy_host'] + * config['proxy_username'] + * config['proxy_password'] + */ + + private function commonCurlParams($url,$userAgent) + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_PORT, 443); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + if (!is_null($this->config['cabundle_file'])) { + curl_setopt($ch, CURLOPT_CAINFO, $this->config['cabundle_file']); + } + + if (!empty($userAgent)) + curl_setopt($ch, CURLOPT_USERAGENT, $userAgent); + + if ($this->config['proxy_host'] != null && $this->config['proxy_port'] != -1) { + curl_setopt($ch, CURLOPT_PROXY, $this->config['proxy_host'] . ':' . $this->config['proxy_port']); + } + + if ($this->config['proxy_username'] != null && $this->config['proxy_password'] != null) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config['proxy_username'] . ':' . $this->config['proxy_password']); + } + + return $ch; + } + + /* POST using curl for the following situations + * 1. API calls + * 2. IPN certificate retrieval + * 3. Get User Info + */ + + public function httpPost($url, $userAgent = null, $parameters = null) + { + $ch = $this->commonCurlParams($url,$userAgent); + + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); + curl_setopt($ch, CURLOPT_HEADER, true); + + $response = $this->execute($ch); + return $response; + } + + /* GET using curl for the following situations + * 1. IPN certificate retrieval + * 2. Get User Info + */ + + public function httpGet($url, $userAgent = null) + { + $ch = $this->commonCurlParams($url,$userAgent); + + // Setting the HTTP header with the Access Token only for Getting user info + if ($this->header) { + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Authorization: bearer ' . $this->accessToken + )); + } + + $response = $this->execute($ch); + return $response; + } + + /* Execute Curl request */ + + private function execute($ch) + { + $response = ''; + if (!$response = curl_exec($ch)) { + $error_msg = "Unable to post request, underlying exception of " . curl_error($ch); + curl_close($ch); + throw new \Exception($error_msg); + } + curl_close($ch); + return $response; + } +} diff --git a/includes/gateways/libs/amazon/Interface.php b/includes/gateways/libs/amazon/Interface.php new file mode 100755 index 00000000000..65a6bd63ffd --- /dev/null +++ b/includes/gateways/libs/amazon/Interface.php @@ -0,0 +1,482 @@ +_response [XML] + */ + + public function toArray(); + + /* Get the status of the BillingAgreement */ + + public function getBillingAgreementDetailsStatus($response); +} diff --git a/includes/gateways/libs/amazon/IpnHandler.php b/includes/gateways/libs/amazon/IpnHandler.php new file mode 100755 index 00000000000..7de7f5c78cd --- /dev/null +++ b/includes/gateways/libs/amazon/IpnHandler.php @@ -0,0 +1,421 @@ + null, + 'proxy_host' => null, + 'proxy_port' => -1, + 'proxy_username' => null, + 'proxy_password' => null); + + + public function __construct($headers, $body, $ipnConfig = null) + { + $this->headers = array_change_key_case($headers, CASE_LOWER); + $this->body = $body; + + if ($ipnConfig != null) { + $this->checkConfigKeys($ipnConfig); + } + + // Get the list of fields that we are interested in + $this->fields = array( + "Timestamp" => true, + "Message" => true, + "MessageId" => true, + "Subject" => false, + "TopicArn" => true, + "Type" => true + ); + + // Validate the IPN message header [x-amz-sns-message-type] + $this->validateHeaders(); + + // Converts the IPN [Message] to Notification object + $this->getMessage(); + + // Checks if the notification [Type] is Notification and constructs the signature fields + $this->checkForCorrectMessageType(); + + // Verifies the signature against the provided pem file in the IPN + $this->constructAndVerifySignature(); + } + + private function checkConfigKeys($ipnConfig) + { + $ipnConfig = array_change_key_case($ipnConfig, CASE_LOWER); + $ipnConfig = trimArray($ipnConfig); + + foreach ($ipnConfig as $key => $value) { + if (array_key_exists($key, $this->ipnConfig)) { + $this->ipnConfig[$key] = $value; + } else { + throw new \Exception('Key ' . $key . ' is either not part of the configuration or has incorrect Key name. + check the ipnConfig array key names to match your key names of your config array ', 1); + } + } + } + + /* Setter function + * Sets the value for the key if the key exists in ipnConfig + */ + + public function __set($name, $value) + { + if (array_key_exists(strtolower($name), $this->ipnConfig)) { + $this->ipnConfig[$name] = $value; + } else { + throw new \Exception("Key " . $name . " is not part of the configuration", 1); + } + } + + /* Getter function + * Returns the value for the key if the key exists in ipnConfig + */ + + public function __get($name) + { + if (array_key_exists(strtolower($name), $this->ipnConfig)) { + return $this->ipnConfig[$name]; + } else { + throw new \Exception("Key " . $name . " was not found in the configuration", 1); + } + } + + /* Trim the input Array key values */ + + private function trimArray($array) + { + foreach ($array as $key => $value) + { + $array[$key] = trim($value); + } + return $array; + } + + private function validateHeaders() + { + // Quickly check that this is a sns message + if (!array_key_exists('x-amz-sns-message-type', $this->headers)) { + throw new \Exception("Error with message - header " . "does not contain x-amz-sns-message-type header"); + } + + if ($this->headers['x-amz-sns-message-type'] !== 'Notification') { + throw new \Exception("Error with message - header x-amz-sns-message-type is not " . "Notification, is " . $this->headers['x-amz-sns-message-type']); + } + } + + private function getMessage() + { + $this->snsMessage = json_decode($this->body, true); + + $json_error = json_last_error(); + + if ($json_error != 0) { + $errorMsg = "Error with message - content is not in json format" . $this->getErrorMessageForJsonError($json_error) . " " . $this->snsMessage; + throw new \Exception($errorMsg); + } + } + + /* Convert a json error code to a descriptive error message + * + * @param int $json_error message code + * + * @return string error message + */ + + private function getErrorMessageForJsonError($json_error) + { + switch ($json_error) { + case JSON_ERROR_DEPTH: + return " - maximum stack depth exceeded."; + break; + case JSON_ERROR_STATE_MISMATCH: + return " - invalid or malformed JSON."; + break; + case JSON_ERROR_CTRL_CHAR: + return " - control character error."; + break; + case JSON_ERROR_SYNTAX: + return " - syntax error."; + break; + default: + return "."; + break; + } + } + + /* checkForCorrectMessageType() + * + * Checks if the Field [Type] is set to ['Notification'] + * Gets the value for the fields marked true in the fields array + * Constructs the signature string + */ + + private function checkForCorrectMessageType() + { + $type = $this->getMandatoryField("Type"); + if (strcasecmp($type, "Notification") != 0) { + throw new \Exception("Error with SNS Notification - unexpected message with Type of " . $type); + } + + if (strcmp($this->getMandatoryField("Type"), "Notification") != 0) { + throw new \Exception("Error with signature verification - unable to verify " . $this->getMandatoryField("Type") . " message"); + } else { + + // Sort the fields into byte order based on the key name(A-Za-z) + ksort($this->fields); + + // Extract the key value pairs and sort in byte order + $signatureFields = array(); + foreach ($this->fields as $fieldName => $mandatoryField) { + if ($mandatoryField) { + $value = $this->getMandatoryField($fieldName); + } else { + $value = $this->getField($fieldName); + } + + if (!is_null($value)) { + array_push($signatureFields, $fieldName); + array_push($signatureFields, $value); + } + } + + /* Create the signature string - key / value in byte order + * delimited by newline character + ending with a new line character + */ + $this->signatureFields = implode("\n", $signatureFields) . "\n"; + + } + } + + /* Verify that the signature is correct for the given data and + * public key + * + * @param string $data data to validate + * @param string $signature decoded signature to compare against + * @param string $certificatePath path to certificate, can be file or url + * + * @throws Exception if there is an error with the call + * + * @return bool true if valid + */ + + private function constructAndVerifySignature() + { + $signature = base64_decode($this->getMandatoryField("Signature")); + $certificatePath = $this->getMandatoryField("SigningCertURL"); + + $this->certificate = $this->getCertificate($certificatePath); + + $result = $this->verifySignatureIsCorrectFromCertificate($signature); + if (!$result) { + throw new \Exception("Unable to match signature from remote server: signature of " . $this->getCertificate($certificatePath) . " , SigningCertURL of " . $this->getMandatoryField("SigningCertURL") . " , SignatureOf " . $this->getMandatoryField("Signature")); + } + } + + /* getCertificate($certificatePath) + * + * gets the certificate from the $certificatePath using Curl + */ + + private function getCertificate($certificatePath) + { + $httpCurlRequest = new HttpCurl($this->ipnConfig); + + $response = $httpCurlRequest->httpGet($certificatePath); + + return $response; + } + + /* Verify that the signature is correct for the given data and public key + * + * @param string $data data to validate + * @param string $signature decoded signature to compare against + * @param string $certificate certificate object defined in Certificate.php + */ + + public function verifySignatureIsCorrectFromCertificate($signature) + { + $certKey = openssl_get_publickey($this->certificate); + + if ($certKey === False) { + throw new \Exception("Unable to extract public key from cert"); + } + + try { + $certInfo = openssl_x509_parse($this->certificate, true); + $certSubject = $certInfo["subject"]; + + if (is_null($certSubject)) { + throw new \Exception("Error with certificate - subject cannot be found"); + } + } catch (\Exception $ex) { + throw new \Exception("Unable to verify certificate - error with the certificate subject", null, $ex); + } + + if (strcmp($certSubject["CN"], $this->expectedCnName)) { + throw new \Exception("Unable to verify certificate issued by Amazon - error with certificate subject"); + } + + $result = -1; + try { + $result = openssl_verify($this->signatureFields, $signature, $certKey, OPENSSL_ALGO_SHA1); + } catch (\Exception $ex) { + throw new \Exception("Unable to verify signature - error with the verification algorithm", null, $ex); + } + + return ($result > 0); + } + + + /* Extract the mandatory field from the message and return the contents + * + * @param string $fieldName name of the field to extract + * + * @throws Exception if not found + * + * @return string field contents if found + */ + + private function getMandatoryField($fieldName) + { + $value = $this->getField($fieldName); + if (is_null($value)) { + throw new \Exception("Error with json message - mandatory field " . $fieldName . " cannot be found"); + } + return $value; + } + + /* Extract the field if present, return null if not defined + * + * @param string $fieldName name of the field to extract + * + * @return string field contents if found, null otherwise + */ + + private function getField($fieldName) + { + if (array_key_exists($fieldName, $this->snsMessage)) { + return $this->snsMessage[$fieldName]; + } else { + return null; + } + } + + /* returnMessage() - JSON decode the raw [Message] portion of the IPN */ + + public function returnMessage() + { + return json_decode($this->snsMessage['Message'], true); + } + + /* toJson() - Converts IPN [Message] field to JSON + * + * Has child elements + * ['NotificationData'] [XML] - API call XML notification data + * @param remainingFields - consists of remaining IPN array fields that are merged + * Type - Notification + * MessageId - ID of the Notification + * Topic ARN - Topic of the IPN + * @return response in JSON format + */ + + public function toJson() + { + $response = $this->simpleXmlObject(); + + // Merging the remaining fields with the response + $remainingFields = $this->getRemainingIpnFields(); + $responseArray = array_merge($remainingFields,(array)$response); + + // Converting to JSON format + $response = json_encode($responseArray); + + return $response; + } + + /* toArray() - Converts IPN [Message] field to associative array + * @return response in array format + */ + + public function toArray() + { + $response = $this->simpleXmlObject(); + + // Converting the SimpleXMLElement Object to array() + $response = json_encode($response); + $response = json_decode($response, true); + + // Merging the remaining fields with the response array + $remainingFields = $this->getRemainingIpnFields(); + $response = array_merge($remainingFields,$response); + + return $response; + } + + /* addRemainingFields() - Add remaining fields to the datatype + * + * Has child elements + * ['NotificationData'] [XML] - API call XML response data + * Convert to SimpleXML element object + * Type - Notification + * MessageId - ID of the Notification + * Topic ARN - Topic of the IPN + * @return response in array format + */ + + private function simpleXmlObject() + { + $ipnMessage = $this->returnMessage(); + + // Getting the Simple XML element object of the IPN XML Response Body + $response = simplexml_load_string((string) $ipnMessage['NotificationData']); + + // Adding the Type, MessageId, TopicArn details of the IPN to the Simple XML element Object + $response->addChild('Type', $this->snsMessage['Type']); + $response->addChild('MessageId', $this->snsMessage['MessageId']); + $response->addChild('TopicArn', $this->snsMessage['TopicArn']); + + return $response; + } + + /* getRemainingIpnFields() + * Gets the remaining fields of the IPN to be later appended to the return message + */ + + private function getRemainingIpnFields() + { + $ipnMessage = $this->returnMessage(); + + $remainingFields = array( + 'NotificationReferenceId' =>$ipnMessage['NotificationReferenceId'], + 'NotificationType' =>$ipnMessage['NotificationType'], + 'IsSample' =>$ipnMessage['IsSample'], + 'SellerId' =>$ipnMessage['SellerId'], + 'ReleaseEnvironment' =>$ipnMessage['ReleaseEnvironment'], + 'Version' =>$ipnMessage['Version']); + + return $remainingFields; + } +} diff --git a/includes/gateways/libs/amazon/ResponseParser.php b/includes/gateways/libs/amazon/ResponseParser.php new file mode 100755 index 00000000000..92a2e15a53a --- /dev/null +++ b/includes/gateways/libs/amazon/ResponseParser.php @@ -0,0 +1,86 @@ +response = $response; + } + + /* Returns the XML portion of the response */ + + public function toXml() + { + return $this->response['ResponseBody']; + } + + /* toJson - converts XML into Json + * @param $response [XML] + */ + + public function toJson() + { + $response = $this->simpleXmlObject(); + + return (json_encode($response)); + } + + /* toArray - converts XML into associative array + * @param $this->response [XML] + */ + + public function toArray() + { + $response = $this->simpleXmlObject(); + + // Converting the SimpleXMLElement Object to array() + $response = json_encode($response); + + return (json_decode($response, true)); + } + + private function simpleXmlObject() + { + $response = $this->response; + + // Getting the HttpResponse Status code to the output as a string + $status = strval($response['Status']); + + // Getting the Simple XML element object of the XML Response Body + $response = simplexml_load_string((string) $response['ResponseBody']); + + // Adding the HttpResponse Status code to the output as a string + $response->addChild('ResponseStatus', $status); + + return $response; + } + + /* Get the status of the BillingAgreement */ + + public function getBillingAgreementDetailsStatus($response) + { + $data= new \SimpleXMLElement($response); + $namespaces = $data->getNamespaces(true); + foreach($namespaces as $key=>$value){ + $namespace = $value; + } + $data->registerXPathNamespace('GetBA', $namespace); + foreach ($data->xpath('//GetBA:BillingAgreementStatus') as $value) { + $baStatus = json_decode(json_encode((array)$value), TRUE); + } + + return $baStatus ; + } +} diff --git a/includes/gateways/manual.php b/includes/gateways/manual.php index 388c1523bdc..fcaa023e428 100755 --- a/includes/gateways/manual.php +++ b/includes/gateways/manual.php @@ -2,48 +2,44 @@ /** * Manual Gateway * - * @package Easy Digital Downloads - * @subpackage Manual Gateway - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Gateways + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** - * Manual Remove CC Form - * - * Manual does not need a CC form, so remove it. + * Manual Gateway does not need a CC form, so remove it. * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_manual_remove_cc_form() { - // we only register the action so that the default CC form is not shown -} -add_action('edd_manual_cc_form', 'edd_manual_remove_cc_form'); - + * @since 1.0 + * @return void + */ +add_action( 'edd_manual_cc_form', '__return_false' ); /** - * Manual Payment + * Processes the purchase data and uses the Manual Payment gateway to record + * the transaction in the Purchase History * - * @access private - * @since 1.0 - * @return void + * @since 1.0 + * @param array $purchase_data Purchase Data + * @return void */ +function edd_manual_payment( $purchase_data ) { + if( ! wp_verify_nonce( $purchase_data['gateway_nonce'], 'edd-gateway' ) ) { + wp_die( __( 'Nonce verification has failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } -function edd_manual_payment($purchase_data) { - global $edd_options; - - /* - * purchase data comes in like this + /* + * Purchase data comes in like this * $purchase_data = array( 'downloads' => array of download IDs, 'price' => total price of cart contents, - 'purchase_key' => // random key + 'purchase_key' => // Random key 'user_email' => $user_email, 'date' => date('Y-m-d H:i:s'), 'user_id' => $user_id, @@ -52,30 +48,31 @@ function edd_manual_payment($purchase_data) { 'cart_details' => array of cart details, ); */ - - $payment = array( - 'price' => $purchase_data['price'], - 'date' => $purchase_data['date'], - 'user_email' => $purchase_data['user_email'], + + $payment_data = array( + 'price' => $purchase_data['price'], + 'date' => $purchase_data['date'], + 'user_email' => $purchase_data['user_email'], 'purchase_key' => $purchase_data['purchase_key'], - 'currency' => $edd_options['currency'], - 'downloads' => $purchase_data['downloads'], - 'user_info' => $purchase_data['user_info'], + 'currency' => edd_get_currency(), + 'downloads' => $purchase_data['downloads'], + 'user_info' => $purchase_data['user_info'], 'cart_details' => $purchase_data['cart_details'], - 'status' => 'pending' + 'status' => 'pending', ); - - // record the pending payment - $payment = edd_insert_payment($payment); - - if($payment) { - edd_update_payment_status($payment, 'publish'); - // empty the shopping cart + + // Record the pending payment + $payment = edd_insert_payment( $payment_data ); + + if ( $payment ) { + edd_update_payment_status( $payment, 'complete' ); + // Empty the shopping cart edd_empty_cart(); edd_send_to_success_page(); } else { - // if errors are present, send the user back to the purchase page so they can be corrected - edd_send_back_to_checkout('?payment-mode=' . $purchase_data['post_data']['edd-gateway']); + edd_record_gateway_error( __( 'Payment Error', 'easy-digital-downloads' ), sprintf( __( 'Payment creation failed while processing a manual (free or test) purchase. Payment data: %s', 'easy-digital-downloads' ), json_encode( $payment_data ) ), $payment ); + // If errors are present, send the user back to the purchase page so they can be corrected + edd_send_back_to_checkout( '?payment-mode=' . $purchase_data['post_data']['edd-gateway'] ); } } -add_action('edd_gateway_manual', 'edd_manual_payment'); \ No newline at end of file +add_action( 'edd_gateway_manual', 'edd_manual_payment' ); diff --git a/includes/gateways/paypal-standard.php b/includes/gateways/paypal-standard.php old mode 100644 new mode 100755 index 4bfb188d08b..aefe248b30d --- a/includes/gateways/paypal-standard.php +++ b/includes/gateways/paypal-standard.php @@ -2,333 +2,1379 @@ /** * PayPal Standard Gateway * - * @package Easy Digital Downloads - * @subpackage PayPal Standard Gateway - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Gateways + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ +use EDD\Orders\Order; + +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** * PayPal Remove CC Form * * PayPal Standard does not need a CC form, so remove it. * - * @access private - * @since 1.0 - * @return void -*/ + * @access private + * @since 1.0 + */ +add_action( 'edd_paypal_cc_form', '__return_false' ); -function edd_paypal_remove_cc_form() { - // we only register the action so that the default CC form is not shown +/** + * Register the PayPal Standard gateway subsection + * + * @since 2.6 + * @param array $gateway_sections Current Gateway Tab subsections + * @return array Gateway subsections with PayPal Standard + */ +function edd_register_paypal_gateway_section( $gateway_sections ) { + if ( \EDD\Gateways\PayPal\paypal_standard_enabled() ) { + $gateway_sections['paypal'] = __( 'PayPal Standard', 'easy-digital-downloads' ); + } + + return $gateway_sections; } -add_action( 'edd_paypal_cc_form', 'edd_paypal_remove_cc_form' ); +add_filter( 'edd_settings_sections_gateways', 'edd_register_paypal_gateway_section', 1, 1 ); + +/** + * Registers the PayPal Standard settings for the PayPal Standard subsection + * + * @since 2.6 + * @param array $gateway_settings Gateway tab settings + * @return array Gateway tab settings with the PayPal Standard settings + */ +function edd_register_paypal_gateway_settings( $gateway_settings ) { + if ( ! \EDD\Gateways\PayPal\paypal_standard_enabled() ) { + return $gateway_settings; + } + + $paypal_settings = array( + 'paypal_email' => array( + 'id' => 'paypal_email', + 'name' => __( 'PayPal Email', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your PayPal account\'s email', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + ), + 'paypal_image_url' => array( + 'id' => 'paypal_image_url', + 'name' => __( 'PayPal Image', 'easy-digital-downloads' ), + 'desc' => __( 'Upload an image to display on the PayPal checkout page.', 'easy-digital-downloads' ), + 'type' => 'upload', + 'size' => 'regular', + ), + ); + + $pdt_desc = sprintf( + __( 'Enter your PayPal Identity Token in order to enable Payment Data Transfer (PDT). This allows payments to be verified without relying on the PayPal IPN. See our documentation for further information.', 'easy-digital-downloads' ), + 'https://easydigitaldownloads.com/docs/paypal-legacy-gateways-standard-express-pro-advanced/' + ); + + $paypal_settings['paypal_identify_token'] = array( + 'id' => 'paypal_identity_token', + 'name' => __( 'PayPal Identity Token', 'easy-digital-downloads' ), + 'type' => 'text', + 'desc' => $pdt_desc, + 'size' => 'regular', + ); + + $desc = sprintf( + __( 'If you are unable to use Payment Data Transfer and payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases. See our FAQ for further information.', 'easy-digital-downloads' ), + 'https://easydigitaldownloads.com/docs/paypal-payments-not-marked-as-complete/' + ); + $paypal_settings['disable_paypal_verification'] = array( + 'id' => 'disable_paypal_verification', + 'name' => __( 'Disable PayPal IPN Verification', 'easy-digital-downloads' ), + 'check' => __( 'Disabled', 'easy-digital-downloads' ), + 'desc' => $desc, + 'type' => 'checkbox_description', + ); + + $api_key_settings = array( + 'paypal_api_keys_desc' => array( + 'id' => 'paypal_api_keys_desc', + 'name' => __( 'API Credentials', 'easy-digital-downloads' ), + 'type' => 'descriptive_text', + 'desc' => sprintf( + __( 'API credentials are necessary to process PayPal refunds from inside WordPress. These can be obtained from your PayPal account.', 'easy-digital-downloads' ), + 'https://developer.paypal.com/docs/classic/api/apiCredentials/#creating-an-api-signature' + ) + ), + 'paypal_live_api_username' => array( + 'id' => 'paypal_live_api_username', + 'name' => __( 'Live API Username', 'easy-digital-downloads' ), + 'desc' => __( 'Your PayPal live API username. ', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular' + ), + 'paypal_live_api_password' => array( + 'id' => 'paypal_live_api_password', + 'name' => __( 'Live API Password', 'easy-digital-downloads' ), + 'desc' => __( 'Your PayPal live API password.', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular' + ), + 'paypal_live_api_signature' => array( + 'id' => 'paypal_live_api_signature', + 'name' => __( 'Live API Signature', 'easy-digital-downloads' ), + 'desc' => __( 'Your PayPal live API signature.', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular' + ), + 'paypal_test_api_username' => array( + 'id' => 'paypal_test_api_username', + 'name' => __( 'Test API Username', 'easy-digital-downloads' ), + 'desc' => __( 'Your PayPal test API username.', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular' + ), + 'paypal_test_api_password' => array( + 'id' => 'paypal_test_api_password', + 'name' => __( 'Test API Password', 'easy-digital-downloads' ), + 'desc' => __( 'Your PayPal test API password.', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular' + ), + 'paypal_test_api_signature' => array( + 'id' => 'paypal_test_api_signature', + 'name' => __( 'Test API Signature', 'easy-digital-downloads' ), + 'desc' => __( 'Your PayPal test API signature.', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular' + ) + ); + + $paypal_settings = array_merge( $paypal_settings, $api_key_settings ); + + $paypal_settings = apply_filters( 'edd_paypal_settings', $paypal_settings ); + $gateway_settings['paypal'] = $paypal_settings; + + return $gateway_settings; +} +add_filter( 'edd_settings_gateways', 'edd_register_paypal_gateway_settings', 1, 1 ); /** * Process PayPal Purchase * - * @access private - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @param array $purchase_data Purchase Data + * @return void + */ function edd_process_paypal_purchase( $purchase_data ) { - global $edd_options; - - // check there is a gateway name - if ( ! isset( $purchase_data['post_data']['edd-gateway'] ) ) - return; - - /* - Purchase data comes in like this: - //////////////////////////////// - - $purchase_data = array( - 'downloads' => array of download IDs, - 'price' => total price of cart contents, - 'purchase_key' => // random key - 'user_email' => $user_email, - 'date' => date( 'Y-m-d H:i:s' ), - 'user_id' => $user_id, - 'post_data' => $_POST, - 'user_info' => array of user's information and used discount code - 'cart_details' => array of cart details, - ); - */ - - // collect payment data - $payment_data = array( - 'price' => $purchase_data['price'], - 'date' => $purchase_data['date'], - 'user_email' => $purchase_data['user_email'], - 'purchase_key' => $purchase_data['purchase_key'], - 'currency' => $edd_options['currency'], - 'downloads' => $purchase_data['downloads'], - 'user_info' => $purchase_data['user_info'], - 'cart_details' => $purchase_data['cart_details'], - 'status' => 'pending' - ); - - // record the pending payment - $payment = edd_insert_payment( $payment_data ); - - // check payment - if ( ! $payment ) { - // problems? send back - edd_send_back_to_checkout( '?payment-mode=' . $purchase_data['post_data']['edd-gateway'] ); - } else { - // only send to PayPal if the pending payment is created successfully - $listener_url = trailingslashit( home_url() ).'?edd-listener=IPN'; - - // get the success url - $return_url = add_query_arg( 'payment-confirmation', 'paypal', get_permalink( $edd_options['success_page'] ) ); - - // get the complete cart cart_summary - $summary = edd_get_purchase_summary( $purchase_data, false ); - - // get the PayPal redirect uri - $paypal_redirect = trailingslashit( edd_get_paypal_redirect() ) . '?'; - - // setup PayPal arguments - $paypal_args = array( - 'cmd' => '_xclick', - 'amount' => $purchase_data['price'], - 'business' => $edd_options['paypal_email'], - 'item_name' => stripslashes_deep( html_entity_decode( $summary, ENT_COMPAT, 'UTF-8' ) ), - 'email' => $purchase_data['user_email'], - 'no_shipping' => '1', - 'shipping' => '0', - 'no_note' => '1', - 'currency_code' => $edd_options['currency'], - 'item_number' => $purchase_data['purchase_key'], - 'charset' => get_bloginfo( 'charset' ), - 'custom' => $payment, - 'rm' => '2', - 'return' => $return_url, - 'notify_url' => $listener_url - ); - - // build query - $paypal_redirect .= http_build_query( apply_filters('edd_paypal_redirect_args', $paypal_args, $purchase_data ) ); - - // get rid of cart contents - edd_empty_cart(); - - // Redirect to PayPal - wp_redirect( $paypal_redirect ); - exit; - } - + if ( ! wp_verify_nonce( $purchase_data['gateway_nonce'], 'edd-gateway' ) ) { + wp_die( __( 'Nonce verification has failed', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + // Collect payment data + $payment_data = array( + 'price' => $purchase_data['price'], + 'date' => $purchase_data['date'], + 'user_email' => $purchase_data['user_email'], + 'purchase_key' => $purchase_data['purchase_key'], + 'currency' => edd_get_currency(), + 'downloads' => $purchase_data['downloads'], + 'user_info' => $purchase_data['user_info'], + 'cart_details' => $purchase_data['cart_details'], + 'gateway' => 'paypal', + 'status' => ! empty( $purchase_data['buy_now'] ) ? 'private' : 'pending' + ); + + // Record the pending payment + $payment = edd_insert_payment( $payment_data ); + + // Check payment + if ( ! $payment ) { + // Record the error + edd_record_gateway_error( __( 'Payment Error', 'easy-digital-downloads' ), sprintf( __( 'Payment creation failed before sending buyer to PayPal. Payment data: %s', 'easy-digital-downloads' ), json_encode( $payment_data ) ), $payment ); + // Problems? send back + edd_send_back_to_checkout( '?payment-mode=' . $purchase_data['post_data']['edd-gateway'] ); + } else { + // Only send to PayPal if the pending payment is created successfully + $listener_url = add_query_arg( 'edd-listener', 'IPN', home_url( 'index.php' ) ); + + // Set the session data to recover this payment in the event of abandonment or error. + EDD()->session->set( 'edd_resume_payment', $payment ); + + // Get the success url + $return_url = add_query_arg( + array( + 'payment-confirmation' => 'paypal', + 'payment-id' => urlencode( $payment ), + ), + get_permalink( edd_get_option( 'success_page', false ) ) + ); + + // Get the PayPal redirect uri + $paypal_redirect = trailingslashit( edd_get_paypal_redirect() ) . '?'; + + // Setup PayPal arguments + $paypal_args = array( + 'business' => edd_get_option( 'paypal_email', false ), + 'email' => $purchase_data['user_email'], + 'first_name' => $purchase_data['user_info']['first_name'], + 'last_name' => $purchase_data['user_info']['last_name'], + 'invoice' => $purchase_data['purchase_key'], + 'no_shipping' => '1', + 'shipping' => '0', + 'no_note' => '1', + 'currency_code' => edd_get_currency(), + 'charset' => get_bloginfo( 'charset' ), + 'custom' => $payment, + 'rm' => '2', + 'return' => esc_url_raw( $return_url ), + 'cancel_return' => esc_url_raw( edd_get_failed_transaction_uri( '?payment-id=' . sanitize_key( $payment ) ) ), + 'notify_url' => esc_url_raw( $listener_url ), + 'image_url' => esc_url_raw( edd_get_paypal_image_url() ), + 'cbt' => get_bloginfo( 'name' ), + 'bn' => 'EasyDigitalDownloads_SP' + ); + + if ( ! empty( $purchase_data['user_info']['address'] ) ) { + $paypal_args['address1'] = $purchase_data['user_info']['address']['line1']; + $paypal_args['address2'] = $purchase_data['user_info']['address']['line2']; + $paypal_args['city'] = $purchase_data['user_info']['address']['city']; + $paypal_args['country'] = $purchase_data['user_info']['address']['country']; + } + + $paypal_extra_args = array( + 'cmd' => '_cart', + 'upload' => '1' + ); + + $paypal_args = array_merge( $paypal_extra_args, $paypal_args ); + + // Add cart items + $i = 1; + $paypal_sum = 0; + if ( is_array( $purchase_data['cart_details'] ) && ! empty( $purchase_data['cart_details'] ) ) { + foreach ( $purchase_data['cart_details'] as $item ) { + + $item_amount = round( ( $item['subtotal'] / $item['quantity'] ) - ( $item['discount'] / $item['quantity'] ), 2 ); + + if ( $item_amount <= 0 ) { + $item_amount = 0; + } + + $paypal_args['item_name_' . $i ] = stripslashes_deep( html_entity_decode( edd_get_cart_item_name( $item ), ENT_COMPAT, 'UTF-8' ) ); + $paypal_args['quantity_' . $i ] = $item['quantity']; + $paypal_args['amount_' . $i ] = $item_amount; + + if ( edd_use_skus() ) { + $paypal_args['item_number_' . $i ] = edd_get_download_sku( $item['id'] ); + } + + $paypal_sum += ( $item_amount * $item['quantity'] ); + + $i++; + + } + } + + // Calculate discount + $discounted_amount = 0.00; + if ( ! empty( $purchase_data['fees'] ) ) { + $i = empty( $i ) ? 1 : $i; + foreach ( $purchase_data['fees'] as $fee ) { + if ( empty( $fee['download_id'] ) && floatval( $fee['amount'] ) > '0' ) { + // this is a positive fee + $paypal_args['item_name_' . $i ] = stripslashes_deep( html_entity_decode( wp_strip_all_tags( $fee['label'] ), ENT_COMPAT, 'UTF-8' ) ); + $paypal_args['quantity_' . $i ] = '1'; + $paypal_args['amount_' . $i ] = edd_sanitize_amount( $fee['amount'] ); + $i++; + } else if ( empty( $fee['download_id'] ) ) { + + // This is a negative fee (discount) not assigned to a specific Download + $discounted_amount += abs( $fee['amount'] ); + } + } + } + + $price_before_discount = $purchase_data['price']; + if ( $discounted_amount > '0' ) { + $paypal_args['discount_amount_cart'] = edd_sanitize_amount( $discounted_amount ); + + /* + * Add the discounted amount back onto the price to get the "price before discount". We do this + * to avoid double applying any discounts below. + * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6837 + */ + $price_before_discount += $paypal_args['discount_amount_cart']; + } + + // Check if there are any additional discounts we need to add that we haven't already accounted for. + if( $paypal_sum > $price_before_discount ) { + $difference = round( $paypal_sum - $price_before_discount, 2 ); + if ( ! isset( $paypal_args['discount_amount_cart'] ) ) { + $paypal_args['discount_amount_cart'] = 0; + } + $paypal_args['discount_amount_cart'] += $difference; + } + + // Add taxes to the cart + if ( edd_use_taxes() ) { + + $paypal_args['tax_cart'] = edd_sanitize_amount( $purchase_data['tax'] ); + + } + + $paypal_args = apply_filters( 'edd_paypal_redirect_args', $paypal_args, $purchase_data ); + + edd_debug_log( 'PayPal arguments: ' . print_r( $paypal_args, true ) ); + + // Build query + $paypal_redirect .= http_build_query( $paypal_args ); + + // Fix for some sites that encode the entities + $paypal_redirect = str_replace( '&', '&', $paypal_redirect ); + + // Allow paypal as a redirect destination. + add_filter( 'allowed_redirect_hosts', 'edd_allow_redirect_to_paypal', 10 ); + + // Redirect to PayPal. + edd_redirect( $paypal_redirect ); + } } add_action( 'edd_gateway_paypal', 'edd_process_paypal_purchase' ); - /** - * Listen For PayPal IPN + * Add paypal.com to the list of allowed hosts that wp_safe_redirect can redirect to. * - * Listens for a PayPal IPN requests and then sends to the processing function. - * - * @access private - * @since 1.0 - * @return void -*/ + * @since 3.0 + * @param array $redirects - The list of urls that wp_safe_redirect can redirect to. + * @return array + */ +function edd_allow_redirect_to_paypal( $redirects ) { + $redirects[] = 'www.sandbox.paypal.com'; + $redirects[] = 'sandbox.paypal.com'; + $redirects[] = 'www.paypal.com'; + $redirects[] = 'paypal.com'; + return $redirects; +} +/** + * Listens for a PayPal IPN requests and then sends to the processing function + * + * @since 1.0 + * @return void + */ function edd_listen_for_paypal_ipn() { - global $edd_options; - - - // regular PayPal IPN - if ( ! isset( $edd_options['paypal_alternate_verification'] ) ) { - - if ( isset( $_GET['edd-listener'] ) && $_GET['edd-listener'] == 'IPN' ) { - do_action( 'edd_verify_paypal_ipn' ); - } - - // alternate purchase verification - } else { - - if ( isset( $_GET['tx'] ) && isset( $_GET['st'] ) && isset( $_GET['amt'] ) && isset( $_GET['cc'] ) && isset( $_GET['cm'] ) && isset( $_GET['item_number'] ) ) { - // we are using the alternate method of verifying PayPal purchases - - // setup each of the variables from PayPal - $payment_status = $_GET['st']; - $paypal_amount = $_GET['amt']; - $payment_id = $_GET['cm']; - $purchase_key = $_GET['item_number']; - $currency = $_GET['cc']; - - // retrieve the meta info for this payment - $payment_meta = get_post_meta( $payment_id, '_edd_payment_meta', true ); - $payment_amount = edd_format_amount( $payment_meta['amount'] ); - - if ( $currency != $edd_options['currency'] ) { - return; // the currency code is invalid - } - if ( number_format((float)$paypal_amount, 2) != $payment_amount ) { - return; // the prices don't match - } - if ( $purchase_key != $payment_meta['key'] ) { - return; // purchase keys don't match - } - if ( strtolower( $payment_status ) != 'completed' || edd_is_test_mode() ) { - return; // payment wasn't completed - } - - // everything has been verified, update the payment to "complete" - edd_update_payment_status( $payment_id, 'publish' ); - } - } + // Regular PayPal IPN + if ( isset( $_GET['edd-listener'] ) && 'ipn' === strtolower( $_GET['edd-listener'] ) ) { + + edd_debug_log( 'PayPal IPN endpoint loaded' ); + + /** + * This is necessary to delay execution of PayPal PDT and to avoid a race condition causing the order status + * updates to be triggered twice. + * + * @since 2.9.4 + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6605 + */ + $token = edd_get_option( 'paypal_identity_token' ); + if ( $token ) { + sleep( 5 ); + } + + do_action( 'edd_verify_paypal_ipn' ); + } } add_action( 'init', 'edd_listen_for_paypal_ipn' ); - /** * Process PayPal IPN * - * @access private - * @since 1.0 - * @return void -*/ - + * @since 1.0 + * @return void + */ function edd_process_paypal_ipn() { - global $edd_options; - - // check the request method is POST - if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] != 'POST' ) { - return; - } - - // set initial post data to false - $post_data = false; - - // fallback just in case post_max_size is lower than needed - if ( ini_get( 'allow_url_fopen' ) ) { - $post_data = file_get_contents( 'php://input' ); + // Check the request method is POST + if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] != 'POST' ) { + return; + } + + edd_debug_log( 'edd_process_paypal_ipn() running during PayPal IPN processing' ); + + // Set initial post data to empty string + $post_data = ''; + + // Fallback just in case post_max_size is lower than needed + if ( ini_get( 'allow_url_fopen' ) ) { + $post_data = file_get_contents( 'php://input' ); + } else { + // If allow_url_fopen is not enabled, then make sure that post_max_size is large enough + ini_set( 'post_max_size', '12M' ); + } + // Start the encoded data collection with notification command + $encoded_data = 'cmd=_notify-validate'; + + // Get current arg separator + $arg_separator = edd_get_php_arg_separator_output(); + + // Verify there is a post_data + if ( $post_data || strlen( $post_data ) > 0 ) { + // Append the data + $encoded_data .= $arg_separator . $post_data; + } else { + // Check if POST is empty + if ( empty( $_POST ) ) { + // Nothing to do + return; + } else { + // Loop through each POST + foreach ( $_POST as $key => $value ) { + // Encode the value and append the data + $encoded_data .= $arg_separator . "$key=" . urlencode( $value ); + } + } + } + + // Convert collected post data to an array + parse_str( $encoded_data, $encoded_data_array ); + + foreach ( $encoded_data_array as $key => $value ) { + + if ( false !== strpos( $key, 'amp;' ) ) { + $new_key = str_replace( '&', '&', $key ); + $new_key = str_replace( 'amp;', '&', $new_key ); + + unset( $encoded_data_array[ $key ] ); + $encoded_data_array[ $new_key ] = $value; + } + + } + + /** + * PayPal Web IPN Verification + * + * Allows filtering the IPN Verification data that PayPal passes back in via IPN with PayPal Standard + * + * @since 2.8.13 + * + * @param array $data The PayPal Web Accept Data + */ + $encoded_data_array = apply_filters( 'edd_process_paypal_ipn_data', $encoded_data_array ); + + edd_debug_log( 'encoded_data_array data array: ' . print_r( $encoded_data_array, true ) ); + + if ( ! edd_get_option( 'disable_paypal_verification' ) ) { + + // Validate the IPN + + $remote_post_vars = array( + 'method' => 'POST', + 'timeout' => 45, + 'redirection' => 5, + 'httpversion' => '1.1', + 'blocking' => true, + 'headers' => array( + 'host' => 'www.paypal.com', + 'connection' => 'close', + 'content-type' => 'application/x-www-form-urlencoded', + 'post' => '/cgi-bin/webscr HTTP/1.1', + 'user-agent' => 'EDD IPN Verification/' . EDD_VERSION . '; ' . get_bloginfo( 'url' ) + + ), + 'sslverify' => false, + 'body' => $encoded_data_array + ); + + edd_debug_log( 'Attempting to verify PayPal IPN. Data sent for verification: ' . print_r( $remote_post_vars, true ) ); + + // Get response + $api_response = wp_remote_post( edd_get_paypal_redirect( true, true ), $remote_post_vars ); + + if ( is_wp_error( $api_response ) ) { + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $api_response ) ) ); + edd_debug_log( 'Invalid IPN verification response. IPN data: ' . print_r( $api_response, true ) ); + + return; // Something went wrong + } + + if ( wp_remote_retrieve_body( $api_response ) !== 'VERIFIED' && edd_get_option( 'disable_paypal_verification', false ) ) { + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $api_response ) ) ); + edd_debug_log( 'Invalid IPN verification response. IPN data: ' . print_r( $api_response, true ) ); + + return; // Response not okay + } + + edd_debug_log( 'IPN verified successfully' ); + } + + // Check if $post_data_array has been populated + if ( ! is_array( $encoded_data_array ) && ! empty( $encoded_data_array ) ) { + return; + } + + $defaults = array( + 'txn_type' => '', + 'payment_status' => '' + ); + + $encoded_data_array = wp_parse_args( $encoded_data_array, $defaults ); + + $payment_id = 0; + + if ( ! empty( $encoded_data_array[ 'parent_txn_id' ] ) ) { + $payment_id = edd_get_purchase_id_by_transaction_id( $encoded_data_array[ 'parent_txn_id' ] ); + } elseif ( ! empty( $encoded_data_array[ 'txn_id' ] ) ) { + $payment_id = edd_get_purchase_id_by_transaction_id( $encoded_data_array[ 'txn_id' ] ); + } + + if ( empty( $payment_id ) ) { + $payment_id = ! empty( $encoded_data_array[ 'custom' ] ) ? absint( $encoded_data_array[ 'custom' ] ) : 0; + } + + if ( has_action( 'edd_paypal_' . $encoded_data_array['txn_type'] ) ) { + // Allow PayPal IPN types to be processed separately + do_action( 'edd_paypal_' . $encoded_data_array['txn_type'], $encoded_data_array, $payment_id ); } else { - // if allow_url_fopen is not enabled, then make sure that post_max_size is large enough - ini_set('post_max_size', '12M'); - } - // start the encoded data collection with notification command - $encoded_data = 'cmd=_notify-validate'; - - // get current arg separator - $arg_separator = edd_get_php_arg_separator_output(); - - // verify there is a post_data - if ( $post_data || strlen( $post_data ) > 0 ) { - // append the data - $encoded_data .= $arg_separator.$post_data; - } else { - // check if POST is empty - if ( empty( $_POST ) ) { - // nothing to do - return; - } else { - // loop trough each POST - foreach ( $_POST as $key => $value ) { - // encode the value and append the data - $encoded_data .= $arg_separator."$key=" . urlencode( $value ); - } - } - } - - // convert collected post data to an array - parse_str( $encoded_data, $encoded_data_array ); - - // get the PayPal redirect uri - $paypal_redirect = edd_get_paypal_redirect(true); - - $remote_post_vars = array( - 'method' => 'POST', - 'timeout' => 45, - 'redirection' => 5, - 'httpversion' => '1.0', - 'blocking' => true, - 'headers' => array(), - 'body' => $encoded_data_array - ); - - // get response - $api_response = wp_remote_post( edd_get_paypal_redirect(), $remote_post_vars ); - - if( is_wp_error( $api_response) ) - return; // something went wrong - - if ($api_response['body'] !== 'VERIFIED' && !isset($edd_options['disable_paypal_verification']) ) - return; // response not okay - - // convert collected post data to an array - parse_str( $post_data, $post_data_array ); - - // check if $post_data_array has been populated - if ( ! is_array( $encoded_data_array ) && ! empty( $encoded_data_array ) ) - return; - - // collect payment details - $payment_id = $encoded_data_array['custom']; - $purchase_key = $encoded_data_array['item_number']; - $paypal_amount = $encoded_data_array['mc_gross']; - $payment_status = $encoded_data_array['payment_status']; - $currency_code = strtolower( $encoded_data_array['mc_currency'] ); - - // retrieve the meta info for this payment - $payment_meta = get_post_meta( $payment_id, '_edd_payment_meta', true ); - $payment_amount = edd_format_amount( $payment_meta['amount'] ); - - // verify details - if ( $currency_code != strtolower( $edd_options['currency'] ) ) { - // the currency code is invalid - return; - } - if ( number_format((float)$paypal_amount, 2) != $payment_amount ) { - // the prices don't match - //return; - } - if ( $purchase_key != $payment_meta['key'] ) { - // purchase keys don't match - return; - } - - if ( isset( $encoded_data_array['txn_type'] ) && $encoded_data_array['txn_type'] == 'web_accept' ) { - - $status = strtolower( $payment_status ); - - if ( $status == 'completed' || edd_is_test_mode() ) { - edd_update_payment_status( $payment_id, 'publish' ); - } - - } + // Fallback to web accept just in case the txn_type isn't present + do_action( 'edd_paypal_web_accept', $encoded_data_array, $payment_id ); + } + exit; } add_action( 'edd_verify_paypal_ipn', 'edd_process_paypal_ipn' ); +/** + * Process web accept (one time) payment IPNs + * + * @since 1.3.4 + * @param array $data IPN Data + * @param int $payment_id Payment ID + * @return void + */ +function edd_process_paypal_web_accept_and_cart( $data, $payment_id ) { + + /** + * PayPal Web Accept Data + * + * Allows filtering the Web Accept data that PayPal passes back in via IPN with PayPal Standard + * + * @since 2.8.13 + * + * @param array $data The PayPal Web Accept Data + * @param int $payment_id The Payment ID associated with this IPN request + */ + $data = apply_filters( 'edd_paypal_web_accept_and_cart_data', $data, $payment_id ); + + if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && $data['payment_status'] != 'Refunded' ) { + return; + } + + if ( empty( $payment_id ) ) { + return; + } + + $payment = new EDD_Payment( $payment_id ); + + // Collect payment details + $purchase_key = isset( $data['invoice'] ) ? $data['invoice'] : false; + if ( ! $purchase_key && ! empty( $data['item_number'] ) ) { + $purchase_key = $data['item_number']; + } + $paypal_amount = $data['mc_gross']; + $payment_status = strtolower( $data['payment_status'] ); + $currency_code = strtolower( $data['mc_currency'] ); + $business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] ); + + + if ( $payment->gateway != 'paypal' ) { + return; // this isn't a PayPal standard IPN + } + + // Verify payment recipient + if ( strcasecmp( $business_email, trim( edd_get_option( 'paypal_email', false ) ) ) != 0 ) { + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid business email in IPN response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $data ) ), $payment_id ); + edd_debug_log( 'Invalid business email in IPN response. IPN data: ' . print_r( $data, true ) ); + edd_update_payment_status( $payment_id, 'failed' ); + edd_insert_payment_note( $payment_id, __( 'Payment failed due to invalid PayPal business email.', 'easy-digital-downloads' ) ); + return; + } + + // Verify payment currency + if ( $currency_code != strtolower( $payment->currency ) ) { + + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid currency in IPN response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $data ) ), $payment_id ); + edd_debug_log( 'Invalid currency in IPN response. IPN data: ' . print_r( $data, true ) ); + edd_update_payment_status( $payment_id, 'failed' ); + edd_insert_payment_note( $payment_id, __( 'Payment failed due to invalid currency in PayPal IPN.', 'easy-digital-downloads' ) ); + return; + } + + if ( empty( $payment->email ) ) { + + // This runs when a Buy Now purchase was made. It bypasses checkout so no personal info is collected until PayPal + + // Setup and store the customers's details + $address = array(); + $address['line1'] = ! empty( $data['address_street'] ) ? sanitize_text_field( $data['address_street'] ) : false; + $address['city'] = ! empty( $data['address_city'] ) ? sanitize_text_field( $data['address_city'] ) : false; + $address['state'] = ! empty( $data['address_state'] ) ? sanitize_text_field( $data['address_state'] ) : false; + $address['country'] = ! empty( $data['address_country_code'] ) ? sanitize_text_field( $data['address_country_code'] ) : false; + $address['zip'] = ! empty( $data['address_zip'] ) ? sanitize_text_field( $data['address_zip'] ) : false; + + $payment->email = sanitize_text_field( $data['payer_email'] ); + $payment->first_name = sanitize_text_field( $data['first_name'] ); + $payment->last_name = sanitize_text_field( $data['last_name'] ); + $payment->address = $address; + + if ( empty( $payment->customer_id ) ) { + + $customer = new EDD_Customer( $payment->email ); + if ( ! $customer || $customer->id < 1 ) { + + $customer->create( array( + 'email' => $payment->email, + 'name' => $payment->first_name . ' ' . $payment->last_name, + 'user_id' => $payment->user_id + ) ); + + } + + $payment->customer_id = $customer->id; + } + + $payment->save(); + + } + + if( empty( $customer ) ) { + + $customer = new EDD_Customer( $payment->customer_id ); + + } + + // Record the payer email on the EDD_Customer record if it is different than the email entered on checkout + if( ! empty( $data['payer_email'] ) && ! in_array( strtolower( $data['payer_email'] ), array_map( 'strtolower', $customer->emails ) ) ) { + + $customer->add_email( strtolower( $data['payer_email'] ) ); + + } + + if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) { + + // Process a refund + edd_process_paypal_refund( $data, $payment_id ); + + } else { + + if ( edd_get_payment_status( $payment_id ) == 'complete' ) { + return; // Only complete payments once + } + + // Retrieve the total purchase amount (before PayPal) + $payment_amount = edd_get_payment_amount( $payment_id ); + + if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) { + // The prices don't match + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid payment amount in IPN response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $data ) ), $payment_id ); + edd_debug_log( 'Invalid payment amount in IPN response. IPN data: ' . printf( $data, true ) ); + edd_update_payment_status( $payment_id, 'failed' ); + edd_insert_payment_note( $payment_id, __( 'Payment failed due to invalid amount in PayPal IPN.', 'easy-digital-downloads' ) ); + return; + } + if ( $purchase_key != edd_get_payment_key( $payment_id ) ) { + // Purchase keys don't match + edd_debug_log( 'Invalid purchase key in IPN response. IPN data: ' . printf( $data, true ) ); + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid purchase key in IPN response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $data ) ), $payment_id ); + edd_update_payment_status( $payment_id, 'failed' ); + edd_insert_payment_note( $payment_id, __( 'Payment failed due to invalid purchase key in PayPal IPN.', 'easy-digital-downloads' ) ); + return; + } + + if ( 'completed' == $payment_status || edd_is_test_mode() ) { + + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ) , $data['txn_id'] ) ); + edd_set_payment_transaction_id( $payment_id, $data['txn_id'], number_format( (float) $paypal_amount, 2 ) ); + edd_update_payment_status( $payment_id, 'complete' ); + + } else if ( 'pending' == $payment_status && isset( $data['pending_reason'] ) ) { + + // Look for possible pending reasons, such as an echeck + + $note = ''; + + switch( strtolower( $data['pending_reason'] ) ) { + + case 'echeck' : + + $note = __( 'Payment made via eCheck and will clear automatically in 5-8 days', 'easy-digital-downloads' ); + $payment->status = 'processing'; + $payment->save(); + break; + + case 'address' : + + $note = __( 'Payment requires a confirmed customer address and must be accepted manually through PayPal', 'easy-digital-downloads' ); + + break; + + case 'intl' : + + $note = __( 'Payment must be accepted manually through PayPal due to international account regulations', 'easy-digital-downloads' ); + + break; + + case 'multi-currency' : + + $note = __( 'Payment received in non-shop currency and must be accepted manually through PayPal', 'easy-digital-downloads' ); + + break; + + case 'paymentreview' : + case 'regulatory_review' : + + $note = __( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations', 'easy-digital-downloads' ); + + break; + + case 'unilateral' : + + $note = __( 'Payment was sent to non-confirmed or non-registered email address.', 'easy-digital-downloads' ); + + break; + + case 'upgrade' : + + $note = __( 'PayPal account must be upgraded before this payment can be accepted', 'easy-digital-downloads' ); + + break; + + case 'verify' : + + $note = __( 'PayPal account is not verified. Verify account in order to accept this payment', 'easy-digital-downloads' ); + + break; + + case 'other' : + + $note = __( 'Payment is pending for unknown reasons. Contact PayPal support for assistance', 'easy-digital-downloads' ); + + break; + + } + + if ( ! empty( $note ) ) { + + edd_debug_log( 'Payment not marked as completed because: ' . $note ); + edd_insert_payment_note( $payment_id, $note ); + + } + + } + } +} +add_action( 'edd_paypal_web_accept', 'edd_process_paypal_web_accept_and_cart', 10, 2 ); + +/** + * Process PayPal IPN Refunds + * + * @since 1.3.4 + * @param array $data IPN Data + * @return void + */ +function edd_process_paypal_refund( $data, $payment_id = 0 ) { + + /** + * PayPal Process Refund Data + * + * Allows filtering the Refund data that PayPal passes back in via IPN with PayPal Standard + * + * @since 2.8.13 + * + * @param array $data The PayPal Refund data + * @param int $payment_id The Payment ID associated with this IPN request + */ + $data = apply_filters( 'edd_process_paypal_refund_data', $data, $payment_id ); + + // Collect payment details + if ( empty( $payment_id ) ) { + return; + } + + if ( get_post_status( $payment_id ) == 'refunded' ) { + return; // Only refund payments once + } + + $payment_amount = edd_get_payment_amount( $payment_id ); + $refund_amount = $data['mc_gross'] * -1; + + if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) { + + edd_insert_payment_note( $payment_id, sprintf( __( 'Partial PayPal refund processed: %s', 'easy-digital-downloads' ), $data['parent_txn_id'] ) ); + return; // This is a partial refund + + } + + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Payment #%s Refunded for reason: %s', 'easy-digital-downloads' ), $data['parent_txn_id'], $data['reason_code'] ) ); + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Refund Transaction ID: %s', 'easy-digital-downloads' ), $data['txn_id'] ) ); + edd_update_payment_status( $payment_id, 'refunded' ); +} + +/** + * Get PayPal Redirect + * + * @since 1.0.8.2 + * @param bool $ssl_check Is SSL? + * @param bool $ipn Is this an IPN verification check? + * @return string + */ +function edd_get_paypal_redirect( $ssl_check = false, $ipn = false ) { + + $protocol = 'http://'; + if ( is_ssl() || ! $ssl_check ) { + $protocol = 'https://'; + } + + // Check the current payment mode + if ( edd_is_test_mode() ) { + + // Test mode + + if ( $ipn ) { + + $paypal_uri = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'; + + } else { + + $paypal_uri = $protocol . 'www.sandbox.paypal.com/cgi-bin/webscr'; + + } + + } else { + + // Live mode + + if ( $ipn ) { + + $paypal_uri = 'https://ipnpb.paypal.com/cgi-bin/webscr'; + + } else { + + $paypal_uri = $protocol . 'www.paypal.com/cgi-bin/webscr'; + + } + + } + + return apply_filters( 'edd_paypal_uri', $paypal_uri, $ssl_check, $ipn ); +} + +/** + * Get the image for the PayPal purchase page. + * + * @since 2.8 + * @return string + */ +function edd_get_paypal_image_url() { + $image_url = trim( edd_get_option( 'paypal_image_url', '' ) ); + return apply_filters( 'edd_paypal_image_url', $image_url ); +} + +/** + * Shows "Purchase Processing" message for PayPal payments are still pending on site return. + * + * This helps address the Race Condition, as detailed in issue #1839 + * + * @since 1.9 + * @return string + */ +function edd_paypal_success_page_content( $content ) { + + if ( ! isset( $_GET['payment-id'] ) && ! edd_get_purchase_session() ) { + return $content; + } + + edd_empty_cart(); + + $payment_id = isset( $_GET['payment-id'] ) ? absint( $_GET['payment-id'] ) : false; + + if ( ! $payment_id ) { + $session = edd_get_purchase_session(); + $payment_id = edd_get_purchase_id_by_key( $session['purchase_key'] ); + } + + $payment = new EDD_Payment( $payment_id ); + + if ( $payment->ID > 0 && 'pending' == $payment->status ) { + + // Payment is still pending so show processing indicator to fix the Race Condition, issue # + ob_start(); + + edd_get_template_part( 'payment', 'processing' ); + + $content = ob_get_clean(); + + } + + return $content; + +} +add_filter( 'edd_payment_confirm_paypal', 'edd_paypal_success_page_content' ); + +/** + * Mark payment as complete on return from PayPal if a PayPal Identity Token is present. + * + * See https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6197 + * + * @since 2.8.13 + * @return void + */ +function edd_paypal_process_pdt_on_return() { + + if ( ! isset( $_GET['payment-id'] ) || ! isset( $_GET['tx'] ) ) { + return; + } + + $token = edd_get_option( 'paypal_identity_token' ); + + if ( ! edd_is_success_page() || ! $token || ! edd_is_gateway_active( 'paypal' ) ) { + return; + } + + $payment_id = isset( $_GET['payment-id'] ) ? absint( $_GET['payment-id'] ) : false; + + if ( empty( $payment_id ) ) { + return; + } + + $purchase_session = edd_get_purchase_session(); + $payment = new EDD_Payment( $payment_id ); + + // If there is no purchase session, don't try and fire PDT. + if ( empty( $purchase_session ) ) { + return; + } + + // Do not fire a PDT verification if the purchase session does not match the payment-id PDT is asking to verify. + if ( ! empty( $purchase_session['purchase_key'] ) && $payment->key !== $purchase_session['purchase_key'] ) { + return; + } + + if ( $token && ! empty( $_GET['tx'] ) && $payment->ID > 0 ) { + + // An identity token has been provided in settings so let's immediately verify the purchase + + $remote_post_vars = array( + 'method' => 'POST', + 'timeout' => 45, + 'redirection' => 5, + 'httpversion' => '1.1', + 'blocking' => true, + 'headers' => array( + 'host' => 'www.paypal.com', + 'connection' => 'close', + 'content-type' => 'application/x-www-form-urlencoded', + 'post' => '/cgi-bin/webscr HTTP/1.1', + 'user-agent' => 'EDD PDT Verification/' . EDD_VERSION . '; ' . get_bloginfo( 'url' ) + + ), + 'sslverify' => false, + 'body' => array( + 'tx' => sanitize_text_field( $_GET['tx'] ), + 'at' => $token, + 'cmd' => '_notify-synch', + ) + ); + + // Sanitize the data for debug logging. + $debug_args = $remote_post_vars; + $debug_args['body']['at'] = str_pad( substr( $debug_args['body']['at'], -6 ), strlen( $debug_args['body']['at'] ), '*', STR_PAD_LEFT ); + edd_debug_log( 'Attempting to verify PayPal payment with PDT. Args: ' . print_r( $debug_args, true ) ); + + edd_debug_log( 'Sending PDT Verification request to ' . edd_get_paypal_redirect() ); + + $request = wp_remote_post( edd_get_paypal_redirect(), $remote_post_vars ); + + if ( ! is_wp_error( $request ) ) { + + $body = wp_remote_retrieve_body( $request ); + + // parse the data + $lines = explode( "\n", trim( $body ) ); + $data = array(); + if ( strcmp ( $lines[0], "SUCCESS" ) == 0 ) { + + for ( $i = 1; $i < count( $lines ); $i++ ) { + $parsed_line = explode( "=", $lines[ $i ],2 ); + $data[ urldecode( $parsed_line[0] ) ] = urldecode( $parsed_line[1] ); + } + + if ( isset( $data['mc_gross'] ) ) { + + $total = $data['mc_gross']; + + } else if ( isset( $data['payment_gross'] ) ) { + + $total = $data['payment_gross']; + + } else if ( isset( $_REQUEST['amt'] ) ) { + + $total = $_REQUEST['amt']; + + } else { + + $total = null; + + } + + if ( is_null( $total ) ) { + + edd_debug_log( 'Attempt to verify PayPal payment with PDT failed due to payment total missing' ); + $payment->add_note( __( 'Payment could not be verified while validating PayPal PDT. Missing payment total fields.', 'easy-digital-downloads' ) ); + $payment->status = 'pending'; + + } elseif ( (float) $total < (float) $payment->total ) { + + /** + * Here we account for payments that are less than the expected results only. There are times that + * PayPal will sometimes round and have $0.01 more than the amount. The goal here is to protect store owners + * from getting paid less than expected. + */ + edd_debug_log( 'Attempt to verify PayPal payment with PDT failed due to payment total discrepancy' ); + $payment->add_note( sprintf( __( 'Payment failed while validating PayPal PDT. Amount expected: %f. Amount Received: %f', 'easy-digital-downloads' ), $payment->total, $data['payment_gross'] ) ); + $payment->status = 'failed'; + + } else { + + // Verify the status + switch( strtolower( $data['payment_status'] ) ) { + + case 'completed': + $payment->status = 'complete'; + break; + + case 'failed': + $payment->status = 'failed'; + break; + + default: + $payment->status = 'pending'; + break; + + } + + } + + $payment->transaction_id = sanitize_text_field( $_GET['tx'] ); + $payment->save(); + + } elseif ( strcmp ( $lines[0], "FAIL" ) == 0 ) { + + edd_debug_log( 'Attempt to verify PayPal payment with PDT failed due to PDT failure response: ' . print_r( $body, true ) ); + $payment->add_note( __( 'Payment failed while validating PayPal PDT.', 'easy-digital-downloads' ) ); + $payment->status = 'failed'; + $payment->save(); + + } else { + + edd_debug_log( 'Attempt to verify PayPal payment with PDT met with an unexpected result: ' . print_r( $body, true ) ); + $payment->add_note( __( 'PayPal PDT encountered an unexpected result, payment set to pending', 'easy-digital-downloads' ) ); + $payment->status = 'pending'; + $payment->save(); + + } + + } else { + + edd_debug_log( 'Attempt to verify PayPal payment with PDT failed. Request return: ' . print_r( $request, true ) ); + + } + } + +} +add_action( 'template_redirect', 'edd_paypal_process_pdt_on_return' ); /** - * Get Paypal Redirect + * Given a Payment ID, extract the transaction ID * - * @access private - * @since 1.0.8.2 - * @return string -*/ - -function edd_get_paypal_redirect( $ssl_check = false ) { - global $edd_options; - - if( is_ssl() || ! $ssl_check ) { - $protocal = 'https://'; + * @since 2.1 + * @since 3.0 Updated to use EDD_Note class. + * + * @param string $payment_id Payment ID. + * @return string Transaction ID. + */ +function edd_paypal_get_payment_transaction_id( $payment_id ) { + $transaction_id = ''; + $notes = edd_get_payment_notes( $payment_id ); + + foreach ( $notes as $note ) { + if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->content, $match ) ) { + $transaction_id = $match[1]; + continue; + } + } + + return apply_filters( 'edd_paypal_set_payment_transaction_id', $transaction_id, $payment_id ); +} +add_filter( 'edd_get_payment_transaction_id-paypal', 'edd_paypal_get_payment_transaction_id', 10, 1 ); + +/** + * Given a transaction ID, generate a link to the PayPal transaction ID details + * + * @since 2.2 + * @param string $transaction_id The Transaction ID + * @param int $payment_id The payment ID for this transaction + * @return string A link to the PayPal transaction details + */ +function edd_paypal_link_transaction_id( $transaction_id, $payment_id ) { + + $payment = new EDD_Payment( $payment_id ); + $sandbox = 'test' === $payment->mode ? 'sandbox.' : ''; + $paypal_base_url = 'https://' . $sandbox . 'paypal.com/activity/payment/'; + $transaction_url = '' . esc_html( $transaction_id ) . ''; + + return apply_filters( 'edd_paypal_link_payment_details_transaction_id', $transaction_url ); + +} +add_filter( 'edd_payment_details_transaction_id-paypal', 'edd_paypal_link_transaction_id', 10, 2 ); + +/** + * Shows a checkbox to automatically refund payments in PayPal. + * + * @param Order $order + * + * @since 3.0 + * @return void + */ +function edd_paypal_refund_checkbox( Order $order ) { + if ( 'paypal' !== $order->gateway ) { + return; + } + + // If our credentials are not set, return early. + $key = $order->mode; + $username = edd_get_option( 'paypal_' . $key . '_api_username' ); + $password = edd_get_option( 'paypal_' . $key . '_api_password' ); + $signature = edd_get_option( 'paypal_' . $key . '_api_signature' ); + + if ( empty( $username ) || empty( $password ) || empty( $signature ) ) { + return; + } + ?> +
    +
    + + +
    +
    + gateway ) || 'paypal' !== $order->gateway ) { + return; + } + + // Get our data out of the serialized string. + parse_str( $_POST['data'], $form_data ); + + if ( empty( $form_data['edd-paypal-refund'] ) ) { + edd_add_note( array( + 'object_id' => $order_id, + 'object_type' => 'order', + 'user_id' => is_admin() ? get_current_user_id() : 0, + 'content' => __( 'Transaction not refunded in PayPal, as checkbox was not selected.', 'easy-digital-downloads' ) + ) ); + + return; + } + + $refund = edd_get_order( $refund_id ); + if ( empty( $refund->total ) ) { + return; + } + + edd_refund_paypal_purchase( $order, $refund ); +} +add_action( 'edd_refund_order', 'edd_paypal_maybe_refund_transaction', 10, 3 ); + +/** + * Refunds a purchase made via PayPal. + * + * @since 2.6.0 + * + * @param EDD_Payment|Order|int $payment_id_or_object The ID or object of the order to refund. + * @param Order|null $refund_object Optional. The refund object associated with this + * transaction refund. If provided, then the refund + * amount is used as the transaction refund amount (used for + * partial refunds), and an EDD transaction record will be + * inserted. + * + * @return void + */ +function edd_refund_paypal_purchase( $payment_id_or_object, $refund_object = null ) { + /* + * Internally we want to work with an Order object, but we also need + * an EDD_Payment object for backwards compatibility in the hooks. + */ + $order = $payment = false; + if ( $payment_id_or_object instanceof Order ) { + $order = $payment_id_or_object; + $payment = edd_get_payment( $order->id ); + } elseif ( $payment_id_or_object instanceof EDD_Payment ) { + $payment = $payment_id_or_object; + $order = edd_get_order( $payment_id_or_object->ID ); + } elseif ( is_numeric( $payment_id_or_object ) ) { + $order = edd_get_order( $payment_id_or_object ); + $payment = edd_get_payment( $payment_id_or_object ); + } + + if ( empty( $order ) || ! $order instanceof Order ) { + return; + } + + // Set PayPal API key credentials. + $credentials = array( + 'api_endpoint' => 'test' == $order->mode ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp', + 'api_username' => edd_get_option( 'paypal_' . $order->mode . '_api_username' ), + 'api_password' => edd_get_option( 'paypal_' . $order->mode . '_api_password' ), + 'api_signature' => edd_get_option( 'paypal_' . $order->mode . '_api_signature' ) + ); + + $credentials = apply_filters( 'edd_paypal_refund_api_credentials', $credentials, $payment ); + + $body = array( + 'USER' => $credentials['api_username'], + 'PWD' => $credentials['api_password'], + 'SIGNATURE' => $credentials['api_signature'], + 'VERSION' => '124', + 'METHOD' => 'RefundTransaction', + 'TRANSACTIONID' => $order->get_transaction_id(), + 'REFUNDTYPE' => 'Full' + ); + + // If a refund object is supplied, let's check if this should be a partial refund instead. + if ( $refund_object instanceof Order && abs( $refund_object->total ) !== abs( $order->total ) ) { + $body['REFUNDTYPE'] = 'Partial'; + $body['AMT'] = abs( $refund_object->total ); + + /* Translators: %d - order ID number; %s - formatted refund amount */ + edd_debug_log( sprintf( 'Processing partial PayPal refund for order #%d. Amount: %s.', $order->id, edd_currency_filter( $refund_object->total, $refund_object->currency ) ) ); } else { - $protocal = 'http://'; - } - - // check the current payment mode - if ( edd_is_test_mode() ) { - // test mode - $paypal_uri = $protocal . 'www.sandbox.paypal.com/cgi-bin/webscr'; - } else { - // live mode - $paypal_uri = $protocal . 'www.paypal.com/cgi-bin/webscr'; - } - - return $paypal_uri; -} \ No newline at end of file + /* Translators: %d - order ID number */ + edd_debug_log( sprintf( 'Processing full PayPal refund for order #%d.', $order->id ) ); + } + + $body = apply_filters( 'edd_paypal_refund_body_args', $body, $payment ); + + // Prepare the headers of the refund request. + $headers = array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Cache-Control' => 'no-cache' + ); + + $headers = apply_filters( 'edd_paypal_refund_header_args', $headers, $payment ); + + // Prepare args of the refund request. + $args = array( + 'body' => $body, + 'headers' => $headers, + 'httpversion' => '1.1' + ); + + $args = apply_filters( 'edd_paypal_refund_request_args', $args, $payment ); + + $error_msg = ''; + $request = wp_remote_post( $credentials['api_endpoint'], $args ); + + if ( is_wp_error( $request ) ) { + + $success = false; + $error_msg = $request->get_error_message(); + + } else { + + $body = wp_remote_retrieve_body( $request ); + if ( is_string( $body ) ) { + wp_parse_str( $body, $body ); + } + + if ( isset( $body['ACK'] ) && 'success' === strtolower( $body['ACK'] ) ) { + $success = true; + } else { + $success = false; + if ( isset( $body['L_LONGMESSAGE0'] ) ) { + $error_msg = $body['L_LONGMESSAGE0']; + } else { + $error_msg = __( 'PayPal refund failed for unknown reason.', 'easy-digital-downloads' ); + } + } + + } + + if ( $success ) { + + edd_update_order_meta( $order->id, '_edd_paypal_refunded', true ); + + // Add a note to the original order, and, if provided, the new refund object. + if ( isset( $body['GROSSREFUNDAMT'] ) ) { + /* Translators: %1$s - amount refunded; %2$s - transaction ID. */ + $note_message = sprintf( __( '%1$s refunded in PayPal. Transaction ID: %2$s', 'easy-digital-downloads' ), edd_currency_filter( edd_format_amount( $body['GROSSREFUNDAMT'] ) ), esc_html( $body['REFUNDTRANSACTIONID'] ) ); + } else { + /* Translators: %s - transaction ID. */ + $note_message = sprintf( __( 'PayPal refund transaction ID: %s', 'easy-digital-downloads' ), esc_html( $body['REFUNDTRANSACTIONID'] ) ); + } + $note_object_ids = array( $order->id ); + if ( $refund_object instanceof Order ) { + $note_object_ids[] = $refund_object->id; + } + foreach ( $note_object_ids as $note_object_id ) { + edd_add_note( array( + 'object_id' => $note_object_id, + 'object_type' => 'order', + 'user_id' => is_admin() ? get_current_user_id() : 0, + 'content' => $note_message + ) ); + } + + // Add a negative transaction. + if ( $refund_object instanceof Order && isset( $body['REFUNDTRANSACTIONID'] ) && isset( $body['GROSSREFUNDAMT'] ) ) { + edd_add_order_transaction( array( + 'object_id' => $refund_object->id, + 'object_type' => 'order', + 'transaction_id' => sanitize_text_field( $body['REFUNDTRANSACTIONID'] ), + 'gateway' => 'paypal', + 'status' => 'complete', + 'total' => edd_negate_amount( $body['GROSSREFUNDAMT'] ) + ) ); + } + + } else { + edd_add_note( array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'user_id' => is_admin() ? get_current_user_id() : 0, + 'content' => sprintf( __( 'PayPal refund failed: %s', 'easy-digital-downloads' ), $error_msg ) + ) ); + } + + // Run hook letting people know the payment has been refunded successfully. + do_action( 'edd_paypal_refund_purchase', $payment ); +} diff --git a/includes/gateways/paypal.php b/includes/gateways/paypal.php deleted file mode 100755 index 7ed41eb4683..00000000000 --- a/includes/gateways/paypal.php +++ /dev/null @@ -1,274 +0,0 @@ - array of download IDs, - 'price' => total price of cart contents, - 'purchase_key' => // random key - 'user_email' => $user_email, - 'date' => date('Y-m-d H:i:s'), - 'user_id' => $user_id, - 'post_data' => $_POST, - 'user_info' => array of user's information and used discount code - 'cart_details' => array of cart details, - ); - */ - - $payment_data = array( - 'price' => $purchase_data['price'], - 'date' => $purchase_data['date'], - 'user_email' => $purchase_data['user_email'], - 'purchase_key' => $purchase_data['purchase_key'], - 'currency' => $edd_options['currency'], - 'downloads' => $purchase_data['downloads'], - 'user_info' => $purchase_data['user_info'], - 'cart_details' => $purchase_data['cart_details'], - 'status' => 'pending' - ); - - // record the pending payment - $payment = edd_insert_payment($payment_data); - - if($payment) { - // only send to paypal if the pending payment is created successfully - $listener_url = trailingslashit(home_url()).'?edd-listener=IPN'; - $return_url = add_query_arg('payment-confirmation', 'paypal', get_permalink($edd_options['success_page'])); - $cart_summary = edd_get_purchase_summary($purchase_data, false); - - // one time payment - if(edd_is_test_mode()) { - $paypal_redirect = 'https://www.sandbox.paypal.com/cgi-bin/webscr/?'; - } else { - $paypal_redirect = 'https://www.paypal.com/cgi-bin/webscr/?'; - } - $paypal_args = array( - 'cmd' => '_xclick', - 'amount' => $purchase_data['price'], - 'business' => $edd_options['paypal_email'], - 'item_name' => $cart_summary, - 'email' => $purchase_data['user_email'], - 'no_shipping' => '1', - 'no_note' => '1', - 'currency_code' => $edd_options['currency'], - 'item_number' => $purchase_data['purchase_key'], - 'charset' => 'UTF-8', - 'custom' => $payment, - 'rm' => '2', - 'return' => $return_url, - 'notify_url' => $listener_url - ); - //var_dump(http_build_query($paypal_args)); exit; - $paypal_redirect .= http_build_query($paypal_args); - - //var_dump(urldecode($paypal_redirect)); exit; - - // get rid of cart contents - edd_empty_cart(); - - // Redirect to paypal - wp_redirect($paypal_redirect); - exit; - - } else { - // if errors are present, send the user back to the purchase page so they can be corrected - edd_send_back_to_checkout('?payment-mode=' . $purchase_data['post_data']['edd-gateway']); - } -} -add_action('edd_gateway_paypal', 'edd_process_paypal_purchase'); - - -/** - * Listen For PayPal IPN - * - * Listens for a PayPal IPN requests and then sends to the processing function. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_listen_for_paypal_ipn() { - global $edd_options; - - // regular PayPal IPN - if(!isset($edd_options['paypal_alternate_verification'])) { - - if(isset($_GET['edd-listener']) && $_GET['edd-listener'] == 'IPN') { - do_action('edd_verify_paypal_ipn'); - } - - // alternate purchase verification - } else { - if(isset($_GET['tx']) && isset($_GET['st']) && isset($_GET['amt']) && isset($_GET['cc']) && isset($_GET['cm']) && isset($_GET['item_number'])) { - // we are using the alternate method of verifying PayPal purchases - - // setup each of the variables from PayPal - $payment_status = $_GET['st']; - $paypal_amount = $_GET['amt']; - $payment_id = $_GET['cm']; - $purchase_key = $_GET['item_number']; - $currency = $_GET['cc']; - - // retrieve the meta info for this payment - $payment_meta = get_post_meta($payment_id, '_edd_payment_meta', true); - $payment_amount = edd_format_amount($payment_meta['amount']); - if($currency != $edd_options['currency']) { - return; // the currency code is invalid - } - if($paypal_amount != $payment_amount) { - return; // the prices don't match - } - if($purchase_key != $payment_meta['key']) { - return; // purchase keys don't match - } - if(strtolower($payment_status) != 'completed' || edd_is_test_mode()) { - return; // payment wasn't completed - } - - // everything has been verified, update the payment to "complete" - edd_update_payment_status($payment_id, 'publish'); - } - } -} -add_action('init', 'edd_listen_for_paypal_ipn'); - - -/** - * Process PayPal IPN - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_process_paypal_ipn() { - - global $edd_options; - - // instantiate the IpnListener class - if(!class_exists('IpnListener')) { - include_once(EDD_PLUGIN_DIR . 'includes/gateways/libraries/paypal/ipnlistener.php'); - } - - $listener = new IpnListener(); - - if(edd_is_test_mode()) { - $listener->use_sandbox = true; - } - - - if(isset($edd_options['ssl'])) { - $listener->use_ssl = false; - } - // to post using the fsockopen() function rather than cURL, use: - if(isset($edd_options['paypal_disable_curl'])) { - $listener->use_curl = false; - } - - try { - $listener->requirePostMethod(); - $verified = $listener->processIpn(); - } catch (Exception $e) { - wp_mail(get_bloginfo('admin_email'), 'IPN Error', $e->getMessage()); - exit(0); - } - - if ($verified) { - $payment_id = $_POST['custom']; - $purchase_key = $_POST['item_number']; - $paypal_amount = $_POST['mc_gross']; - $payment_status = $_POST['payment_status']; - $currency_code = strtolower($_POST['mc_currency']); - - // retrieve the meta info for this payment - $payment_meta = get_post_meta($payment_id, '_edd_payment_meta', true); - $payment_amount = edd_format_amount($payment_meta['amount']); - - if($currency_code != strtolower($edd_options['currency'])) { - return; // the currency code is invalid - } - if($paypal_amount != $payment_amount) { - return; // the prices don't match - } - if($purchase_key != $payment_meta['key']) { - return; // purchase keys don't match - } - - if(isset($_POST['txn_type']) && $_POST['txn_type'] == 'web_accept') { - - $status = strtolower($payment_status); - - if( $status == 'completed' || edd_is_test_mode()) { - - // set the payment to complete. This also sends the emails - edd_update_payment_status($payment_id, 'publish'); - - } else if( $status == 'refunded' ) { - - // this refund process doesn't work yet - - $payment_data = get_post_meta($payment_id, '_edd_payment_meta', true); - $downloads = maybe_unserialize($payment_data['downloads']); - - if( is_array( $downloads ) ) { - foreach( $downloads as $download ) { - edd_undo_purchase( $download['id'], $payment_id ); - } - } - - wp_update_post(array('ID' => $payment_id, 'post_status' => 'refunded')); - - } - } - - } else { - wp_mail(get_bloginfo('admin_email'), __('Invalid IPN', 'edd'), $listener->getTextReport()); - } -} -add_action('edd_verify_paypal_ipn', 'edd_process_paypal_ipn'); \ No newline at end of file diff --git a/includes/gateways/paypal/admin/connect.php b/includes/gateways/paypal/admin/connect.php new file mode 100644 index 00000000000..ecf12457736 --- /dev/null +++ b/includes/gateways/paypal/admin/connect.php @@ -0,0 +1,830 @@ +signupLink ) ) { + ?> +
    +

    + tag; %2$s closing tag */ + __( '%1$sPayPal Communication Error:%2$s We are having trouble communicating with PayPal at the moment. Please try again later, and if the issue persists, reach out to our support team.', 'easy-digital-downloads' ), + '', + '' + ), array( 'strong' => array() ) ); + ?> +

    +
    + + + + + +
    + + +
    + + + 403, + 'body' => array( + 'message' => __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), + ), + ); + } + + $mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE; + + $existing_connect_details = get_partner_details( $mode ); + + if ( ! empty( $existing_connect_details ) ) { + // Ensure the data we have contains all necessary details. + if ( + ( ! empty( $existing_connect_details->expires ) && $existing_connect_details->expires > time() ) && + ! empty( $existing_connect_details->nonce ) && + ! empty( $existing_connect_details->signupLink ) && // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + ! empty( $existing_connect_details->product ) + ) { + return array( + 'code' => 200, + 'body' => $existing_connect_details, + ); + } + } + + $response = wp_remote_post( + EDD_PAYPAL_PARTNER_CONNECT_URL . 'signup-link', + array( + 'headers' => array( + 'Content-Type' => 'application/json', + ), + 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), + 'body' => wp_json_encode( + array( + 'mode' => $mode, + 'country_code' => edd_get_shop_country(), + 'currency_code' => edd_get_currency(), + 'return_url' => get_settings_url(), + ) + ), + ) + ); + + $code = wp_remote_retrieve_response_code( $response ); + + if ( is_wp_error( $response ) ) { + + return array( + 'code' => $code, + 'body' => $response->get_error_message(), + ); + } + + $body = wp_remote_retrieve_body( $response ); + $body = json_decode( $body ); + + // We're storing an expiration so we can get a new one if it's been a day. + $body->expires = time() + DAY_IN_SECONDS; + + // We need to store this temporarily so we can use the nonce again in the next request. + update_option( 'edd_paypal_commerce_connect_details_' . $mode, wp_json_encode( $body ), false ); + + return array( + 'code' => $code, + 'body' => $body, + ); +} + +/** + * AJAX handler for processing the PayPal Connection. + * + * @since 2.11 + * @deprecated 3.1.2 Instead of doing this via an AJAX request, we now do this on page load. + * + * @return void + */ +function process_connect() { + _edd_deprecated_function( __FUNCTION__, '3.1.2', 'EDD_PayPal_Commerce::get_onboarding_data()' ); + + // This validates the nonce. + check_ajax_referer( 'edd_process_paypal_connect' ); + + $onboarding_data = get_onboarding_data(); + + if ( 200 !== intval( $onboarding_data['code'] ) ) { + wp_send_json_error( sprintf( + /* Translators: %d - HTTP response code; %s - Response from the API */ + __( 'Unexpected response code: %d. Error: %s', 'easy-digital-downloads' ), + $onboarding_data['code'], + wp_json_encode( $onboarding_data['body'] ) + ) ); + } + + if ( empty( $onboarding_data['body']->signupLink ) || empty( $onboarding_data['body']->nonce ) ) { + wp_send_json_error( __( 'An unexpected error occurred.', 'easy-digital-downloads' ) ); + } + + wp_send_json_success( $onboarding_data['body'] ); +} + +/** + * AJAX handler for processing the PayPal Reconnect. + * + * @since 3.1.0.3 + * @return void + */ +function process_reconnect() { + // This validates the nonce. + check_ajax_referer( 'edd_process_paypal_connect' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ); + } + + $mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE; + + /** + * Make sure we still have connection details from the previously connected site. + */ + $connection_details = get_option( 'edd_paypal_commerce_connect_details_' . $mode ); + + if ( empty( $connection_details ) ) { + // Somehow we ended up here, but now that we're in an invalid state, remove all settings so we can fully reset. + delete_option( 'edd_paypal_commerce_connect_details_' . $mode ); + delete_option( 'edd_paypal_commerce_webhook_id_' . $mode ); + delete_option( 'edd_paypal_' . $mode . '_merchant_details' ); + wp_send_json_error( __( 'Failure reconnecting to PayPal. Please try again', 'easy-digital-downloads' ) ); + } + + try { + PayPal\Webhooks\create_webhook( $mode ); + } catch ( \Exception $e ) { + $message = esc_html__( 'Your account has been successfully reconnected, but an error occurred while creating a webhook.', 'easy-digital-downloads' ); + } + + wp_safe_redirect( esc_url_raw( get_settings_url() ) ); +} +add_action( 'wp_ajax_edd_paypal_commerce_reconnect', __NAMESPACE__ . '\process_reconnect' ); + +/** + * Retrieves partner Connect details for the given mode. + * + * @param string $mode Store mode. If omitted, current mode is used. + * + * @return stdObj|null + */ +function get_partner_details( $mode = '' ) { + if ( ! $mode ) { + $mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE; + } + return json_decode( get_option( 'edd_paypal_commerce_connect_details_' . $mode ) ); +} + +/** + * AJAX handler for retrieving a one-time access token, then used to retrieve + * the seller's API credentials. + * + * @since 2.11 + * @return void + */ +function get_and_save_credentials() { + // This validates the nonce. + check_ajax_referer( 'edd_process_paypal_connect' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ); + } + + if ( empty( $_POST['auth_code'] ) || empty( $_POST['share_id'] ) ) { + wp_send_json_error( __( 'Missing PayPal authentication information. Please try again.', 'easy-digital-downloads' ) ); + } + + $mode = edd_is_test_mode() ? PayPal\API::MODE_SANDBOX : PayPal\API::MODE_LIVE; + + $partner_details = get_partner_details( $mode ); + if ( empty( $partner_details->nonce ) ) { + wp_send_json_error( __( 'Missing nonce. Please refresh the page and try again.', 'easy-digital-downloads' ) ); + } + + $paypal_subdomain = edd_is_test_mode() ? '.sandbox' : ''; + $api_url = 'https://api-m' . $paypal_subdomain . '.paypal.com/'; + $api_args = array( + 'headers' => array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Authorization' => sprintf( 'Basic %s', base64_encode( $_POST['share_id'] ) ), + 'timeout' => 15, + ), + 'body' => array( + 'grant_type' => 'authorization_code', + 'code' => $_POST['auth_code'], + 'code_verifier' => $partner_details->nonce, + ), + 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), + ); + + /* + * First get a temporary access token from PayPal. + */ + $response = wp_remote_post( + $api_url . 'v1/oauth2/token', + $api_args + ); + + if ( is_wp_error( $response ) ) { + wp_send_json_error( $response->get_error_message() ); + } + + $code = wp_remote_retrieve_response_code( $response ); + $body = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( empty( $body->access_token ) ) { + wp_send_json_error( + sprintf( + /* Translators: %d - HTTP response code */ + __( 'Unexpected response from PayPal while generating token. Response code: %d. Please try again.', 'easy-digital-downloads' ), + $code + ) + ); + } + + /* + * Now we can use this access token to fetch the seller's credentials for all future + * API requests. + */ + $response = wp_remote_get( $api_url . 'v1/customer/partners/' . urlencode( \EDD\Gateways\PayPal\get_partner_merchant_id( $mode ) ) . '/merchant-integrations/credentials/', array( + 'headers' => array( + 'Authorization' => sprintf( 'Bearer %s', $body->access_token ), + 'Content-Type' => 'application/json', + 'timeout' => 15 + ), + 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), + ) ); + + if ( is_wp_error( $response ) ) { + wp_send_json_error( $response->get_error_message() ); + } + + $code = wp_remote_retrieve_response_code( $response ); + $body = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( empty( $body->client_id ) || empty( $body->client_secret ) ) { + wp_send_json_error( sprintf( + /* Translators: %d - HTTP response code */ + __( 'Unexpected response from PayPal. Response code: %d. Please try again.', 'easy-digital-downloads' ), + $code + ) ); + } + + edd_update_option( 'paypal_' . $mode . '_client_id', sanitize_text_field( $body->client_id ) ); + edd_update_option( 'paypal_' . $mode . '_client_secret', sanitize_text_field( $body->client_secret ) ); + + $message = esc_html__( 'Successfully connected.', 'easy-digital-downloads' ); + + try { + PayPal\Webhooks\create_webhook( $mode ); + } catch ( \Exception $e ) { + $message = esc_html__( 'Your account has been successfully connected, but an error occurred while creating a webhook.', 'easy-digital-downloads' ); + } + + /** + * Triggers when an account is successfully connected to PayPal. + * + * @param string $mode The mode that the account was connected in. Either `sandbox` or `live`. + * + * @since 2.11 + */ + do_action( 'edd_paypal_commerce_connected', $mode ); + + wp_send_json_success( $message ); +} + +add_action( 'wp_ajax_edd_paypal_commerce_get_access_token', __NAMESPACE__ . '\get_and_save_credentials' ); + +/** + * Verifies the connected account. + * + * @since 2.11 + * @return void + */ +function get_account_info() { + check_ajax_referer( 'edd_paypal_account_information' ); + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + wp_send_json_error( wpautop( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ) ); + } + + try { + $status = 'success'; + $account_status = ''; + $actions = array( + 'refresh_merchant' => '', + 'webhook' => '' + ); + + $disconnect_links = array( + 'disconnect' => '' . __( "Disconnect webhooks from PayPal", "easy-digital-downloads" ) . '', + 'delete' => '' . __( "Delete connection with PayPal", "easy-digital-downloads" ) . '', + ); + + $validator = new AccountStatusValidator(); + $validator->check(); + + /* + * 1. Check REST API credentials + */ + $rest_api_message = '' . __( 'API:', 'easy-digital-downloads' ) . '' . ' '; + if ( $validator->errors_for_credentials->errors ) { + $rest_api_dashicon = 'no'; + $status = 'error'; + $rest_api_message .= $validator->errors_for_credentials->get_error_message(); + } else { + $rest_api_dashicon = 'yes'; + $mode_string = edd_is_test_mode() ? __( 'sandbox', 'easy-digital-downloads' ) : __( 'live', 'easy-digital-downloads' ); + + /* Translators: %s - the connected mode, either `sandbox` or `live` */ + $rest_api_message .= sprintf( __( 'Your PayPal account is successfully connected in %s mode.', 'easy-digital-downloads' ), $mode_string ); + } + + ob_start(); + ?> +
  • + + array() ) ); ?> +
  • + ' . __( 'Payment Status:', 'easy-digital-downloads' ) . '' . ' '; + if ( $validator->errors_for_merchant_account->errors ) { + $merchant_dashicon = 'no'; + $status = 'error'; + $merchant_account_message .= __( 'You need to address the following issues before you can start receiving payments:', 'easy-digital-downloads' ); + + // We can only refresh the status if we have a merchant ID. + if ( in_array( 'missing_merchant_details', $validator->errors_for_merchant_account->get_error_codes() ) ) { + unset( $actions['refresh_merchant'] ); + } + } else { + $merchant_dashicon = 'yes'; + $merchant_account_message .= __( 'Ready to accept payments.', 'easy-digital-downloads' ); + } + + ob_start(); + ?> +
  • + + + errors_for_merchant_account->errors ) : ?> +
      + errors_for_merchant_account->get_error_codes() as $code ) : ?> +
    • errors_for_merchant_account->get_error_message( $code ), array( 'strong' => array() ) ); ?>
    • + +
    + +
  • + ' . __( 'Webhook:', 'easy-digital-downloads' ) . '' . ' '; + if ( $validator->errors_for_webhook->errors ) { + $webhook_dashicon = 'no'; + $status = ( 'success' === $status ) ? 'warning' : $status; + $webhook_message .= $validator->errors_for_webhook->get_error_message(); + + if ( in_array( 'webhook_missing', $validator->errors_for_webhook->get_error_codes() ) ) { + unset( $disconnect_links['disconnect'] ); + $actions['webhook'] = ''; + } + } else { + unset( $disconnect_links['delete'] ); + $webhook_dashicon = 'yes'; + $webhook_message .= __( 'Webhook successfully configured for the following events:', 'easy-digital-downloads' ); + } + + ob_start(); + ?> +
  • + + array() ) ); ?> + webhook ) : ?> +
      + +
    • + + +
    • + +
    + +
  • + ', + '', + '
  • ', + '', + '
  • ' + ); + } + + wp_send_json_success( array( + 'status' => $status, + 'account_status' => '', + 'webhook_object' => isset( $validator ) ? $validator->webhook : null, + 'actions' => array_values( $actions ), + 'disconnect_links' => array_values( $disconnect_links ), + ) ); + } catch ( \Exception $e ) { + wp_send_json_error( array( + 'status' => isset( $status ) ? $status : 'error', + 'message' => wpautop( $e->getMessage() ) + ) ); + } +} + +add_action( 'wp_ajax_edd_paypal_commerce_get_account_info', __NAMESPACE__ . '\get_account_info' ); + +/** + * Returns the URL for disconnecting from PayPal Commerce. + * + * @since 2.11 + * @return string + */ +function get_disconnect_url() { + return wp_nonce_url( + add_query_arg( + array( + 'edd_action' => 'disconnect_paypal_commerce' + ), + admin_url() + ), + 'edd_disconnect_paypal_commerce' + ); +} + +/** + * Returns the URL for deleting the app PayPal Commerce. + * + * @since 3.1.0.3 + * @return string + */ +function get_delete_url() { + return wp_nonce_url( + add_query_arg( + array( + 'edd_action' => 'delete_paypal_commerce' + ), + admin_url() + ), + 'edd_delete_paypal_commerce' + ); +} + +/** + * Disconnects from PayPal in the current mode. + * + * @since 2.11 + * @return void + */ +function process_disconnect() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'edd_disconnect_paypal_commerce' ) ) { + wp_die( esc_html__( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $mode = edd_is_test_mode() ? PayPal\API::MODE_SANDBOX : PayPal\API::MODE_LIVE; + + try { + $api = new PayPal\API(); + + try { + // Disconnect the webhook. + // This is in another try/catch because we want to delete the token cache (below) even if this fails. + // This only deletes the webhooks in PayPal, we do not remove the webhook ID in EDD until we delete the connection. + PayPal\Webhooks\delete_webhook( $mode ); + } catch ( \Exception $e ) { + + } + + // Also delete the token cache key, to ensure we fetch a fresh one if they connect to a different account later. + delete_option( $api->token_cache_key ); + } catch ( \Exception $e ) { + + } + + wp_safe_redirect( esc_url_raw( get_settings_url() ) ); + exit; +} + +add_action( 'edd_disconnect_paypal_commerce', __NAMESPACE__ . '\process_disconnect' ); + +/** + * Fully deletes past Merchant Information from PayPal in the current mode. + * + * @since 3.1.0.3 + * @return void + */ +function process_delete() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'edd_delete_paypal_commerce' ) ) { + wp_die( esc_html__( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $mode = edd_is_test_mode() ? PayPal\API::MODE_SANDBOX : PayPal\API::MODE_LIVE; + + // Delete merchant information. + delete_option( 'edd_paypal_' . $mode . '_merchant_details' ); + + // Delete partner connect information. + delete_option( 'edd_paypal_commerce_connect_details_' . $mode ); + + try { + $api = new PayPal\API(); + + try { + // Disconnect the webhook. + // This is in another try/catch because we want to delete the token cache (below) even if this fails. + // This only deletes the webhooks in PayPal, we do not remove the webhook ID in EDD until we delete the connection. + PayPal\Webhooks\delete_webhook( $mode ); + } catch ( \Exception $e ) { + + } + + // Also delete the token cache key, to ensure we fetch a fresh one if they connect to a different account later. + delete_option( $api->token_cache_key ); + } catch ( \Exception $e ) { + + } + + // Now delete our webhook ID. + delete_option( sanitize_key( 'edd_paypal_commerce_webhook_id_' . $mode ) ); + + // Delete API credentials. + $edd_settings_to_delete = array( + 'paypal_' . $mode . '_client_id', + 'paypal_' . $mode . '_client_secret', + ); + + foreach ( $edd_settings_to_delete as $option_name ) { + edd_delete_option( $option_name ); + } + + // Unset the PayPal Commerce gateway as an enabled gateway. + $enabled_gateways = edd_get_option( 'gateways', array() ); + unset( $enabled_gateways['paypal_commerce'] ); + edd_update_option( 'gateways', $enabled_gateways ); + + wp_safe_redirect( esc_url_raw( get_settings_url() ) ); + exit; +} + +add_action( 'edd_delete_paypal_commerce', __NAMESPACE__ . '\process_delete' ); + +/** + * AJAX callback for refreshing payment status. + * + * @since 2.11 + */ +function refresh_merchant_status() { + check_ajax_referer( 'edd_check_merchant_status' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ); + } + + $merchant_details = PayPal\MerchantAccount::retrieve(); + + try { + if ( empty( $merchant_details->merchant_id ) ) { + throw new \Exception( __( 'No merchant ID saved. Please reconnect to PayPal.', 'easy-digital-downloads' ) ); + } + + $partner_details = get_partner_details(); + $nonce = isset( $partner_details->nonce ) ? $partner_details->nonce : null; + + $new_details = get_merchant_status( $merchant_details->merchant_id, $nonce ); + $merchant_account = new PayPal\MerchantAccount( $new_details ); + $merchant_account->save(); + + wp_send_json_success(); + } catch ( \Exception $e ) { + wp_send_json_error( esc_html( $e->getMessage() ) ); + } +} + +add_action( 'wp_ajax_edd_paypal_commerce_check_merchant_status', __NAMESPACE__ . '\refresh_merchant_status' ); + +/** + * AJAX callback for creating a webhook. + * + * @since 2.11 + */ +function create_webhook() { + check_ajax_referer( 'edd_create_paypal_webhook' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ); + } + + try { + PayPal\Webhooks\create_webhook(); + + wp_send_json_success(); + } catch ( \Exception $e ) { + wp_send_json_error( esc_html( $e->getMessage() ) ); + } +} + +add_action( 'wp_ajax_edd_paypal_commerce_create_webhook', __NAMESPACE__ . '\create_webhook' ); + +/** + * AJAX callback for syncing a webhook. This is used to fix issues with missing events. + * + * @since 2.11 + */ +function update_webhook() { + check_ajax_referer( 'edd_update_paypal_webhook' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'easy-digital-downloads' ) ); + } + + try { + PayPal\Webhooks\sync_webhook(); + + wp_send_json_success(); + } catch ( \Exception $e ) { + wp_send_json_error( esc_html( $e->getMessage() ) ); + } +} + +add_action( 'wp_ajax_edd_paypal_commerce_update_webhook', __NAMESPACE__ . '\update_webhook' ); + +/** + * PayPal Redirect Callback + * + * This processes after the merchant is redirected from PayPal. We immediately + * check their seller status via partner connect and save their merchant status. + * The user is then redirected back to the settings page. + * + * @since 2.11 + */ +add_action( 'admin_init', function () { + if ( ! isset( $_GET['merchantIdInPayPal'] ) || ! edd_is_admin_page( 'settings' ) ) { + return; + } + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + edd_debug_log( 'PayPal Connect - Checking merchant status.' ); + + $merchant_id = urldecode( $_GET['merchantIdInPayPal'] ); + + try { + $details = get_merchant_status( $merchant_id ); + edd_debug_log( 'PayPal Connect - Successfully retrieved merchant status.' ); + } catch ( \Exception $e ) { + /* + * This won't be enough to actually validate the merchant status, but we want to ensure + * we save the merchant ID no matter what. + */ + $details = array( + 'merchant_id' => $merchant_id + ); + + edd_debug_log( sprintf( 'PayPal Connect - Failed to retrieve merchant status from PayPal. Error: %s', $e->getMessage() ) ); + } + + $merchant_account = new PayPal\MerchantAccount( $details ); + $merchant_account->save(); + + wp_safe_redirect( esc_url_raw( get_settings_url() ) ); + exit; +} ); + +/** + * Retrieves the merchant's status in PayPal. + * + * @param string $merchant_id + * @param string $nonce + * + * @return array + * @throws PayPal\Exceptions\API_Exception + */ +function get_merchant_status( $merchant_id, $nonce = '' ) { + $response = wp_remote_post( EDD_PAYPAL_PARTNER_CONNECT_URL . 'merchant-status', array( + 'headers' => array( + 'Content-Type' => 'application/json', + ), + 'body' => json_encode( array( + 'mode' => edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE, + 'merchant_id' => $merchant_id, + 'nonce' => $nonce + ) ), + 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), + ) ); + + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( 200 !== (int) $response_code ) { + if ( ! empty( $response_body['error'] ) ) { + $error_message = $response_body['error']; + } else { + $error_message = sprintf( + 'Invalid HTTP response code: %d. Response: %s', + $response_code, + wp_remote_retrieve_body( $response ) + ); + } + + throw new PayPal\Exceptions\API_Exception( $error_message, $response_code ); + } + + return $response_body; +} diff --git a/includes/gateways/paypal/admin/notices.php b/includes/gateways/paypal/admin/notices.php new file mode 100644 index 00000000000..82732ede614 --- /dev/null +++ b/includes/gateways/paypal/admin/notices.php @@ -0,0 +1,66 @@ + 'dismiss_notices', + 'edd_notice' => 'paypal_commerce' + ) ), 'edd_notice_nonce' ); + + $setup_url = add_query_arg( array( + 'post_type' => 'download', + 'page' => 'edd-settings', + 'tab' => 'gateways', + 'section' => 'paypal_commerce' + ), admin_url( 'edit.php' ) ); + + ?> +
    +

    +

    + ', + '' + ), array( 'a' => array( 'href' => true, 'target' => true ) ) ); + ?> +

    +

    + + +

    +
    + esc_html__( 'An unexpected error occurred. Please refresh the page and try again.', 'easy-digital-downloads' ), + 'isConnected' => PayPal\has_rest_api_connection(), + ) + ); + } +} +add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_connect_scripts' ); + +/** + * Forces the Cache-Control header on the PayPal Commerce settings page to send the no-store header + * which prevents the back-forward cache (bfcache) from storing a copy of this page in local + * cache. This helps make sure that page elements modified via AJAX and DOM manipulations aren't + * incorrectly shown as if they never changed. + * + * See: https://github.com/easydigitaldownloads/EDD-Software-Licensing/issues/1346#issuecomment-382159918 + * + * @since 3.6 + * @param array $headers An array of nocache headers. + * + * @return array + */ +function _bfcache_buster( $headers ) { + if ( ! is_admin() ) { + return $headers; + } + + if ( edd_is_admin_page( 'settings' ) && isset( $_GET['section'] ) && 'paypal_commerce' === $_GET['section'] ) { /* phpcs:ignore WordPress.Security.NonceVerification.Recommended */ + $headers['Cache-Control'] = 'no-cache, must-revalidate, max-age=0, no-store'; + } + + return $headers; +} +add_filter( 'nocache_headers', __NAMESPACE__ . '\_bfcache_buster', 10, 1 ); diff --git a/includes/gateways/paypal/admin/settings.php b/includes/gateways/paypal/admin/settings.php new file mode 100644 index 00000000000..c927d30d527 --- /dev/null +++ b/includes/gateways/paypal/admin/settings.php @@ -0,0 +1,155 @@ + array( + 'id' => 'paypal_settings', + 'name' => '

    ' . __( 'PayPal Settings', 'easy-digital-downloads' ) . '

    ', + 'type' => 'header', + ), + 'paypal_connect_button' => array( + 'id' => 'paypal_connect_button', + 'name' => __( 'Connection Status', 'easy-digital-downloads' ), + 'class' => 'edd-paypal-connect-row', + 'type' => 'hook', + ), + 'paypal_sandbox_client_id' => array( + 'id' => 'paypal_sandbox_client_id', + 'name' => __( 'Test Client ID', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your test client ID.', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + 'class' => 'edd-hidden', + ), + 'paypal_sandbox_client_secret' => array( + 'id' => 'paypal_sandbox_client_secret', + 'name' => __( 'Test Client Secret', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your test client secret.', 'easy-digital-downloads' ), + 'type' => 'password', + 'size' => 'regular', + 'class' => 'edd-hidden', + ), + 'paypal_live_client_id' => array( + 'id' => 'paypal_live_client_id', + 'name' => __( 'Live Client ID', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your live client ID.', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + 'class' => 'edd-hidden', + ), + 'paypal_live_client_secret' => array( + 'id' => 'paypal_live_client_secret', + 'name' => __( 'Live Client Secret', 'easy-digital-downloads' ), + 'desc' => __( 'Enter your live client secret.', 'easy-digital-downloads' ), + 'type' => 'password', + 'size' => 'regular', + 'class' => 'edd-hidden', + ), + 'paypal_documentation' => array( + 'id' => 'paypal_documentation', + 'name' => '', + 'type' => 'hook', + ), + ); + + $is_connected = PayPal\has_rest_api_connection(); + if ( ! $is_connected ) { + $paypal_settings['paypal_settings']['tooltip_title'] = __( 'Connect with PayPal', 'easy-digital-downloads' ); + $paypal_settings['paypal_settings']['tooltip_desc'] = __( 'Connecting your store with PayPal allows Easy Digital Downloads to automatically configure your store to securely communicate with PayPal.

    You may see "Sandhills Development, LLC", mentioned during the process—that is the company behind Easy Digital Downloads.', 'easy-digital-downloads' ); + } + + /** + * Filters the PayPal Settings. + * + * @param array $paypal_settings + */ + $paypal_settings = apply_filters( 'edd_paypal_settings', $paypal_settings ); + $gateway_settings['paypal_commerce'] = $paypal_settings; + + return $gateway_settings; +} + +add_filter( 'edd_settings_gateways', __NAMESPACE__ . '\register_gateway_settings', 1, 1 ); + +/** + * Returns the content for the documentation settings. + * + * @since 2.11 + * @return string + */ +function documentation_settings_field() { + ?> +

    + + + + + + + +

    + +
    +

    + SSL setup article.', 'easy-digital-downloads' ), + 'https://easydigitaldownloads.com/docs/do-i-need-an-ssl-certificate/' + ), array( 'a' => array( 'href' => true, 'target' => true ) ) ); + ?> +

    +
    + is_recurring( $args['download_id'] ) ) { + return $args; + } + + // Price ID is recurring. + if ( ! empty( $args['price_id'] ) && edd_recurring()->is_price_recurring( $args['download_id'], $args['price_id'] ) ) { + return $args; + } + } + + if ( ! empty( $args['direct'] ) ) { + $args['class'] .= ' edd-paypal-checkout-buy-now'; + } + + return $args; +} + +add_filter( 'edd_purchase_link_args', __NAMESPACE__ . '\maybe_add_purchase_link_class' ); + +/** + * Registers PayPal Commerce JavaScript if using "direct" buy now links. + * + * @param int $download_id ID of the download. + * @param array $args Purchase link arguments. + * + * @since 2.11 + */ +function maybe_enable_buy_now_js( $download_id, $args ) { + if ( ! empty( $args['direct'] ) && is_buy_now_enabled() ) { + register_js( true ); + $timestamp = time(); + ?> + + + + +
    +
    + + +
    +

    + +

    +
    + 403 ) ); + } + + edd_debug_log( 'PayPal - create_order()' ); + + if ( ! ready_to_accept_payments() ) { + edd_record_gateway_error( + __( 'PayPal Gateway Error', 'easy-digital-downloads' ), + __( 'Account not ready to accept payments.', 'easy-digital-downloads' ) + ); + + $error_message = current_user_can( 'manage_options' ) + ? __( 'Please connect your PayPal account in the gateway settings.', 'easy-digital-downloads' ) + : __( 'Unexpected authentication error. Please contact a site administrator.', 'easy-digital-downloads' ); + + wp_send_json_error( edd_build_errors_html( array( + 'paypal-error' => $error_message + ) ) ); + } + + try { + // Create pending payment in EDD. + $payment_args = array( + 'price' => $purchase_data['price'], + 'date' => $purchase_data['date'], + 'user_email' => $purchase_data['user_email'], + 'purchase_key' => $purchase_data['purchase_key'], + 'currency' => edd_get_currency(), + 'downloads' => $purchase_data['downloads'], + 'cart_details' => $purchase_data['cart_details'], + 'user_info' => $purchase_data['user_info'], + 'status' => 'pending', + 'gateway' => 'paypal_commerce' + ); + + $payment_id = edd_insert_payment( $payment_args ); + + if ( ! $payment_id ) { + throw new Gateway_Exception( + __( 'An unexpected error occurred. Please try again.', 'easy-digital-downloads' ), + 500, + sprintf( + 'Payment creation failed before sending buyer to PayPal. Payment data: %s', + json_encode( $payment_args ) + ) + ); + } + + $order_data = array( + 'intent' => 'CAPTURE', + 'purchase_units' => get_order_purchase_units( $payment_id, $purchase_data, $payment_args ), + 'application_context' => array( + //'locale' => get_locale(), // PayPal doesn't like this. Might be able to replace `_` with `-` + 'shipping_preference' => 'NO_SHIPPING', + 'user_action' => 'PAY_NOW', + 'return_url' => edd_get_checkout_uri(), + 'cancel_url' => edd_get_failed_transaction_uri( '?payment-id=' . urlencode( $payment_id ) ) + ), + 'payment_instructions' => array( + 'disbursement_mode' => 'INSTANT' + ) + ); + + // Add payer data if we have it. We won't have it when using Buy Now buttons. + if ( ! empty( $purchase_data['user_email'] ) ) { + $order_data['payer']['email_address'] = $purchase_data['user_email']; + } + if ( ! empty( $purchase_data['user_info']['first_name'] ) ) { + $order_data['payer']['name']['given_name'] = $purchase_data['user_info']['first_name']; + } + if ( ! empty( $purchase_data['user_info']['last_name'] ) ) { + $order_data['payer']['name']['surname'] = $purchase_data['user_info']['last_name']; + } + + /** + * Filters the arguments sent to PayPal. + * + * @param array $order_data API request arguments. + * @param array $purchase_data Purchase data. + * @param int $payment_id ID of the EDD payment. + * + * @since 2.11 + */ + $order_data = apply_filters( 'edd_paypal_order_arguments', $order_data, $purchase_data, $payment_id ); + + try { + $api = new API(); + $response = $api->make_request( 'v2/checkout/orders', $order_data ); + + if ( ! isset( $response->id ) && _is_item_total_mismatch( $response ) ) { + + edd_record_gateway_error( + __( 'PayPal Gateway Warning', 'easy-digital-downloads' ), + sprintf( + /* Translators: %s - Original order data sent to PayPal. */ + __( 'PayPal could not complete the transaction with the itemized breakdown. Original order data sent: %s', 'easy-digital-downloads' ), + json_encode( $order_data ) + ), + $payment_id + ); + + // Try again without the item breakdown. That way if we have an error in our totals the whole API request won't fail. + $order_data['purchase_units'] = array( + get_order_purchase_units_without_breakdown( $payment_id, $purchase_data, $payment_args ) + ); + + // Re-apply the filter. + $order_data = apply_filters( 'edd_paypal_order_arguments', $order_data, $purchase_data, $payment_id ); + + $response = $api->make_request( 'v2/checkout/orders', $order_data ); + } + + if ( ! isset( $response->id ) ) { + throw new Gateway_Exception( + __( 'An error occurred while communicating with PayPal. Please try again.', 'easy-digital-downloads' ), + $api->last_response_code, + sprintf( + 'Unexpected response when creating order: %s', + json_encode( $response ) + ) + ); + } + + edd_debug_log( sprintf( '-- Successful PayPal response. PayPal order ID: %s; EDD order ID: %d', esc_html( $response->id ), $payment_id ) ); + + edd_update_payment_meta( $payment_id, 'paypal_order_id', sanitize_text_field( $response->id ) ); + + /* + * Send successfully created order ID back. + * We also send back a new nonce, for verification in the next step: `capture_order()`. + * If the user was just logged into a new account, the previously sent nonce may have + * become invalid. + */ + $timestamp = time(); + wp_send_json_success( array( + 'paypal_order_id' => $response->id, + 'edd_order_id' => $payment_id, + 'nonce' => wp_create_nonce( 'edd_process_paypal' ), + 'timestamp' => $timestamp, + 'token' => \EDD\Utils\Tokenizer::tokenize( $timestamp ), + ) ); + } catch ( Authentication_Exception $e ) { + throw new Gateway_Exception( __( 'An authentication error occurred. Please try again.', 'easy-digital-downloads' ), $e->getCode(), $e->getMessage() ); + } catch ( API_Exception $e ) { + throw new Gateway_Exception( __( 'An error occurred while communicating with PayPal. Please try again.', 'easy-digital-downloads' ), $e->getCode(), $e->getMessage() ); + } + } catch ( Gateway_Exception $e ) { + if ( ! isset( $payment_id ) ) { + $payment_id = 0; + } + + $e->record_gateway_error( $payment_id ); + + wp_send_json_error( edd_build_errors_html( array( + 'paypal-error' => $e->getMessage() + ) ) ); + } +} + +add_action( 'edd_gateway_paypal_commerce', __NAMESPACE__ . '\create_order', 9 ); + +/** + * Captures the order in PayPal + * + * @since 2.11 + */ +function capture_order() { + edd_debug_log( 'PayPal - capture_order()' ); + try { + + $token = isset( $_POST['token'] ) ? sanitize_text_field( $_POST['token'] ) : ''; + $timestamp = isset( $_POST['timestamp'] ) ? sanitize_text_field( $_POST['timestamp'] ) : ''; + + if ( ! empty( $timestamp ) && ! empty( $token ) ) { + if ( !\EDD\Utils\Tokenizer::is_token_valid( $token, $timestamp ) ) { + throw new Gateway_Exception( + __('A validation error occurred. Please try again.', 'easy-digital-downloads'), + 403, + 'Token validation failed.' + ); + } + } elseif ( empty( $token ) && ! empty( $_POST['edd_process_paypal_nonce'] ) ) { + if ( ! wp_verify_nonce( $_POST['edd_process_paypal_nonce'], 'edd_process_paypal' ) ) { + throw new Gateway_Exception( + __( 'A validation error occurred. Please try again.', 'easy-digital-downloads' ), + 403, + 'Nonce validation failed.' + ); + } + } else { + throw new Gateway_Exception( + __( 'A validation error occurred. Please try again.', 'easy-digital-downloads' ), + 400, + 'Missing validation fields.' + ); + } + + if ( empty( $_POST['paypal_order_id'] ) ) { + throw new Gateway_Exception( + __( 'An unexpected error occurred. Please try again.', 'easy-digital-downloads' ), + 400, + 'Missing PayPal order ID during capture.' + ); + } + + try { + $api = new API(); + $response = $api->make_request( 'v2/checkout/orders/' . urlencode( $_POST['paypal_order_id'] ) . '/capture' ); + + edd_debug_log( sprintf( '-- PayPal Response code: %d; order ID: %s', $api->last_response_code, esc_html( $_POST['paypal_order_id'] ) ) ); + + if ( ! in_array( $api->last_response_code, array( 200, 201 ) ) ) { + $message = ! empty( $response->message ) ? $response->message : __( 'Failed to process payment. Please try again.', 'easy-digital-downloads' ); + + /* + * If capture failed due to funding source, we want to send a `restart` back to PayPal. + * @link https://developer.paypal.com/docs/checkout/integration-features/funding-failure/ + */ + if ( ! empty( $response->details ) && is_array( $response->details ) ) { + foreach ( $response->details as $detail ) { + if ( isset( $detail->issue ) && 'INSTRUMENT_DECLINED' === $detail->issue ) { + $message = __( 'Unable to complete your order with your chosen payment method. Please choose a new funding source.', 'easy-digital-downloads' ); + $retry = true; + break; + } + } + } + + throw new Gateway_Exception( + $message, + 400, + sprintf( 'Order capture failure. PayPal response: %s', json_encode( $response ) ) + ); + } + + $payment = $transaction_id = false; + if ( isset( $response->purchase_units ) && is_array( $response->purchase_units ) ) { + foreach ( $response->purchase_units as $purchase_unit ) { + if ( ! empty( $purchase_unit->reference_id ) ) { + $payment = edd_get_payment_by( 'key', $purchase_unit->reference_id ); + $transaction_id = isset( $purchase_unit->payments->captures[0]->id ) ? $purchase_unit->payments->captures[0]->id : false; + + if ( ! empty( $payment ) && isset( $purchase_unit->payments->captures[0]->status ) ) { + if ( 'COMPLETED' === strtoupper( $purchase_unit->payments->captures[0]->status ) ) { + $payment->status = 'complete'; + } elseif( 'DECLINED' === strtoupper( $purchase_unit->payments->captures[0]->status ) ) { + $payment->status = 'failed'; + } + } + break; + } + } + } + + if ( ! empty( $payment ) ) { + /** + * Buy Now Button + * + * Fill in missing data when using "Buy Now". This bypasses checkout so not all information + * was collected prior to payment. Instead, we pull it from the PayPal info. + */ + if ( empty( $payment->email ) ) { + if ( ! empty( $response->payer->email_address ) ) { + $payment->email = sanitize_text_field( $response->payer->email_address ); + } + if ( empty( $payment->first_name ) && ! empty( $response->payer->name->given_name ) ) { + $payment->first_name = sanitize_text_field( $response->payer->name->given_name ); + } + if ( empty( $payment->last_name ) && ! empty( $response->payer->name->surname ) ) { + $payment->last_name = sanitize_text_field( $response->payer->name->surname ); + } + + if ( empty( $payment->customer_id ) && ! empty( $payment->email ) ) { + $customer = new \EDD_Customer( $payment->email ); + + if ( $customer->id < 1 ) { + $customer->create( array( + 'email' => $payment->email, + 'name' => trim( sprintf( '%s %s', $payment->first_name, $payment->last_name ) ), + 'user_id' => $payment->user_id + ) ); + } + } + } + + if ( ! empty( $transaction_id ) ) { + $payment->transaction_id = sanitize_text_field( $transaction_id ); + + edd_insert_payment_note( $payment->ID, sprintf( + /* Translators: %s - transaction ID */ + __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ), + esc_html( $transaction_id ) + ) ); + } + + $payment->save(); + + if ( 'failed' === $payment->status ) { + $retry = true; + throw new Gateway_Exception( + __( 'Your payment was declined. Please try a new payment method.', 'easy-digital-downloads' ), + 400, + sprintf( 'Order capture failure. PayPal response: %s', json_encode( $response ) ) + ); + } + } + + wp_send_json_success( array( 'redirect_url' => edd_get_success_page_uri() ) ); + } catch ( Authentication_Exception $e ) { + throw new Gateway_Exception( __( 'An authentication error occurred. Please try again.', 'easy-digital-downloads' ), $e->getCode(), $e->getMessage() ); + } catch ( API_Exception $e ) { + throw new Gateway_Exception( __( 'An error occurred while communicating with PayPal. Please try again.', 'easy-digital-downloads' ), $e->getCode(), $e->getMessage() ); + } + } catch ( Gateway_Exception $e ) { + if ( ! isset( $payment_id ) ) { + $payment_id = 0; + } + + $e->record_gateway_error( $payment_id ); + + wp_send_json_error( array( + 'message' => edd_build_errors_html( array( + 'paypal_capture_failure' => $e->getMessage() + ) ), + 'retry' => isset( $retry ) ? $retry : false + ) ); + } +} + +add_action( 'wp_ajax_nopriv_edd_capture_paypal_order', __NAMESPACE__ . '\capture_order' ); +add_action( 'wp_ajax_edd_capture_paypal_order', __NAMESPACE__ . '\capture_order' ); + +/** + * Gets a fresh set of gateway options when a PayPal order is cancelled. + * @link https://github.com/awesomemotive/easy-digital-downloads/issues/8883 + * + * @since 2.11.3 + * @return void + */ +function cancel_order() { + $nonces = array(); + $gateways = edd_get_enabled_payment_gateways( true ); + foreach ( $gateways as $gateway_id => $gateway ) { + $nonces[ $gateway_id ] = wp_create_nonce( 'edd-gateway-selected-' . esc_attr( $gateway_id ) ); + } + + wp_send_json_success( + array( + 'nonces' => $nonces, + ) + ); +} +add_action( 'wp_ajax_nopriv_edd_cancel_paypal_order', __NAMESPACE__ . '\cancel_order' ); +add_action( 'wp_ajax_edd_cancel_paypal_order', __NAMESPACE__ . '\cancel_order' ); diff --git a/includes/gateways/paypal/class-account-status-validator.php b/includes/gateways/paypal/class-account-status-validator.php new file mode 100644 index 00000000000..f44c3ff3873 --- /dev/null +++ b/includes/gateways/paypal/class-account-status-validator.php @@ -0,0 +1,178 @@ +mode = $mode; + + // Set up base error objects. + $this->errors_for_credentials = new \WP_Error(); + $this->errors_for_merchant_account = new \WP_Error(); + $this->errors_for_webhook = new \WP_Error(); + } + + /** + * Checks everything. + * + * @since 2.11 + */ + public function check() { + $this->check_rest(); + $this->check_merchant_account(); + $this->check_webhook(); + } + + /** + * Checks for valid REST API credentials. + * + * @since 2.11 + */ + public function check_rest() { + $credentials = array( + 'client_id' => edd_get_option( 'paypal_' . $this->mode . '_client_id' ), + 'client_secret' => edd_get_option( 'paypal_' . $this->mode . '_client_secret' ), + ); + + foreach ( $credentials as $credential ) { + if ( empty( $credential ) ) { + $this->errors_for_credentials->add( 'no_credentials', __( 'Not connected.', 'easy-digital-downloads' ) ); + break; + } + } + } + + /** + * Determines if the merchant account is ready to accept payments. + * It's possible (I think) to have valid API credentials ( @see AccountStatusValidator::check_rest() ) + * but still be unable to start taking payments, such as because your account + * email hasn't been confirmed yet. + * + * @since 2.11 + */ + public function check_merchant_account() { + try { + $this->merchant_details = MerchantAccount::retrieve(); + $this->merchant_details->validate(); + + if ( ! $this->merchant_details->is_account_ready() ) { + foreach ( $this->merchant_details->get_errors()->get_error_codes() as $code ) { + $this->errors_for_merchant_account->add( $code, $this->merchant_details->get_errors()->get_error_message( $code ) ); + } + } + } catch ( Exceptions\MissingMerchantDetails $e ) { + $this->errors_for_merchant_account->add( 'missing_merchant_details', __( 'Missing merchant details from PayPal. Please reconnect and make sure you click the button to be redirected back to your site.', 'easy-digital-downloads' ) ); + } catch ( Exceptions\InvalidMerchantDetails $e ) { + $this->errors_for_merchant_account->add( 'invalid_merchant_details', $e->getMessage() ); + } + } + + /** + * Confirms that the webhook is set up and has all the necessary events registered. + * + * @since 2.11 + */ + public function check_webhook() { + try { + $this->webhook = Webhooks\get_webhook_details( $this->mode ); + if ( empty( $this->webhook->id ) ) { + throw new \Exception( __( 'Webhook not configured. Some actions may not work properly.', 'easy-digital-downloads' ) ); + } + + // Now compare the events to make sure we have them all. + $expected_events = array_keys( Webhooks\get_webhook_events( $this->mode ) ); + + if ( ! empty( $this->webhook->event_types ) && is_array( $this->webhook->event_types ) ) { + foreach ( $this->webhook->event_types as $event_type ) { + if ( ! empty( $event_type->name ) && ! empty( $event_type->status ) && 'ENABLED' === strtoupper( $event_type->status ) ) { + $this->enabled_webhook_events[] = $event_type->name; + } + } + } + + $missing_events = array_diff( $expected_events, $this->enabled_webhook_events ); + $number_missing = count( $missing_events ); + + if ( $number_missing ) { + $this->errors_for_webhook->add( 'missing_events', _n( + 'Webhook is configured but missing an event. Click "Sync Webhook" to correct this.', + 'Webhook is configured but missing events. Click "Sync Webhook" to correct this.', + $number_missing, + 'easy-digital-downloads' + ) ); + } + } catch ( \Exception $e ) { + $this->errors_for_webhook->add( 'webhook_missing', $e->getMessage() ); + } + } + +} diff --git a/includes/gateways/paypal/class-merchant-account.php b/includes/gateways/paypal/class-merchant-account.php new file mode 100644 index 00000000000..8491e0fd442 --- /dev/null +++ b/includes/gateways/paypal/class-merchant-account.php @@ -0,0 +1,193 @@ + $value ) { + $this->{$key} = $value; + } + + $this->wp_error = new \WP_Error(); + } + + /** + * Builds a new MerchantAccount object from a JSON object. + * + * @since 2.11 + * + * @param string $json + * + * @return MerchantAccount + */ + public static function from_json( $json ) { + $merchant_details = json_decode( $json, true ); + if ( empty( $merchant_details ) || ! is_array( $merchant_details ) ) { + $merchant_details = array(); + } + + return new MerchantAccount( $merchant_details ); + } + + /** + * Converts the account details to JSON. + * + * @since 2.11 + * + * @return string|false + */ + public function to_json() { + return json_encode( get_object_vars( $this ) ); + } + + /** + * Determines whether or not the details are valid. + * Note: This does NOT determine actual "ready to accept payments" status, it just + * verifies that we have all the information we need to determine that. + * + * @throws MissingMerchantDetails + * @throws InvalidMerchantDetails + */ + public function validate() { + if ( empty( $this->merchant_id ) ) { + throw new MissingMerchantDetails(); + } + + $required_properties = array( + 'merchant_id', + 'payments_receivable', + 'primary_email_confirmed', + 'products', + ); + + $valid_properties = array(); + foreach( $required_properties as $property ) { + if ( property_exists( $this, $property ) && ! is_null( $this->{$property} ) ) { + $valid_properties[] = $property; + } + } + + $difference = array_diff( $required_properties, $valid_properties ); + + if ( $difference ) { + throw new InvalidMerchantDetails( + 'Please click "Re-Check Payment Status" below to confirm your payment status.' + ); + } + } + + /** + * Determines whether or not the account is ready to accept payments. + * + * @since 2.11 + * + * @return bool + */ + public function is_account_ready() { + if ( ! $this->payments_receivable ) { + $this->wp_error->add( 'payments_receivable', __( 'Your account is unable to receive payments. Please contact PayPal customer support.', 'easy-digital-downloads' ) ); + } + + if ( ! $this->primary_email_confirmed ) { + $this->wp_error->add( + 'primary_email_confirmed', + __( 'Your PayPal email address needs to be confirmed.', 'easy-digital-downloads' ) + ); + } + + return empty( $this->wp_error->errors ); + } + + /** + * Retrieves errors preventing the account from being "ready". + * + * @see MerchantAccount::is_account_ready() + * + * @since 2.11 + * + * @return \WP_Error + */ + public function get_errors() { + return $this->wp_error; + } + + /** + * Returns the option name for the current mode. + * + * @since 2.11 + * + * @return string + */ + private static function get_option_name() { + return sprintf( + 'edd_paypal_%s_merchant_details', + edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE + ); + } + + /** + * Saves the merchant details. + * + * @since 2.11 + */ + public function save() { + update_option( self::get_option_name(), $this->to_json() ); + } + + /** + * Retrieves the saved merchant details. + * + * @since 2.11 + * + * @return MerchantAccount + * @throws \InvalidArgumentException + */ + public static function retrieve() { + return self::from_json( get_option( self::get_option_name(), '' ) ); + } + +} diff --git a/includes/gateways/paypal/class-paypal-api.php b/includes/gateways/paypal/class-paypal-api.php new file mode 100644 index 00000000000..81696c0c4c8 --- /dev/null +++ b/includes/gateways/paypal/class-paypal-api.php @@ -0,0 +1,271 @@ +mode = $mode; + + if ( self::MODE_SANDBOX === $mode ) { + $this->api_url = 'https://api-m.sandbox.paypal.com'; + } else { + $this->api_url = 'https://api-m.paypal.com'; + } + + if ( empty( $credentials ) ) { + $credentials = array( + 'client_id' => edd_get_option( 'paypal_' . $this->mode . '_client_id' ), + 'client_secret' => edd_get_option( 'paypal_' . $this->mode . '_client_secret' ), + ); + } + + $this->set_credentials( $credentials ); + } + + /** + * Magic getter + * + * @param string $property + * + * @since 2.10 + * @return mixed + */ + public function __get( $property ) { + return isset( $this->{$property} ) ? $this->{$property} : null; + } + + /** + * Sets the credentials to use for API requests. + * + * @param array $creds { + * Credentials to set. + * + * @type string $client_id PayPal client ID. + * @type string $client_secret PayPal client secret. + * @type string $cache_key Cache key used for storing the access token until it expires. Should be unique to + * the set of credentials. The mode is automatically appended, so should not be + * included manually. + * } + * + * @since 2.11 + * @throws Authentication_Exception + */ + public function set_credentials( $creds ) { + $creds = wp_parse_args( $creds, array( + 'client_id' => '', + 'client_secret' => '', + 'cache_key' => 'edd_paypal_commerce_access_token' + ) ); + + $required_creds = array( 'client_id', 'client_secret', 'cache_key' ); + + foreach ( $required_creds as $cred_id ) { + if ( empty( $creds[ $cred_id ] ) ) { + throw new Authentication_Exception( sprintf( + /* Translators: %s - The ID of the PayPal credential */ + __( 'Missing PayPal credential: %s', 'easy-digital-downloads' ), + $cred_id + ) ); + } + } + + foreach ( $creds as $cred_id => $cred_value ) { + $this->{$cred_id} = $cred_value; + } + + $this->token_cache_key = sanitize_key( $creds['cache_key'] . '_' . $this->mode ); + } + + /** + * Retrieves the access token. This checks cache first, and if the cached token isn't valid then + * a new one is generated from the API. + * + * @since 2.11 + * @return Token + * @throws API_Exception + */ + public function get_access_token() { + try { + $token = Token::from_json( (string) get_option( $this->token_cache_key ) ); + + return ! $token->is_expired() ? $token : $this->generate_access_token(); + } catch ( \RuntimeException $e ) { + return $this->generate_access_token(); + } + } + + /** + * Generates a new access token and caches it. + * + * @since 2.11 + * @return Token + * @throws API_Exception + */ + private function generate_access_token() { + $response = wp_remote_post( $this->api_url . '/v1/oauth2/token', array( + 'headers' => array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Authorization' => sprintf( 'Basic %s', base64_encode( sprintf( '%s:%s', $this->client_id, $this->client_secret ) ) ), + 'timeout' => 15 + ), + 'body' => array( + 'grant_type' => 'client_credentials' + ), + 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), + ) ); + + $body = json_decode( wp_remote_retrieve_body( $response ) ); + $code = intval( wp_remote_retrieve_response_code( $response ) ); + + if ( is_wp_error( $response ) ) { + throw new API_Exception( $response->get_error_message(), $code ); + } + + if ( ! empty( $body->error_description ) ) { + throw new API_Exception( $body->error_description, $code ); + } + + if ( 200 !== $code ) { + throw new API_Exception( sprintf( + /* Translators: %d - HTTP response code. */ + __( 'Unexpected response code: %d', 'easy-digital-downloads' ), + $code + ), $code ); + } + + $token = new Token( $body ); + + update_option( $this->token_cache_key, $token->to_json() ); + + return $token; + } + + /** + * Makes an API request. + * + * @param string $endpoint API endpoint. + * @param array $body Array of data to send in the request. + * @param array $headers Array of headers. + * @param string $method HTTP method. + * + * @since 2.11 + * @return mixed + * @throws API_Exception + */ + public function make_request( $endpoint, $body = array(), $headers = array(), $method = 'POST' ) { + $headers = wp_parse_args( $headers, array( + 'Content-Type' => 'application/json', + 'Authorization' => sprintf( 'Bearer %s', $this->get_access_token()->token() ), + 'PayPal-Partner-Attribution-Id' => EDD_PAYPAL_PARTNER_ATTRIBUTION_ID + ) ); + + $request_args = array( + 'method' => $method, + 'timeout' => 15, + 'headers' => $headers, + 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), + ); + + if ( ! empty( $body ) ) { + $request_args['body'] = json_encode( $body ); + } + + // In a few rare cases, we may be providing a full URL to `$endpoint` instead of just the path. + $api_url = ( 'https://' === substr( $endpoint, 0, 8 ) ) ? $endpoint : $this->api_url . '/' . $endpoint; + + $response = wp_remote_request( $api_url, $request_args ); + + if ( is_wp_error( $response ) ) { + throw new API_Exception( $response->get_error_message() ); + } + + $this->last_response_code = intval( wp_remote_retrieve_response_code( $response ) ); + + return json_decode( wp_remote_retrieve_body( $response ) ); + } + +} diff --git a/includes/gateways/paypal/class-token.php b/includes/gateways/paypal/class-token.php new file mode 100644 index 00000000000..25cc81e399a --- /dev/null +++ b/includes/gateways/paypal/class-token.php @@ -0,0 +1,112 @@ +created ) ) { + $token_object->created = time(); + } + + if ( ! $this->is_valid( $token_object ) ) { + throw new \RuntimeException( __( 'Invalid token.', 'easy-digital-downloads' ) ); + } + + $this->token_object = $token_object; + } + + /** + * Creates a new token from a JSON string. + * + * @param string $json + * + * @return Token + * @throws \Exception + */ + public static function from_json( $json ) { + return new Token( json_decode( $json ) ); + } + + /** + * Returns the token object as a JSON string. + * + * @since 2.11 + * @return string|false + */ + public function to_json() { + return json_encode( $this->token_object ); + } + + /** + * Determines whether or not the token has expired. + * + * @since 2.11 + * @return bool + */ + public function is_expired() { + // Regenerate tokens 10 minutes early, just in case. + $expires_in = $this->token_object->expires_in - ( 10 * MINUTE_IN_SECONDS ); + + return time() > $this->token_object->created + $expires_in; + } + + /** + * Returns the access token. + * + * @since 2.11 + * @return string + */ + public function token() { + return $this->token_object->access_token; + } + + /** + * Determines whether or not we have a valid token object. + * Note: This does not check the _expiration_ of the token, just validates that the expected + * data is _present_. + * + * @param object $token_object + * + * @since 2.11 + * @return bool + */ + private function is_valid( $token_object ) { + $required_properties = array( + 'created', + 'access_token', + 'expires_in' + ); + + foreach ( $required_properties as $property ) { + if ( ! isset( $token_object->{$property} ) ) { + return false; + } + } + + return true; + } + +} diff --git a/includes/gateways/paypal/deprecated.php b/includes/gateways/paypal/deprecated.php new file mode 100644 index 00000000000..d2cfe653f6b --- /dev/null +++ b/includes/gateways/paypal/deprecated.php @@ -0,0 +1,102 @@ +gateway ) { + return; + } + + $mode = ( 'live' === $payment->mode ) ? API::MODE_LIVE : API::MODE_SANDBOX; + + try { + $api = new API( $mode ); + } catch ( Exceptions\Authentication_Exception $e ) { + // If we don't have credentials. + return; + } + + $label = __( 'Refund Transaction in PayPal', 'easy-digital-downloads' ); + ?> + + ID ) ) { + return; + } + + if ( 'paypal_commerce' !== $payment->gateway || empty( $_POST['edd-paypal-commerce-refund'] ) ) { + return; + } + + // Payment status should be coming from "publish" or "revoked". + // @todo In 3.0 use `edd_get_refundable_order_statuses()` + if ( ! in_array( $payment->old_status, array( 'publish', 'complete', 'revoked', 'edd_subscription' ) ) ) { + return; + } + + // If the payment has already been refunded, bail. + if ( $payment->get_meta( '_edd_paypal_refunded', true ) ) { + return; + } + + // Process the refund. + try { + refund_transaction( $payment ); + } catch ( \Exception $e ) { + edd_insert_payment_note( $payment->ID, sprintf( + /* Translators: %s - The error message */ + __( 'Failed to refund transaction in PayPal. Error Message: %s', 'easy-digital-downloads' ), + $e->getMessage() + ) ); + } +} diff --git a/includes/gateways/paypal/exceptions/class-api-exception.php b/includes/gateways/paypal/exceptions/class-api-exception.php new file mode 100644 index 00000000000..781afab4a59 --- /dev/null +++ b/includes/gateways/paypal/exceptions/class-api-exception.php @@ -0,0 +1,17 @@ +debug_message = $debug_message; + + parent::__construct( $message, $code ); + } + + /** + * Records a gateway error based off this exception. + * + * @param int $payment_id + * + * @since 2.11 + */ + public function record_gateway_error( $payment_id = 0 ) { + $message = ! empty( $this->debug_message ) ? $this->debug_message : $this->getMessage(); + + edd_record_gateway_error( + __( 'PayPal Gateway Error', 'easy-digital-downloads' ), + sprintf( + /* Translators: %d - HTTP response code; %s - Error message */ + __( 'Response Code: %d; Message: %s', 'easy-digital-downloads' ), + $this->getCode(), + $message + ), + $payment_id + ); + } + +} diff --git a/includes/gateways/paypal/exceptions/class-invalid-merchant-details.php b/includes/gateways/paypal/exceptions/class-invalid-merchant-details.php new file mode 100644 index 00000000000..6d115895b2e --- /dev/null +++ b/includes/gateways/paypal/exceptions/class-invalid-merchant-details.php @@ -0,0 +1,15 @@ +check_merchant_account(); + + return empty( $validator->errors_for_merchant_account->errors ); +} + +/** + * Determines whether or not PayPal Standard should be enabled. + * This returns true if the store owner previously had a PayPal Standard connection but has not yet + * connected to the new REST API implementation. + * + * If PayPal Standard is enabled, then PayPal payments run through the legacy API. + * + * @param string $mode If omitted, current site mode is used. + * + * @since 2.11 + * @return bool + */ +function paypal_standard_enabled( $mode = '' ) { + if ( empty( $mode ) ) { + $mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE; + } + + $rest_connection = has_rest_api_connection( $mode ); + $enabled = ! $rest_connection && edd_get_option( 'paypal_email' ); + + /** + * Filters whether or not PayPal Standard is enabled. + * + * @since 2.11 + * + * @param bool $enabled + */ + return apply_filters( 'edd_paypal_standard_enabled', $enabled ); +} + +/** + * Returns the partner merchant ID for a given mode. + * + * @param string $mode If omitted, current site mode is used. + * + * @since 2.11 + * @return string + */ +function get_partner_merchant_id( $mode = '' ) { + if ( empty( $mode ) ) { + $mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE; + } + + if ( API::MODE_LIVE === $mode ) { + return EDD_PAYPAL_MERCHANT_ID; + } else { + return EDD_PAYPAL_SANDBOX_MERCHANT_ID; + } +} + +/** + * Returns the styles used for the PayPal buttons. + * + * @return array + */ +function get_button_styles() { + $styles = array( + 'layout' => 'vertical', + 'size' => 'responsive', + 'shape' => 'rect', + 'color' => 'gold', + 'label' => 'paypal' + ); + + if ( ! edd_is_checkout() ) { + $styles['layout'] = 'horizontal'; + $styles['label'] = 'buynow'; + } + + /** + * Filters the button styles. + * + * @since 2.11 + */ + return apply_filters( 'edd_paypal_smart_button_style', $styles ); +} + +/** + * Gets the PayPal purchase units without the individual item breakdown. + * + * @since 2.11.2 + * + * @param int $payment_id The payment/order ID. + * @param array $purchase_data The array of purchase data. + * @param array $payment_args The array created to insert the payment into the database. + * + * @return array + */ +function get_order_purchase_units_without_breakdown( $payment_id, $purchase_data, $payment_args ) { + $order_amount = array( + 'currency_code' => edd_get_currency(), + 'value' => (string) edd_sanitize_amount( $purchase_data['price'] ), + ); + if ( (float) $purchase_data['tax'] > 0 ) { + $order_amount['breakdown'] = array( + 'item_total' => array( + 'currency_code' => edd_get_currency(), + 'value' => (string) edd_sanitize_amount( $purchase_data['price'] - $purchase_data['tax'] ) + ), + 'tax_total' => array( + 'currency_code' => edd_get_currency(), + 'value' => (string) edd_sanitize_amount( $purchase_data['tax'] ), + ) + ); + } + + return array( + 'reference_id' => $payment_args['purchase_key'], + 'amount' => $order_amount, + 'custom_id' => $payment_id + ); +} + +/** + * Gets the PayPal purchase units. The order breakdown includes the order items, tax, and discount. + * + * @since 2.11.2 + * @param int $payment_id The payment/order ID. + * @param array $purchase_data The array of purchase data. + * @param array $payment_args The array created to insert the payment into the database. + * @return array + */ +function get_order_purchase_units( $payment_id, $purchase_data, $payment_args ) { + + $currency = edd_get_currency(); + $order_subtotal = $purchase_data['subtotal']; + $items = get_order_items( $purchase_data ); + // Adjust the order subtotal if any items are discounted. + foreach ( $items as &$item ) { + // A discount can be negative, so cast it to an absolute value for comparison. + if ( (float) abs( $item['discount'] ) > 0 ) { + $order_subtotal -= $item['discount']; + } + + // The discount amount is not passed to PayPal as part of the $item. + unset( $item['discount'] ); + } + + $discount = 0; + // Fees which are not item specific need to be added to the PayPal data as order items. + if ( ! empty( $purchase_data['fees'] ) ) { + foreach ( $purchase_data['fees'] as $fee ) { + if ( ! empty( $fee['download_id'] ) ) { + continue; + } + // Positive fees. + if ( floatval( $fee['amount'] ) > 0 ) { + $items[] = array( + 'name' => stripslashes_deep( html_entity_decode( wp_strip_all_tags( $fee['label'] ), ENT_COMPAT, 'UTF-8' ) ), + 'unit_amount' => array( + 'currency_code' => $currency, + 'value' => (string) edd_sanitize_amount( $fee['amount'] ), + ), + 'quantity' => 1, + ); + $order_subtotal += abs( $fee['amount'] ); + } else { + // This is a negative fee (discount) not assigned to a specific Download + $discount += abs( $fee['amount'] ); + } + } + } + + $order_amount = array( + 'currency_code' => $currency, + 'value' => (string) edd_sanitize_amount( $purchase_data['price'] ), + 'breakdown' => array( + 'item_total' => array( + 'currency_code' => $currency, + 'value' => (string) edd_sanitize_amount( $order_subtotal ), + ), + ), + ); + + $tax = (float) $purchase_data['tax'] > 0 ? $purchase_data['tax'] : 0; + if ( $tax > 0 ) { + $order_amount['breakdown']['tax_total'] = array( + 'currency_code' => $currency, + 'value' => (string) edd_sanitize_amount( $tax ), + ); + } + + // This is only added by negative global fees. + if ( $discount > 0 ) { + $order_amount['breakdown']['discount'] = array( + 'currency_code' => $currency, + 'value' => (string) edd_sanitize_amount( $discount ), + ); + } + + return array( + wp_parse_args( array( + 'amount' => $order_amount, + 'items' => $items + ), get_order_purchase_units_without_breakdown( $payment_id, $purchase_data, $payment_args ) ) + ); +} + +/** + * Gets an array of order items, formatted for PayPal, from the $purchase_data. + * + * @since 2.11.2 + * @param array $purchase_data + * @return array + */ +function get_order_items( $purchase_data ) { + // Create an array of items for the order. + $items = array(); + if ( ! is_array( $purchase_data['cart_details'] ) || empty( $purchase_data['cart_details'] ) ) { + return $items; + } + $i = 0; + foreach ( $purchase_data['cart_details'] as $item ) { + $item_amount = ( $item['subtotal'] / $item['quantity'] ) - ( $item['discount'] / $item['quantity'] ); + + if ( $item_amount <= 0 ) { + $item_amount = 0; + } + + $substr_func = function_exists( 'mb_substr' ) ? 'mb_substr' : 'substr'; + $name = $substr_func( edd_get_cart_item_name( $item ), 0, 127 ); + + $items[ $i ] = array( + 'name' => stripslashes_deep( html_entity_decode( $name, ENT_COMPAT, 'UTF-8' ) ), + 'quantity' => $item['quantity'], + 'unit_amount' => array( + 'currency_code' => edd_get_currency(), + 'value' => (string) edd_sanitize_amount( $item_amount ), + ), + 'discount' => $item['discount'], // This is unset later and never sent to PayPal. + ); + if ( edd_use_skus() ) { + $sku = edd_get_download_sku( $item['id'] ); + if ( ! empty( $sku ) && '-' !== $sku ) { + $items[ $i ]['sku'] = $sku; + } + } + $i++; + } + + return $items; +} + +/** + * Attempts to detect if there's an item total mismatch. This means the individual item breakdowns don't + * add up to our proposed totals. + * + * @link https://github.com/easydigitaldownloads/easy-digital-downloads/pull/8835#issuecomment-921759101 + * @internal Not intended for public use. + * + * @since 2.11.2 + * + * @param object $response + * + * @return bool + */ +function _is_item_total_mismatch( $response ) { + if ( ! isset( $response->details ) || ! is_array( $response->details ) ) { + return false; + } + + foreach( $response->details as $detail ) { + if ( ! empty( $detail->issue ) && 'ITEM_TOTAL_MISMATCH' === strtoupper( $detail->issue ) ) { + return true; + } + } + + return false; +} diff --git a/includes/gateways/paypal/gateway-filters.php b/includes/gateways/paypal/gateway-filters.php new file mode 100644 index 00000000000..ac25096fc3d --- /dev/null +++ b/includes/gateways/paypal/gateway-filters.php @@ -0,0 +1,74 @@ +mode ) ? 'sandbox.' : ''; + $transaction_url = 'https://' . urlencode( $subdomain ) . 'paypal.com/activity/payment/' . urlencode( $transaction_id ); + + return '' . esc_html( $transaction_id ) . ''; +} + +add_filter( 'edd_payment_details_transaction_id-paypal_commerce', __NAMESPACE__ . '\link_transaction_id', 10, 2 ); + +/** + * By default, EDD_Payment converts an empty transaction ID to be the ID of the payment. + * We don't want that to happen... Empty should be empty. + * + * @since 2.11 + */ +add_filter( 'edd_get_payment_transaction_id-paypal_commerce', '__return_false' ); diff --git a/includes/gateways/paypal/integrations.php b/includes/gateways/paypal/integrations.php new file mode 100644 index 00000000000..36bf64d05c4 --- /dev/null +++ b/includes/gateways/paypal/integrations.php @@ -0,0 +1,25 @@ + 0 ) { + + // Append the data. + $encoded_data .= $arg_separator . $post_data; + + } else { + + // Check if POST is empty. + if ( empty( $_POST ) ) { + + // Nothing to do. + ipn_debug_log( 'post data not detected, bailing' ); + return; + + } else { + + // Loop through each POST. + foreach ( $_POST as $key => $value ) { + + // Encode the value and append the data. + $encoded_data .= $arg_separator . "$key=" . urlencode( $value ); + + } + + } + + } + + // Convert collected post data to an array. + parse_str( $encoded_data, $encoded_data_array ); + + // We're always going to validate the IPN here... + if ( ! edd_is_test_mode() ) { + + ipn_debug_log( 'preparing to verify IPN data' ); + + // Validate the IPN. + $remote_post_vars = array( + 'method' => 'POST', + 'timeout' => 45, + 'redirection' => 5, + 'httpversion' => '1.1', + 'blocking' => true, + 'body' => $encoded_data_array, + 'headers' => array( + 'host' => 'www.paypal.com', + 'connection' => 'close', + 'content-type' => 'application/x-www-form-urlencoded', + 'post' => '/cgi-bin/webscr HTTP/1.1', + + ), + 'user-agent' => 'Easy Digital Downloads/' . EDD_VERSION . '; ' . get_bloginfo( 'name' ), + ); + + // Get response. + $api_response = wp_remote_post( edd_get_paypal_redirect(), $remote_post_vars ); + $body = wp_remote_retrieve_body( $api_response ); + + if ( is_wp_error( $api_response ) ) { + /* Translators: %s - IPN Verification response */ + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid PayPal Commerce/Express IPN verification response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $api_response ) ) ); + ipn_debug_log( 'verification failed. Data: ' . var_export( $body, true ) ); + status_header( 401 ); + return; // Something went wrong. + } + + if ( 'VERIFIED' !== $body ) { + /* Translators: %s - IPN Verification response */ + edd_record_gateway_error( __( 'IPN Error', 'easy-digital-downloads' ), sprintf( __( 'Invalid PayPal Commerce/Express IPN verification response. IPN data: %s', 'easy-digital-downloads' ), json_encode( $api_response ) ) ); + ipn_debug_log( 'verification failed. Data: ' . var_export( $body, true ) ); + status_header( 401 ); + return; // Response not okay. + } + + // We've verified that the IPN Check passed, we can proceed with processing the IPN data sent to us. + $verified = true; + + } + + /** + * The processIpn() method returned true if the IPN was "VERIFIED" and false if it was "INVALID". + */ + if ( ( $verified || edd_get_option( 'disable_paypal_verification' ) ) || isset( $_POST['verification_override'] ) || edd_is_test_mode() ) { + + status_header( 200 ); + + /** + * Note: Amounts get more properly sanitized on insert. + * + * @see EDD_Subscription::add_payment() + */ + if ( isset( $posted['amount'] ) ) { + $amount = (float) $posted['amount']; + } elseif ( isset( $posted['mc_gross'] ) ) { + $amount = (float) $posted['mc_gross']; + } else { + $amount = 0; + } + + $txn_type = isset( $posted['txn_type'] ) ? strtolower( $posted['txn_type'] ) : ''; + $payment_status = isset( $posted['payment_status'] ) ? strtolower( $posted['payment_status'] ) : ''; + $currency_code = isset( $posted['mc_currency'] ) ? $posted['mc_currency'] : $posted['currency_code']; + $transaction_id = isset( $posted['txn_id'] ) ? $posted['txn_id'] : ''; + + if ( empty( $txn_type ) && empty( $payment_status ) ) { + ipn_debug_log( 'No txn_type or payment_status in the IPN, bailing' ); + return; + } + + // Process webhooks from recurring first, as that is where most of the missing actions will come from. + if ( class_exists( 'EDD_Recurring' ) && isset( $posted['recurring_payment_id'] ) ) { + $posted = apply_filters( 'edd_recurring_ipn_post', $_POST ); // allow $_POST to be modified. + + $subscription = new \EDD_Subscription( $posted['recurring_payment_id'], true ); + + + // Bail if this is the very first payment. + if ( ! empty( $posted['payment_date'] ) && date( 'Y-n-d', strtotime( $subscription->created ) ) == date( 'Y-n-d', strtotime( $posted['payment_date'] ) ) ) { + ipn_debug_log( 'IPN for subscription ' . $subscription->id . ': processing stopped because this is the initial payment.' ); + return; + } + + $parent_payment = edd_get_payment( $subscription->parent_payment_id ); + if ( 'paypal_commerce' !== $parent_payment->gateway ) { + ipn_debug_log( 'This is not for PayPal Commerce - bailing' ); + return; + } + + if ( empty( $subscription->id ) || $subscription->id < 1 ) { + ipn_debug_log( 'no matching subscription found detected, bailing. Data: ' . var_export( $posted, true ) ); + die( 'No subscription found' ); + } + + ipn_debug_log( 'Processing ' . $txn_type . ' IPN for subscription ' . $subscription->id ); + + // Subscriptions. + switch ( $txn_type ) : + + case 'recurring_payment': + case 'recurring_payment_outstanding_payment': + $transaction_exists = edd_get_order_transaction_by( 'transaction_id', $transaction_id ); + if ( ! empty( $transaction_exists ) ) { + ipn_debug_log( 'Transaction ID ' . $transaction_id . ' arlready processed.' ); + return; + } + + $sub_currency = edd_get_payment_currency_code( $subscription->parent_payment_id ); + + // verify details. + if ( ! empty( $sub_currency ) && strtolower( $currency_code ) != strtolower( $sub_currency ) ) { + + // the currency code is invalid + // @TODO: Does this need a parent_id for better error organization? + /* Translators: %s - The payment data sent via the IPN */ + edd_record_gateway_error( __( 'Invalid Currency Code', 'easy-digital-downloads' ), sprintf( __( 'The currency code in an IPN request did not match the site currency code. Payment data: %s', 'easy-digital-downloads' ), json_encode( $posted ) ) ); + + ipn_debug_log( 'subscription ' . $subscription->id . ': invalid currency code detected in IPN data: ' . var_export( $posted, true ) ); + + die( 'invalid currency code' ); + + } + + if ( 'failed' === $payment_status ) { + if ( 'failing' === $subscription->status ) { + ipn_debug_log( 'Subscription ID ' . $subscription->id . ' arlready failing.' ); + return; + } + + $transaction_link = '' . $transaction_id . ''; + /* Translators: %s - The transaction ID of the failed payment */ + $subscription->add_note( sprintf( __( 'Transaction ID %s failed in PayPal', 'easy-digital-downloads' ), $transaction_link ) ); + $subscription->failing(); + + ipn_debug_log( 'subscription ' . $subscription->id . ': payment failed in PayPal' ); + + die( 'Subscription payment failed' ); + + } + + ipn_debug_log( 'subscription ' . $subscription->id . ': preparing to insert renewal payment' ); + + // Build the array for adding a subscription order. + $subscription_payment_args = array( + 'amount' => $amount, + 'transaction_id' => $transaction_id, + ); + + // Create a DateTime object of the payment_date, so we can adjust as needed. + $subscription_payment_date = new \DateTime( $posted['payment_date'] ); + + // To make sure we don't inadverntatly fail, make sure the date was parsed correctly before working with it. + if ( $subscription_payment_date instanceof \DateTime ) { + /** + * Convert to GMT, as that is what EDD 3.0 expects the times to be in. + */ + $subscription_payment_date->setTimezone( new \DateTimeZone( 'GMT' ) ); + + // Now add the date into the arguments for creating the renewal payment. + $subscription_payment_args['date'] = $subscription_payment_date->format( 'Y-m-d H:i:s' ); + } + + // when a user makes a recurring payment. + $payment_id = $subscription->add_payment( $subscription_payment_args ); + + if ( ! empty( $payment_id ) ) { + ipn_debug_log( 'subscription ' . $subscription->id . ': renewal payment was recorded successfully, preparing to renew subscription' ); + $subscription->renew( $payment_id ); + + if ( 'recurring_payment_outstanding_payment' === $txn_type ) { + /* Translators: %s - The collected outstanding balance of the subscription */ + $subscription->add_note( sprintf( __( 'Outstanding subscription balance of %s collected successfully.', 'easy-digital-downloads' ), $amount ) ); + } + } else { + ipn_debug_log( 'subscription ' . $subscription->id . ': renewal payment creation appeared to fail.' ); + } + + die( 'Subscription payment successful' ); + + break; + + case 'recurring_payment_profile_cancel': + case 'recurring_payment_suspended': + case 'recurring_payment_suspended_due_to_max_failed_payment': + if ( 'cancelled' === $subscription->status ) { + ipn_debug_log( 'Subscription ID ' . $subscription->id . ' arlready cancelled.' ); + return; + } + + $subscription->cancel(); + ipn_debug_log( 'subscription ' . $subscription->id . ': subscription cancelled.' ); + + + die( 'Subscription cancelled' ); + + break; + + case 'recurring_payment_failed': + if ( 'failing' === $subscription->status ) { + ipn_debug_log( 'Subscription ID ' . $subscription->id . ' arlready failing.' ); + return; + } + + $subscription->failing(); + ipn_debug_log( 'subscription ' . $subscription->id . ': subscription failing.' ); + do_action( 'edd_recurring_payment_failed', $subscription ); + + break; + + case 'recurring_payment_expired': + if ( 'completed' === $subscription->status ) { + ipn_debug_log( 'Subscription ID ' . $subscription->id . ' arlready completed.' ); + return; + } + + $subscription->complete(); + ipn_debug_log( 'subscription ' . $subscription->id . ': subscription completed.' ); + + die( 'Subscription completed' ); + break; + + endswitch; + } + + // We've processed recurring, now let's handle non-recurring IPNs. + + // First, if this isn't a refund or reversal, we don't need to process anything. + $statuses_to_process = array( 'refunded', 'reversed' ); + if ( ! in_array( $payment_status, $statuses_to_process, true ) ) { + ipn_debug_log( 'Payment Status was not a status we need to process: ' . $payment_status ); + return; + } + + $order_id = 0; + + if ( ! empty( $posted['parent_txn_id'] ) ) { + $order_id = edd_get_order_id_from_transaction_id( $posted['parent_txn_id'] ); + } + + /** + * This section of the IPN should only affect processing refunds or returns on orders made previous, not new + * orders, so we can just look for the parent_txn_id, and if it's not here, bail as this is a new order that + * should be handeled with webhooks. + */ + if ( empty( $order_id ) ) { + ipn_debug_log( 'IPN Track ID ' . $posted['ipn_track_id'] . ' does not need to be processed as it does not belong to an existing record.' ); + return; + } + + $order = edd_get_order( $order_id ); + if ( 'paypal_commerce' !== $order->gateway ) { + ipn_debug_log( 'Order ' . $order_id . ' was not with PayPal Commerce' ); + return; + } + + if ( 'refunded' === $order->status ) { + ipn_debug_log( 'Order ' . $order_id . ' is already refunded' ); + } + + $transaction_exists = edd_get_order_transaction_by( 'transaction_id', $transaction_id ); + if ( ! empty( $transaction_exists ) ) { + ipn_debug_log( 'Refund transaction for ' . $transaction_id . ' already exists' ); + return; + } + + $order_amount = edd_get_payment_amount( $order->id ); + $refunded_amount = ! empty( $amount ) ? $amount : $order_amount; + $currency = ! empty( $currency_code ) ? $currency_code : $order->currency; + + ipn_debug_log( 'Processing a refund for original transaction ' . $order->get_transaction_id() ); + + /* Translators: %1$s - Amount refunded; %2$s - Original payment ID; %3$s - Refund transaction ID */ + $payment_note = sprintf( + esc_html__( 'Amount: %1$s; Payment transaction ID: %2$s; Refund transaction ID: %3$s', 'easy-digital-downloads' ), + edd_currency_filter( edd_format_amount( $refunded_amount ), $currency ), + esc_html( $order->get_transaction_id() ), + esc_html( $transaction_id ) + ); + + // Partial refund. + if ( (float) $refunded_amount < (float) $order_amount ) { + edd_add_note( + array( + 'object_type' => 'order', + 'object_id' => $order->id, + 'content' => __( 'Partial refund processed in PayPal.', 'easy-digital-downloads' ) . ' ' . $payment_note, + ) + ); + edd_update_order_status( $order->id, 'partially_refunded' ); + } else { + // Full refund. + edd_add_note( + array( + 'object_type' => 'order', + 'object_id' => $order->id, + 'content' => __( 'Full refund processed in PayPal.', 'easy-digital-downloads' ) . ' ' . $payment_note, + ) + ); + edd_update_order_status( $order->id, 'refunded' ); + } + + die( 'Refund processed' ); + } else { + ipn_debug_log( 'verification failed, bailing.' ); + status_header( 400 ); + die( 'invalid IPN' ); + + } +} +add_action( 'init', __NAMESPACE__ . '\listen_for_ipn' ); + +/** + * Helper method to prefix any calls to edd_debug_log + * + * @since 3.1.0.3 + * @uses edd_debug_log + * + * @param string $message The message to send to the debug logging. + */ +function ipn_debug_log( $message ) { + edd_debug_log( 'PayPal Commerce IPN: ' . $message ); +} diff --git a/includes/gateways/paypal/paypal.php b/includes/gateways/paypal/paypal.php new file mode 100644 index 00000000000..deed5f12bd1 --- /dev/null +++ b/includes/gateways/paypal/paypal.php @@ -0,0 +1,79 @@ +gateway ) { + return; + } + + $mode = ( 'live' === $order->mode ) ? API::MODE_LIVE : API::MODE_SANDBOX; + + try { + new API( $mode ); + } catch ( Exceptions\Authentication_Exception $e ) { + // If we don't have credentials. + return; + } + ?> +
    +
    + + +
    +
    + gateway ) || 'paypal_commerce' !== $order->gateway ) { + return; + } + + // Get our data out of the serialized string. + parse_str( $_POST['data'], $form_data ); + + if ( empty( $form_data['edd-paypal-commerce-refund'] ) ) { + edd_add_note( array( + 'object_id' => $order_id, + 'object_type' => 'order', + 'user_id' => is_admin() ? get_current_user_id() : 0, + 'content' => __( 'Transaction not refunded in PayPal, as checkbox was not selected.', 'easy-digital-downloads' ) + ) ); + + return; + } + + $refund = edd_get_order( $refund_id ); + if ( empty( $refund->total ) ) { + return; + } + + try { + refund_transaction( $order, $refund ); + } catch ( \Exception $e ) { + edd_debug_log( sprintf( + 'Failure when processing refund #%d. Message: %s', + $refund->id, + $e->getMessage() + ), true ); + + edd_add_note( array( + 'object_id' => $order->id, + 'object_type' => 'order', + 'user_id' => is_admin() ? get_current_user_id() : 0, + 'content' => sprintf( + /* Translators: %d - ID of the refund; %s - error message from PayPal */ + __( 'Failure when processing PayPal refund #%d: %s', 'easy-digital-downloads' ), + $refund->id, + $e->getMessage() + ) + ) ); + } +}, 10, 3 ); + +/** + * Refunds a transaction in PayPal. + * + * @link https://developer.paypal.com/docs/api/payments/v2/#captures_refund + * + * @param \EDD_Payment|Order $payment_or_order + * @param Order|null $refund_object + * + * @since 2.11 + * @throws Authentication_Exception + * @throws API_Exception + * @throws \Exception + */ +function refund_transaction( $payment_or_order, Order $refund_object = null ) { + /* + * Internally we want to work with an Order object, but we also need + * an EDD_Payment object for backwards compatibility in the hooks. + */ + $order = $payment = false; + if ( $payment_or_order instanceof Order ) { + $order = $payment_or_order; + $payment = edd_get_payment( $order->id ); + } elseif ( $payment_or_order instanceof \EDD_Payment ) { + $payment = $payment_or_order; + $order = edd_get_order( $payment->ID ); + } + + if ( empty( $order ) || ! $order instanceof Order ) { + return; + } + + $transaction_id = $order->get_transaction_id(); + + if ( empty( $transaction_id ) ) { + throw new \Exception( __( 'Missing transaction ID.', 'easy-digital-downloads' ) ); + } + + $mode = ( 'live' === $order->mode ) ? API::MODE_LIVE : API::MODE_SANDBOX; + + $api = new API( $mode ); + + $args = $refund_object instanceof Order ? array( 'invoice_id' => $refund_object->id ) : array(); + if ( $refund_object instanceof Order && abs( $refund_object->total ) !== abs( $order->total ) ) { + $args['amount'] = array( + 'value' => abs( $refund_object->total ), + 'currency_code' => $refund_object->currency, + ); + } + + $response = $api->make_request( + 'v2/payments/captures/' . urlencode( $transaction_id ) . '/refund', + $args, + array( + 'Prefer' => 'return=representation', + ) + ); + + if ( 201 !== $api->last_response_code ) { + throw new API_Exception( sprintf( + /* Translators: %d - The HTTP response code; %s - Full API response from PayPal */ + __( 'Unexpected response code: %d. Response: %s', 'easy-digital-downloads' ), + $api->last_response_code, + json_encode( $response ) + ), $api->last_response_code ); + } + + if ( empty( $response->status ) || 'COMPLETED' !== strtoupper( $response->status ) ) { + throw new API_Exception( sprintf( + /* Translators: %s - API response from PayPal */ + __( 'Missing or unexpected refund status. Response: %s', 'easy-digital-downloads' ), + json_encode( $response ) + ) ); + } + + // At this point we can assume it was successful. + edd_update_order_meta( $order->id, '_edd_paypal_refunded', true ); + + if ( ! empty( $response->id ) ) { + // Add a note to the original order, and, if provided, the new refund object. + if ( isset( $response->amount->value ) ) { + $note_message = sprintf( + /* Translators: %1$s - amount refunded; %$2$s - transaction ID. */ + __( '%1$s refunded in PayPal. Refund transaction ID: %2$s', 'easy-digital-downloads' ), + edd_currency_filter( edd_format_amount( $response->amount->value ), $order->currency ), + esc_html( $response->id ) + ); + } else { + $note_message = sprintf( + /* Translators: %s - ID of the refund in PayPal */ + __( 'Successfully refunded in PayPal. Refund transaction ID: %s', 'easy-digital-downloads' ), + esc_html( $response->id ) + ); + } + + $note_object_ids = array( $order->id ); + if ( $refund_object instanceof Order ) { + $note_object_ids[] = $refund_object->id; + } + + foreach ( $note_object_ids as $note_object_id ) { + edd_add_note( array( + 'object_id' => $note_object_id, + 'object_type' => 'order', + 'user_id' => is_admin() ? get_current_user_id() : 0, + 'content' => $note_message + ) ); + } + + // Add a negative transaction. + if ( $refund_object instanceof Order && isset( $response->amount->value ) ) { + edd_add_order_transaction( array( + 'object_id' => $refund_object->id, + 'object_type' => 'order', + 'transaction_id' => sanitize_text_field( $response->id ), + 'gateway' => 'paypal_commerce', + 'status' => 'complete', + 'total' => edd_negate_amount( $response->amount->value ), + ) ); + } + } + + /** + * Triggers after a successful refund. + * + * @param \EDD_Payment $payment + */ + do_action( 'edd_paypal_refund_purchase', $payment ); +} diff --git a/includes/gateways/paypal/scripts.php b/includes/gateways/paypal/scripts.php new file mode 100644 index 00000000000..8b046949653 --- /dev/null +++ b/includes/gateways/paypal/scripts.php @@ -0,0 +1,180 @@ + urlencode( $api->client_id ), + 'currency' => urlencode( strtoupper( edd_get_currency() ) ), + 'intent' => 'capture', + 'disable-funding' => 'card,credit,bancontact,blik,eps,giropay,ideal,mercadopago,mybank,p24,sepa,sofort,venmo' + ) ); + + wp_register_script( + 'sandhills-paypal-js-sdk', + esc_url_raw( add_query_arg( array_filter( $sdk_query_args ), 'https://www.paypal.com/sdk/js' ) ) + ); + + wp_register_script( + 'edd-paypal', + EDD_PLUGIN_URL . 'assets/js/paypal-checkout.js', + array( + 'sandhills-paypal-js-sdk', + 'jquery', + 'edd-ajax' + ), + EDD_VERSION, + true + ); + + if ( edd_is_checkout() || $force_load ) { + maybe_enqueue_polyfills(); + + wp_enqueue_script( 'sandhills-paypal-js-sdk' ); + wp_enqueue_script( 'edd-paypal' ); + + $paypal_script_vars = array( + /** + * Filters the order approval handler. + * + * @since 2.11 + */ + 'approvalAction' => apply_filters( 'edd_paypal_on_approve_action', 'edd_capture_paypal_order' ), + 'defaultError' => edd_build_errors_html( array( + 'paypal-error' => esc_html__( 'An unexpected error occurred. Please try again.', 'easy-digital-downloads' ) + ) ), + 'intent' => ! empty( $sdk_query_args['intent'] ) ? $sdk_query_args['intent'] : 'capture', + 'style' => get_button_styles(), + ); + + wp_localize_script( 'edd-paypal', 'eddPayPalVars', $paypal_script_vars ); + } +} + +add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\register_js', 100 ); + +/** + * Removes the "?ver=" query arg from the PayPal JS SDK URL, because PayPal will throw an error + * if it's included. + * + * @param string $url + * + * @since 2.11 + * @return string + */ +function remove_ver_query_arg( $url ) { + $sdk_url = 'https://www.paypal.com/sdk/js'; + + if ( false !== strpos( $url, $sdk_url ) ) { + $new_url = preg_split( "/(&ver|\?ver)/", $url ); + + return $new_url[0]; + } + + return $url; +} + +add_filter( 'script_loader_src', __NAMESPACE__ . '\remove_ver_query_arg', 100 ); + +/** + * Adds data attributes to the PayPal JS SDK + '); + } ); +} +add_action( 'edd_process_file_download_after_login', 'edd_redirect_file_download_after_login', 10, 2 ); + +/** + * Filter removed in EDD 2.7 + * + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5450 + */ +// add_action( 'edd_process_download_pre_record_log', 'edd_check_file_url_head', 10, 3 ); diff --git a/includes/process-purchase.php b/includes/process-purchase.php index 3e78ee5d084..700f8c82680 100755 --- a/includes/process-purchase.php +++ b/includes/process-purchase.php @@ -2,13 +2,15 @@ /** * Process Purchase * - * @package Easy Digital Downloads - * @subpackage Process Purchase - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Functions + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** * Process Purchase Form @@ -17,201 +19,356 @@ * * @access private * @since 1.0 - * @version 1.0.8.1 * @return void -*/ - + */ function edd_process_purchase_form() { - global $edd_options; - // no need to run on admin - if ( is_admin() ) - return; + do_action( 'edd_pre_process_purchase' ); - // verify the nonce for this action - if ( ! isset( $_POST['edd-nonce'] ) || ! wp_verify_nonce( $_POST['edd-nonce'], 'edd-purchase-nonce' ) ) - return; + // Make sure the cart isn't empty. + if ( ! edd_get_cart_contents() && ! edd_cart_has_fees() ) { + $valid_data = false; + edd_set_error( 'empty_cart', __( 'Your cart is empty', 'easy-digital-downloads' ) ); + } else { + // Validate the form $_POST data. + $valid_data = edd_purchase_form_validate_fields(); - // validate the form $_POST data - $valid_data = edd_purchase_form_validate_fields(); + // Allow themes and plugins to hook to errors. + do_action( 'edd_checkout_error_checks', $valid_data, $_POST ); + } - // allow themes and plugins to hoook to errors - do_action('edd_checkout_error_checks', $_POST); + $is_ajax = isset( $_POST['edd_ajax'] ); - // check errors - if ( false !== $errors = edd_get_errors() ) { - // we have errors, send back to checkout - edd_send_back_to_checkout('?payment-mode=' . $valid_data['gateway']); - exit; + if ( $is_ajax ) { + if ( ! isset( $_POST['edd-process-checkout-nonce'] ) ) { + edd_debug_log( __( 'Missing nonce when processing checkout. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4', 'easy-digital-downloads' ), true ); + } + + $nonce = isset( $_POST['edd-process-checkout-nonce'] ) ? sanitize_text_field( $_POST['edd-process-checkout-nonce'] ) : ''; + $nonce_verified = wp_verify_nonce( $nonce, 'edd-process-checkout' ); + + if ( false === $nonce_verified ) { + edd_set_error( 'checkout-nonce-error', __( 'Error processing purchase. Please reload the page and try again.', 'easy-digital-downloads' ) ); + } } - // check user - if ( false === $user = edd_get_purchase_form_user( $valid_data ) ) { - // something went wrong when collecting data, send back to checkout - edd_send_back_to_checkout('?payment-mode=' . $valid_data['gateway']); - exit; + // Process the login form. + if ( isset( $_POST['edd_login_submit'] ) ) { + edd_process_purchase_login(); } + // Validate the user. + $user = edd_get_purchase_form_user( $valid_data, $is_ajax ); + + // Let extensions validate fields after user is logged in if user has used login/registration form. + do_action( 'edd_checkout_user_error_checks', $user, $valid_data, $_POST ); - // setup user information + if ( false === $valid_data || edd_get_errors() || ! $user ) { + if ( $is_ajax ) { + do_action( 'edd_ajax_checkout_errors' ); + edd_die(); + } else { + return false; + } + } + + if ( $is_ajax ) { + echo 'success'; + edd_die(); + } + + // Setup user information. $user_info = array( - 'id' => $user['user_id'], - 'email' => $user['user_email'], + 'id' => $user['user_id'], + 'email' => $user['user_email'], 'first_name' => $user['user_first'], - 'last_name' => $user['user_last'], - 'discount' => $valid_data['discount'] + 'last_name' => $user['user_last'], + 'discount' => $valid_data['discount'], + 'address' => ! empty( $user['address'] ) ? $user['address'] : array(), + ); + + // Update a customer record if they have added/updated information. + $customer = new EDD_Customer( $user_info['email'] ); + + $name = $user_info['first_name'] . ' ' . $user_info['last_name']; + if ( empty( $customer->name ) || $name != $customer->name ) { + $update_data = array( + 'name' => $name + ); + + // Update the customer's name and update the user record too. + $customer->update( $update_data ); + wp_update_user( array( + 'ID' => get_current_user_id(), + 'first_name' => $user_info['first_name'], + 'last_name' => $user_info['last_name'] + ) ); + } + + // Update the customer's address if different to what's in the database. + $address = wp_parse_args( $user_info['address'], array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'state' => '', + 'country' => '', + 'zip' => '', + ) ); + + $address = array( + 'address' => $address['line1'], + 'address2' => $address['line2'], + 'city' => $address['city'], + 'region' => $address['state'], + 'country' => $address['country'], + 'postal_code' => $address['zip'], ); - // setup purchase information + $card_country = isset( $valid_data['cc_info']['card_country'] ) ? $valid_data['cc_info']['card_country'] : false; + $card_state = isset( $valid_data['cc_info']['card_state'] ) ? $valid_data['cc_info']['card_state'] : false; + $card_zip = isset( $valid_data['cc_info']['card_zip'] ) ? $valid_data['cc_info']['card_zip'] : false; + + // Set up the unique purchase key. If we are resuming a payment, we'll overwrite this with the existing key. + + $purchase_key = edd_generate_order_payment_key( $user['user_email'] ); + $existing_payment = EDD()->session->get( 'edd_resume_payment' ); + + if ( ! empty( $existing_payment ) ) { + $payment = new EDD_Payment( $existing_payment ); + if ( $payment->is_recoverable() && ! empty( $payment->key ) ) { + $purchase_key = $payment->key; + } + } + + // Setup purchase information. $purchase_data = array( - 'downloads' => edd_get_cart_contents(), - 'price' => edd_get_cart_amount(), - 'purchase_key' => strtolower( md5( uniqid() ) ), // random key - 'user_email' => $user['user_email'], - 'date' => date( 'Y-m-d H:i:s' ), - 'user_info' => $user_info, - 'post_data' => $_POST, + 'downloads' => edd_get_cart_contents(), + 'fees' => edd_get_cart_fees(), // Any arbitrary fees that have been added to the cart. + 'subtotal' => edd_get_cart_subtotal(), // Amount before taxes and discounts. + 'discount' => edd_get_cart_discounted_amount(), // Discounted amount. + 'tax' => edd_get_cart_tax(), // Taxed amount. + 'tax_rate' => edd_use_taxes() ? edd_get_cart_tax_rate( $card_country, $card_state, $card_zip ) : 0, // Tax rate. + 'price' => edd_get_cart_total(), // Amount after taxes. + 'purchase_key' => $purchase_key, + 'user_email' => $user['user_email'], + 'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ), + 'user_info' => stripslashes_deep( $user_info ), + 'post_data' => $_POST, 'cart_details' => edd_get_cart_content_details(), - 'gateway' => $valid_data['gateway'], - 'card_info' => $valid_data['cc_info'] + 'gateway' => $valid_data['gateway'], + 'card_info' => $valid_data['cc_info'] ); - - // add the user data for hooks + + // Add the user data for hooks. $valid_data['user'] = $user; - // allow themes and plugins to hook before the gateway + // Allow themes and plugins to hook before the gateway. do_action( 'edd_checkout_before_gateway', $_POST, $user_info, $valid_data ); - // allow the purchase data to be modified before it is sent to the gateway - $purchase_data = apply_filters( - 'edd_purchase_data_before_gateway', - $purchase_data, - $valid_data - ); + // If the total amount in the cart is 0, send to the manual gateway. This emulates a free download purchase. + if ( ! $purchase_data['price'] ) { - // if the total amount in the cart is 0, send to the manaul gateway. This emulates a free download purchase - if ( $purchase_data['price'] <= 0 ) { - // revert to manual - $valid_data['gateway'] = 'manual'; + // Revert to manual. + $purchase_data['gateway'] = 'manual'; + $_POST['edd-gateway'] = 'manual'; } - // used for showing download links to non logged-in users after purchase, and for other plugins needing purchase data. - edd_set_purchase_session( $purchase_data ); + // Allow the purchase data to be modified before it is sent to the gateway. + $purchase_data = apply_filters( + 'edd_purchase_data_before_gateway', + $purchase_data, + $valid_data + ); + + // Setup the data we're storing in the purchase session. + $session_data = $purchase_data; + + // Make sure credit card numbers are never stored in sessions. + unset( $session_data['card_info']['card_number'] ); + + // Used for showing download links to non logged-in users after purchase, and for other plugins needing purchase data. + edd_set_purchase_session( $session_data ); + + // Send info to the gateway for payment processing. + edd_send_to_gateway( $purchase_data['gateway'], $purchase_data ); + edd_die(); +} +add_action( 'edd_purchase', 'edd_process_purchase_form' ); +add_action( 'wp_ajax_edd_process_checkout', 'edd_process_purchase_form' ); +add_action( 'wp_ajax_nopriv_edd_process_checkout', 'edd_process_purchase_form' ); + +/** + * Verify that when a logged in user makes a purchase that the email address + * used doesn't belong to a different customer + * + * @since 2.6 + * @param array $valid_data Validated data submitted for the purchase + * @param array $post Additional $_POST data submitted + * @return void + */ +function edd_checkout_check_existing_email( $valid_data, $post ) { + + // Verify that the email address belongs to this customer + if ( is_user_logged_in() ) { + + $email = strtolower( $valid_data['logged_in_user']['user_email'] ); + $customer = new EDD_Customer( get_current_user_id(), true ); + + // If this email address is not registered with this customer, see if it belongs to any other customer + if ( $email != strtolower( $customer->email ) && ( is_array( $customer->emails ) && ! in_array( $email, array_map( 'strtolower', $customer->emails ) ) ) ) { + $found_customer = new EDD_Customer( $email ); - // send info to the gateway for payment processing - edd_send_to_gateway( $valid_data['gateway'], $purchase_data ); - - exit; + if ( $found_customer->id > 0 ) { + edd_set_error( 'edd-customer-email-exists', sprintf( __( 'The email address %s is already in use.', 'easy-digital-downloads' ), $email ) ); + } + } + } } -add_action('edd_purchase', 'edd_process_purchase_form'); +add_action( 'edd_checkout_error_checks', 'edd_checkout_check_existing_email', 10, 2 ); + +/** + * Process the checkout login form + * + * @access private + * @since 1.8 + * @return void + */ +function edd_process_purchase_login() { + + $is_ajax = isset( $_POST['edd_ajax'] ); + + if ( ! isset( $_POST['edd_login_nonce'] ) ) { + edd_debug_log( __( 'Missing nonce when processing login during checkout. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/09/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4', 'easy-digital-downloads' ), true ); + } + + $nonce = isset( $_POST['edd_login_nonce'] ) ? sanitize_text_field( $_POST['edd_login_nonce'] ) : ''; + $nonce_verified = wp_verify_nonce( $nonce, 'edd-login-form' ); + if ( false === $nonce_verified ) { + edd_set_error( 'edd-login-nonce-failed', __( 'Error processing login. Nonce failed.', 'easy-digital-downloads' ) ); + + if ( $is_ajax ) { + do_action( 'edd_ajax_checkout_errors' ); + edd_die(); + } else { + edd_redirect( wp_get_referer() ); + } + } + + $user_data = edd_purchase_form_validate_user_login(); + + if ( edd_get_errors() || $user_data['user_id'] < 1 ) { + if ( $is_ajax ) { + do_action( 'edd_ajax_checkout_errors' ); + edd_die(); + } else { + edd_redirect( wp_get_referer() ); + } + } + edd_log_user_in( $user_data['user_id'], $user_data['user_login'], $user_data['user_pass'] ); + + if ( $is_ajax ) { + echo 'success'; + edd_die(); + } else { + edd_redirect( edd_get_checkout_uri( $_SERVER['QUERY_STRING'] ) ); + } +} +add_action( 'wp_ajax_edd_process_checkout_login', 'edd_process_purchase_login' ); +add_action( 'wp_ajax_nopriv_edd_process_checkout_login', 'edd_process_purchase_login' ); /** * Purchase Form Validate Fields * * @access private * @since 1.0.8.1 - * @return array -*/ - + * @return bool|array + */ function edd_purchase_form_validate_fields() { - global $edd_options; - - // check if there is $_POST - if ( empty( $_POST ) ) return; - - // start an array to collect valid data + + // Bail if there is no $_POST. + if ( empty( $_POST ) ) { + return false; + } + + // Start an array to collect valid data. $valid_data = array( - 'gateway' => '', // gateway fallback - 'discount' => 'none', // set default discount - 'need_new_user' => false, // new user flag - 'need_user_login' => false, // login user flag - 'logged_user_data' => array(), // logged user collected data - 'new_user_data' => array(), // new user collected data - 'login_user_data' => array(), // login user collected data - 'guest_user_data' => array(), // guest user collected data - 'cc_info' => array() // credit card info + 'gateway' => edd_purchase_form_validate_gateway(), // Gateway fallback. + 'discount' => edd_purchase_form_validate_discounts(), // Set default discount. + 'need_new_user' => false, // New user flag. + 'need_user_login' => false, // Login user flag. + 'logged_user_data' => array(), // Logged user collected data. + 'new_user_data' => array(), // New user collected data. + 'login_user_data' => array(), // Login user collected data. + 'guest_user_data' => array(), // Guest user collected data. + 'cc_info' => edd_purchase_form_validate_cc(), // Credit card info. ); - - // validate the gateway - $valid_data['gateway'] = edd_purchase_form_validate_gateway(); - - // validate discounts - $valid_data['discount'] = edd_purchase_form_validate_discounts(); - - // collect credit card info - $valid_data['cc_info'] = edd_get_purchase_cc_info(); - - // validate agree to terms - if ( isset( $edd_options['show_agree_to_terms'] ) ) - edd_purchase_form_validate_agree_to_terms(); - - // check if user is logged in - if ( is_user_logged_in() ) { - // collect logged in user data + + // Validate agree to terms. + if ( '1' === edd_get_option( 'show_agree_to_terms', false ) ) { + edd_purchase_form_validate_agree_to_terms(); + } + + // Validate agree to privacy policy. + if ( '1' === edd_get_option( 'show_agree_to_privacy_policy', false ) ) { + edd_purchase_form_validate_agree_to_privacy_policy(); + } + + // Collect logged in user data + if ( is_user_logged_in() ) { $valid_data['logged_in_user'] = edd_purchase_form_validate_logged_in_user(); - - } else if ( isset( $_POST['edd-purchase-var'] ) && $_POST['edd-purchase-var'] == 'needs-to-register' ) { - - // set new user registrarion as required - $valid_data['need_new_user'] = true; - - // validate new user data - $valid_data['new_user_data'] = edd_purchase_form_validate_new_user(); - - // check if login validation is needed - } else if ( isset( $_POST['edd-purchase-var'] ) && $_POST['edd-purchase-var'] == 'needs-to-login' ) { - - // set user login as required + + } elseif ( isset( $_POST['edd-purchase-var'] ) && 'needs-to-register' === $_POST['edd-purchase-var'] ) { + // Set new user registration as required. + $valid_data['need_new_user'] = true; + + // Validate new user data. + $valid_data['new_user_data'] = edd_purchase_form_validate_new_user(); + + // Check if login validation is needed. + } elseif ( isset( $_POST['edd-purchase-var'] ) && 'needs-to-login' === $_POST['edd-purchase-var'] ) { + // Set user login as required. $valid_data['need_user_login'] = true; - - // validate users login info + + // Validate users login info. $valid_data['login_user_data'] = edd_purchase_form_validate_user_login(); + + // Not registering or logging in, so setup guest user data } else { - - // not registering or logging in, so setup guest user data + // Not registering or logging in, so setup guest user data. $valid_data['guest_user_data'] = edd_purchase_form_validate_guest_user(); - } - - // return collected data - return $valid_data; + // Return collected data. + return $valid_data; } - /** * Purchase Form Validate Gateway * * @access private - * @since 1.0 + * @since 1.0 * @return string -*/ - + */ function edd_purchase_form_validate_gateway() { - // check if a gateway value is present - if ( isset( $_POST['edd-gateway'] ) && trim( $_POST['edd-gateway'] ) != '' ) { - // clean gateway - $gateway = strip_tags( $_POST['edd-gateway'] ); - // verify if gateway is active - if ( edd_is_gateway_active( $gateway ) ) { - // return active gateway - return $gateway; - } else if ( edd_get_cart_amount() <= 0 ) { - return 'manual'; - } else { - // set invalid gateway error - edd_set_error( 'invalid_gateway', __( 'The selected gateway is not active', 'edd' ) ); + + $gateway = edd_get_default_gateway(); + + // Check if a gateway value is present + if ( ! empty( $_REQUEST['edd-gateway'] ) ) { + + $gateway = sanitize_text_field( $_REQUEST['edd-gateway'] ); + + if ( '0.00' == edd_get_cart_total() ) { + $gateway = 'manual'; + + } elseif ( ! edd_is_gateway_active( $gateway ) ) { + edd_set_error( 'invalid_gateway', __( 'The selected payment gateway is not enabled', 'easy-digital-downloads' ) ); } - } else { - // no gateway is present - edd_set_error( 'empty_gateway', __( 'No gateway has been selected', 'edd' ) ); } - - // return empty - return ''; -} + return $gateway; +} /** * Purchase Form Validate Discounts @@ -219,28 +376,55 @@ function edd_purchase_form_validate_gateway() { * @access private * @since 1.0.8.1 * @return string -*/ - + */ function edd_purchase_form_validate_discounts() { - // check for valid discount is present - if ( isset( $_POST['edd-discount'] ) && trim( $_POST['edd-discount'] ) != '' ) { - // clean discount - $discount = sanitize_text_field( $_POST['edd-discount'] ); - $email = sanitize_email( $_POST['edd_email'] ); - // check if validates - if ( edd_is_discount_valid( $discount, $email ) ) { - // return clean discount - return $discount; - // invalid discount - } else { - // set invalid discount error - edd_set_error( 'invalid_discount', __( 'The discount you entered is invalid', 'edd' ) ); + // Retrieve the discount stored in cookies + $discounts = edd_get_cart_discounts(); + + $user = ''; + if ( isset( $_POST['edd_user_login'] ) && ! empty( $_POST['edd_user_login'] ) ) { + $user = sanitize_text_field( $_POST['edd_user_login'] ); + } elseif ( isset( $_POST['edd_email'] ) && ! empty($_POST['edd_email'] ) ) { + $user = sanitize_text_field( $_POST['edd_email'] ); + } elseif ( is_user_logged_in() ) { + $user = wp_get_current_user()->user_email; + } + + $error = false; + + // Check for valid discount(s) is present + if ( ! empty( $_POST['edd-discount'] ) && __( 'Enter discount', 'easy-digital-downloads' ) != $_POST['edd-discount'] ) { + // Check for a posted discount + $posted_discount = isset( $_POST['edd-discount'] ) ? trim( $_POST['edd-discount'] ) : false; + + // Add the posted discount to the discounts + if ( $posted_discount && ( empty( $discounts ) || edd_multiple_discounts_allowed() ) && edd_is_discount_valid( $posted_discount, $user ) ) { + edd_set_cart_discount( $posted_discount ); + } + + } + + // If we have discounts, loop through them + if ( ! empty( $discounts ) ) { + + foreach ( $discounts as $discount ) { + // Check if valid + if ( ! edd_is_discount_valid( $discount, $user ) ) { + // Discount is not valid + $error = true; + } } + } else { + // No discounts + return 'none'; } - // return default value - return 'none'; -} + if ( $error ) { + edd_set_error( 'invalid_discount', __( 'One or more of the discounts you entered is invalid', 'easy-digital-downloads' ) ); + } + + return implode( ', ', $discounts ); +} /** * Purchase Form Validate Agree To Terms @@ -248,487 +432,908 @@ function edd_purchase_form_validate_discounts() { * @access private * @since 1.0.8.1 * @return void -*/ - + */ function edd_purchase_form_validate_agree_to_terms() { - // validate agree to terms - if ( ! isset( $_POST['edd_agree_to_terms'] ) || $_POST['edd_agree_to_terms'] != 1 ) { - // user did not agree - edd_set_error( 'agree_to_terms', __( 'You must agree to the terms of use', 'edd' ) ); + + // User did not agree + if ( ! isset( $_POST['edd_agree_to_terms'] ) || $_POST['edd_agree_to_terms'] != 1 ) { + edd_set_error( 'agree_to_terms', apply_filters( 'edd_agree_to_terms_text', __( 'You must agree to the terms of use', 'easy-digital-downloads' ) ) ); } } +/** + * Purchase Form Validate Agree To Privacy Policy + * + * @since 2.9.1 + * @return void + */ +function edd_purchase_form_validate_agree_to_privacy_policy() { + + // User did not agree + if ( ! isset( $_POST['edd_agree_to_privacy_policy'] ) || $_POST['edd_agree_to_privacy_policy'] != 1 ) { + edd_set_error( 'agree_to_privacy_policy', apply_filters( 'edd_agree_to_privacy_policy_text', __( 'You must agree to the privacy policy', 'easy-digital-downloads' ) ) ); + } +} /** - * Purchase Form Validate Logged In User + * Purchase Form Required Fields * * @access private - * @since 1.0 + * @since 1.5 * @return array -*/ + */ +function edd_purchase_form_required_fields() { + + // These fields are _always_ required + $required_fields = array( + 'edd_email' => array( + 'error_id' => 'invalid_email', + 'error_message' => __( 'Please enter a valid email address', 'easy-digital-downloads' ) + ), + 'edd_first' => array( + 'error_id' => 'invalid_first_name', + 'error_message' => __( 'Please enter your first name', 'easy-digital-downloads' ) + ) + ); + // Let payment gateways and other extensions determine if address fields should be required + $require_address = apply_filters( 'edd_require_billing_address', edd_use_taxes() && edd_get_cart_total() ); + + if ( ! empty( $require_address ) ) { + + // Zip + $required_fields['card_zip'] = array( + 'error_id' => 'invalid_zip_code', + 'error_message' => __( 'Please enter your zip / postal code', 'easy-digital-downloads' ) + ); + + // City + $required_fields['card_city'] = array( + 'error_id' => 'invalid_city', + 'error_message' => __( 'Please enter your billing city', 'easy-digital-downloads' ) + ); + + // Country + $required_fields['billing_country'] = array( + 'error_id' => 'invalid_country', + 'error_message' => __( 'Please select your billing country', 'easy-digital-downloads' ) + ); + + // State/Region + $required_fields['card_state'] = array( + 'error_id' => 'invalid_state', + 'error_message' => __( 'Please enter billing state / region', 'easy-digital-downloads' ) + ); + + // Check if the Customer's Country has been passed in and if it has no states. + if ( isset( $_POST['billing_country'] ) && isset( $required_fields['card_state'] ) ) { + $customer_billing_country = sanitize_text_field( $_POST['billing_country'] ); + $states = edd_get_shop_states( $customer_billing_country ); + + // If this country has no states, remove the requirement of a card_state. + if ( empty( $states ) ) { + unset( $required_fields['card_state'] ); + } + } + } + + // Filter & return + return (array) apply_filters( 'edd_purchase_form_required_fields', $required_fields ); +} + +/** + * Purchase Form Validate Logged In User + * + * @access private + * @since 1.0 + * @return array + */ function edd_purchase_form_validate_logged_in_user() { global $user_ID; - - // start empty array to collect valid user data + + // Start empty array to collect valid user data $valid_user_data = array( - // assume there will be errors - 'user_id' => -1 + 'user_id' => -1 ); - - // verify there is a user_ID - if ( $user_ID > 0 ) { - // get the logged in user data + // Verify there is a user_ID + if ( $user_ID > 0 ) { + // Get the logged in user data $user_data = get_userdata( $user_ID ); - if( !is_email( $_POST['edd_email'] ) ) { - // if the user enters an email other than the stored email, we must verify it - edd_set_error( 'invalid_email', __( 'Please enter a valid email address.', 'edd' ) ); + $fields = edd_purchase_form_required_fields(); + + // Loop through required fields and show error messages + foreach ( $fields as $field_name => $value ) { + if ( empty( $_POST[ $field_name ] ) && ! empty( $value['error_id'] ) && ! empty( $value['error_message'] ) ) { + edd_set_error( $value['error_id'], $value['error_message'] ); + } } - // verify data + // Verify data if ( $user_data ) { - // collected logged in user data + // Collected logged in user data $valid_user_data = array( - 'user_id' => $user_ID, - 'user_email' => sanitize_email( $_POST['edd_email'] ), - 'user_first' => sanitize_text_field( $_POST['edd_first'] ), - 'user_last' => sanitize_text_field( $_POST['edd_last'] ), - ); + 'user_id' => $user_ID, + 'user_email' => isset( $_POST['edd_email'] ) ? sanitize_email( $_POST['edd_email'] ) : $user_data->user_email, + 'user_first' => isset( $_POST['edd_first'] ) && ! empty( $_POST['edd_first'] ) ? sanitize_text_field( $_POST['edd_first'] ) : $user_data->first_name, + 'user_last' => isset( $_POST['edd_last'] ) && ! empty( $_POST['edd_last'] ) ? sanitize_text_field( $_POST['edd_last'] ) : $user_data->last_name, + ); + + if ( ! is_email( $valid_user_data['user_email'] ) ) { + edd_set_error( 'email_invalid', __( 'Invalid email', 'easy-digital-downloads' ) ); + } + } else { - // set invalid user error - edd_set_error( 'invalid_user', __( 'The user information is invalid.', 'edd' ) ); + // Set invalid user error + edd_set_error( 'invalid_user', __( 'The user information is invalid', 'easy-digital-downloads' ) ); } } - - // return user data + + // Return user data return $valid_user_data; } - /** * Purchase Form Validate New User * * @access private * @since 1.0.8.1 * @return array -*/ - + */ function edd_purchase_form_validate_new_user() { - $registering_new_user = false; - // start empty array to collect valid user data - $valid_user_data = array( - // assume there will be errors - 'user_id' => -1, - // get first name - 'user_first' => isset( $_POST["edd_first"] ) ? strip_tags( trim( $_POST["edd_first"] ) ) : '', - // get last name - 'user_last' => isset( $_POST["edd_last"] ) ? strip_tags( trim( $_POST["edd_last"] ) ) : '', - ); - - // check the new user's credentials against existing ones - $user_login = isset( $_POST["edd_user_login"] ) ? trim( $_POST["edd_user_login"] ) : false; - $user_email = isset( $_POST['edd_email'] ) ? trim( $_POST['edd_email'] ) : false; - $user_pass = isset( $_POST["edd_user_pass"] ) ? trim( $_POST["edd_user_pass"] ) : false; - $pass_confirm = isset( $_POST["edd_user_pass_confirm"] ) ? trim( $_POST["edd_user_pass_confirm"] ) : false; - - // check if we have an username to register - if ( $user_login && strlen( $user_login ) > 0 ) { - + /** Sanitize **************************************************************/ + + // Sanitize first name + $user_first = isset( $_POST['edd_first'] ) + ? sanitize_text_field( $_POST['edd_first'] ) + : ''; + + // Sanitize last name + $user_last = isset( $_POST['edd_last'] ) + ? sanitize_text_field( $_POST['edd_last'] ) + : ''; + + // Sanitize user login. + $user_login = isset( $_POST['edd_user_login'] ) + ? sanitize_user( $_POST['edd_user_login'] ) + : false; + + // Sanitize email address (allowed formatting only) + $user_email = isset( $_POST['edd_email'] ) + ? sanitize_email( $_POST['edd_email'] ) + : false; + + // Trim front/back whitespace from password (don't alter characters) + $user_pass = isset( $_POST['edd_user_pass'] ) + ? trim( $_POST['edd_user_pass'] ) + : false; + + // Trim front/back whitespace from password (don't alter characters) + $pass_confirm = isset( $_POST['edd_user_pass_confirm'] ) + ? trim( $_POST['edd_user_pass_confirm'] ) + : false; + + /** Required Fields *******************************************************/ + + // Get required fields to loop through + $fields = edd_purchase_form_required_fields(); + + // Loop through required fields and provide error messages if missing + foreach ( $fields as $field_name => $value ) { + if ( empty( $_POST[ $field_name ] ) && ! empty( $value['error_id'] ) && ! empty( $value['error_message'] ) ) { + edd_set_error( $value['error_id'], $value['error_message'] ); + } + } + + /** Setup Userdata ********************************************************/ + + // Start an empty array to collect valid user data. + $valid_user_data = array( + 'user_id' => 0, + 'user_first' => $user_first, + 'user_last' => $user_last + ); + + /** Check Login ***********************************************************/ + + // Check if we have a username to register + if ( ! empty( $user_login ) && strlen( $user_login ) > 0 ) { $registering_new_user = true; - - // we have an user name, check if it already exists + + // Error if username already exists. if ( username_exists( $user_login ) ) { - - // username already registered - edd_set_error( 'username_unavailable', __( 'Username already taken', 'edd' ) ); - - // check if it's valid - } else if ( ! validate_username( $user_login ) ) { - - // invalid username - edd_set_error( 'username_invalid', __( 'Invalid username', 'edd' ) ); - + edd_set_error( 'username_unavailable', __( 'Username already exists', 'easy-digital-downloads' ) ); + + // Error if username is not valid + } elseif ( ! edd_validate_username( $user_login ) ) { + is_multisite() + ? edd_set_error( 'username_invalid', __( 'Invalid username. Only lowercase letters (a-z) and numbers are allowed', 'easy-digital-downloads' ) ) + : edd_set_error( 'username_invalid', __( 'Invalid username', 'easy-digital-downloads' ) ); + + // Add login to valid user data } else { - - // all is good to go + // All the checks have run and it's good to go. $valid_user_data['user_login'] = $user_login; - } - } else { - - if ( edd_no_guest_checkout() ) { - edd_set_error( 'registration_required', __( 'You must register or login to complete your purchase', 'edd' ) ); - } - - } - - // check if we have an email to verify - if ( $user_email && strlen( $user_email ) > 0 ) { - - // validate email + + // Error if users are required to register and no login was provided + } elseif ( edd_no_guest_checkout() ) { + edd_set_error( 'registration_required', __( 'You must register or login to complete your purchase', 'easy-digital-downloads' ) ); + } + + /** Check Email ***********************************************************/ + + // Check if we have an email to verify + if ( ! empty( $user_email ) && strlen( $user_email ) > 0 ) { + + // Error if invalid email address if ( ! is_email( $user_email ) ) { - - // invalid email - edd_set_error( 'email_invalid', __( 'Invalid email', 'edd' ) ); - - // check if email exists - } else if ( email_exists( $user_email ) && $registering_new_user ) { - - // email address already registered - edd_set_error( 'email_used', __( 'Email already used', 'edd' ) ); - + edd_set_error( 'email_invalid', __( 'Invalid email', 'easy-digital-downloads' ) ); + + // Email address is unsafe (multisite only) + } elseif ( is_multisite() && is_email_address_unsafe( $user_email ) ) { + edd_set_error( 'email_unsafe', __( 'You cannot use that email address to signup at this time.', 'easy-digital-downloads' ) ); + } elseif ( true === $registering_new_user ) { + // Check if email exists. + $customers = edd_get_customers( + array( + 'email' => $user_email, + 'user_id__not_in' => array( null ), + ) + ); + if ( email_exists( $user_email ) || ! empty( $customers ) ) { + edd_set_error( 'email_used', __( 'Email already used. Login or use a different email to complete your purchase.', 'easy-digital-downloads' ) ); + } else { + $valid_user_data['user_email'] = $user_email; + } } else { - - // all is good to go + // Add email to valid user data. $valid_user_data['user_email'] = $user_email; - } - + + // Error if no email address was provided } else { - - // no email - edd_set_error( 'email_empty', __( 'Enter an email', 'edd' ) ); - - } - - // check password - if ( $user_pass && $pass_confirm ) { - - // verify confirmation matches - if ( $user_pass != $pass_confirm ) { - - // passwords do not match - edd_set_error( 'password_mismatch', __( 'Passwords don\'t match', 'edd' ) ); - + // No email. + edd_set_error( 'email_empty', __( 'Enter an email', 'easy-digital-downloads' ) ); + } + + /** Check Password ********************************************************/ + + // Check password + if ( ! empty( $user_pass ) && ! empty( $pass_confirm ) ) { + + // Error if passwords do not match + if ( 0 !== strcmp( $user_pass, $pass_confirm ) ) { + edd_set_error( 'password_mismatch', __( 'Passwords do not match', 'easy-digital-downloads' ) ); + + // Add password to valid user data } else { - - // all is good to go + // All is good to go. $valid_user_data['user_pass'] = $user_pass; - } - - } else { - - // pass or confrimation missing - if ( ! $user_pass && $registering_new_user ) { - - // password invalid - edd_set_error( 'password_empty', __( 'Enter a password', 'edd' ) ); - - } else if ( ! $pass_confirm && $registering_new_user ) { - - // confirmation invalid - edd_set_error( 'confirmation_empty', __( 'Enter the password confirmation', 'edd' ) ); - + + // Error if no password when signing up + } elseif ( true === $registering_new_user ) { + if ( empty( $user_pass ) ) { + edd_set_error( 'password_empty', __( 'Enter a password', 'easy-digital-downloads' ) ); + } elseif ( empty( $pass_confirm ) ) { + edd_set_error( 'confirmation_empty', __( 'Confirm your password', 'easy-digital-downloads' ) ); } } - - return $valid_user_data; + // Cast as array and return + return (array) $valid_user_data; } - /** * Purchase Form Validate User Login * * @access private * @since 1.0.8.1 - * @return void -*/ - + * @return array + */ function edd_purchase_form_validate_user_login() { - // start an array to collect valid user data + + // Start an array to collect valid user data. $valid_user_data = array( - // assume there will be errors - 'user_id' => -1 + 'user_id' => 0, ); - - // username - if ( ! isset( $_POST['edd-username'] ) || $_POST['edd-username'] == '' ) { - edd_set_error( 'must_log_in', __( 'You must login or register to complete your purchase', 'edd' ) ); + + $user_login = ! empty( $_POST['edd_user_login'] ) ? sanitize_text_field( $_POST['edd_user_login'] ) : ''; + $user_pass = ! empty( $_POST['edd_user_pass'] ) ? $_POST['edd_user_pass'] : ''; + + // Username. + if ( empty( $user_login ) && edd_no_guest_checkout() ) { + edd_set_error( 'must_log_in', __( 'You must log in or register to complete your purchase', 'easy-digital-downloads' ) ); return $valid_user_data; } - - // get the user by login - $user_data = get_user_by( 'login', strip_tags( $_POST['edd-username'] ) ); - - // check if user exists - if ( $user_data ) { - - // get password - $user_pass = isset( $_POST["edd-password"] ) ? $_POST["edd-password"] : false; - - // check user_pass - if ( $user_pass ) { - // check if password is valid - if ( ! wp_check_password( $user_pass, $user_data->user_pass, $user_data->ID ) ) { - // incorrect password - edd_set_error( 'password_incorrect', __( 'The password you entered is incorrect', 'edd' ) ); - // all is correct - } else { - // repopulate the valid user data array - $valid_user_data = array( - 'user_id' => $user_data->ID, - 'user_login' => $user_data->user_login, - 'user_email' => $user_data->user_email, - 'user_first' => $user_data->first_name, - 'user_last' => $user_data->last_name, - 'user_pass' => $user_pass, - ); - } - } else { - // empty password - edd_set_error( 'password_empty', __( 'Enter a password', 'edd' ) ); - } - } else { - // no username - edd_set_error( 'username_incorrect', __( 'The username you entered does not exist', 'edd' ) ); + + $user = edd_log_user_in( 0, $user_login, $user_pass, false ); + + if ( ! $user instanceof WP_User ) { + return $valid_user_data; } - - return $valid_user_data; - -} + // Populate the valid user data array. + return array( + 'user_id' => $user->ID, + 'user_login' => $user->user_login, + 'user_email' => $user->user_email, + 'user_first' => $user->first_name, + 'user_last' => $user->last_name, + 'user_pass' => $user_pass, + ); +} /** * Purchase Form Validate Guest User * - * @access private - * @since 1.0.8.1 - * @return void -*/ - + * @access private + * @since 1.0.8.1 + * @return array + */ function edd_purchase_form_validate_guest_user() { - // start an array to collect valid user data + + // Start an array to collect valid user data $valid_user_data = array( - // set a default id for guests - 'user_id' => 0, + 'user_id' => 0 ); - - // get the guest email - $guest_email = isset( $_POST['edd_email'] ) ? $_POST['edd_email'] : false; - - // check email - if ( $guest_email && strlen( $guest_email ) > 0 ) { - // validate email + + // Show error message if user must be logged in + if ( edd_logged_in_only() ) { + edd_set_error( 'logged_in_only', __( 'You must be logged into an account to purchase', 'easy-digital-downloads' ) ); + } + + // Get the guest email + $guest_email = isset( $_POST['edd_email'] ) + ? sanitize_email( $_POST['edd_email'] ) + : false; + + // Check email + if ( ! empty( $guest_email ) && strlen( $guest_email ) > 0 ) { + + // Invalid email if ( ! is_email( $guest_email ) ) { - // invalid email - edd_set_error( 'email_invalid', __( 'Invalid email', 'edd' ) ); + edd_set_error( 'email_invalid', __( 'Invalid email', 'easy-digital-downloads' ) ); + + // Email address is unsafe (multisite only) + } elseif ( is_multisite() && is_email_address_unsafe( $guest_email ) ) { + edd_set_error( 'email_unsafe', __( 'You cannot use that email address at this time.', 'easy-digital-downloads' ) ); + + // All is good to go } else { - // all is good to go $valid_user_data['user_email'] = $guest_email; } + + // No email } else { - // no email - edd_set_error( 'email_empty', __( 'Enter an email', 'edd' ) ); + edd_set_error( 'email_empty', __( 'Enter an email', 'easy-digital-downloads' ) ); } - - return $valid_user_data; -} + // Get fields + $fields = edd_purchase_form_required_fields(); + + // Loop through required fields and show error messages + foreach ( $fields as $field_name => $value ) { + if ( empty( $_POST[ $field_name ] ) && ! empty( $value['error_id'] ) && ! empty( $value['error_message'] ) ) { + edd_set_error( $value['error_id'], $value['error_message'] ); + } + } + + return (array) $valid_user_data; +} /** - * Register And Login New User + * Register And Login New User. * - * @access private - * @since 1.0.8.1 - * @return integer -*/ - + * @since 1.0.8.1 + * + * @param array $user_data The data provided by the checkout page's registration form. + * @return integer + */ function edd_register_and_login_new_user( $user_data = array() ) { - // verify the array - if ( empty( $user_data ) ) - return -1; - - // insert new user - $user_id = wp_insert_user(array( - 'user_login' => $user_data['user_login'], - 'user_pass' => $user_data['user_pass'], - 'user_email' => $user_data['user_email'], - 'first_name' => $user_data['user_first'], - 'last_name' => $user_data['user_last'], - 'user_registered' => date('Y-m-d H:i:s'), - 'role' => 'subscriber' - ) + + // Verify the array. + if ( empty( $user_data ) ) { + return -1; + } + + // Bail if errors + if ( edd_get_errors() ) { + return -1; + } + + $user_args = apply_filters( + 'edd_insert_user_args', + array( + 'user_login' => isset( $user_data['user_login'] ) ? $user_data['user_login'] : '', + 'user_pass' => isset( $user_data['user_pass'] ) ? $user_data['user_pass'] : '', + 'user_email' => isset( $user_data['user_email'] ) ? $user_data['user_email'] : '', + 'first_name' => isset( $user_data['user_first'] ) ? $user_data['user_first'] : '', + 'last_name' => isset( $user_data['user_last'] ) ? $user_data['user_last'] : '', + 'user_registered' => date( 'Y-m-d H:i:s' ), + 'role' => get_option( 'default_role' ), + ), + $user_data ); - - // validate inserted user - if ( is_wp_error( $user_id ) ) - return -1; - - // allow themes and plugins to hook - do_action('edd_insert_user', $user_id); - - // login new user - edd_log_user_in($user_id, $user_data['user_login'], $user_data['user_pass']); - - // return user id - return $user_id; -} + // Insert new user. + $user_id = wp_insert_user( $user_args ); + + // Validate inserted user. + if ( is_wp_error( $user_id ) ) { + return -1; + } + + // Allow themes and plugins to filter the user data. + $user_data = apply_filters( 'edd_insert_user_data', $user_data, $user_args ); + + // Allow themes and plugins to hook. + do_action( 'edd_insert_user', $user_id, $user_data ); + + // Login new user. + $user = edd_log_user_in( $user_id, $user_data['user_login'], $user_data['user_pass'] ); + + // If we have errors after trying to use wp_signon, return -1. + if ( edd_get_errors() ) { + return -1; + } + + // Return user id. + return $user->ID; +} /** * Get Purchase Form User * - * @access private - * @since 1.0.8.1 - * @return array -*/ - -function edd_get_purchase_form_user( $valid_data = array() ) { - - // initialize user - $user = false; - - // check if user is logged in - if ( is_user_logged_in() ) { - // set the valid user as the logged in collected data + * @since 1.0.8.1 + * @since 3.0 Remove `update_user_meta()` call to update the user's address + * as it is done later on in the order flow where a customer ID + * is available. + * + * @param array $valid_data + * @access private + * @since 1.0.8.1 + * + * @param array $valid_data The validated data from the checkout form validation. + * @return array + */ +function edd_get_purchase_form_user( $valid_data = array(), $is_ajax = null ) { + + // Default variables + $user = false; + $is_ajax = ( null === $is_ajax ) ? edd_doing_ajax() : $is_ajax; + + // Bail if during the ajax submission (check for errors only) + if ( $is_ajax ) { + return true; + + // Set the valid user as the logged in collected data + } elseif ( is_user_logged_in() ) { $user = $valid_data['logged_in_user']; - } - // otherwise check if we have to register or login users - else if ( $valid_data['need_new_user'] === true || $valid_data['need_user_login'] === true ) { - // new user registration - if ( $valid_data['need_new_user'] === true ) { - // set user + + // New user registration + } elseif ( true === $valid_data['need_new_user'] || true === $valid_data['need_user_login'] ) { + // This ensures $_COOKIE is available without a new HTTP request. + add_action( 'set_logged_in_cookie', 'edd_set_logged_in_cookie' ); + + if ( true === $valid_data['need_new_user'] ) { + + // Set user $user = $valid_data['new_user_data']; - // register and login new user + + // Register and login new user. $user['user_id'] = edd_register_and_login_new_user( $user ); - // user login - } else if ( $valid_data['need_user_login'] === true ) { - // set user + + // User login + } elseif ( true === $valid_data['need_user_login'] ) { + /* + * The login form is now processed in the edd_process_purchase_login() function. + * This is still here for backwards compatibility. + * This also allows the old login process to still work if a user removes the + * checkout login submit button. + * + * This also ensures that the customer is logged in correctly if they click "Purchase" + * instead of submitting the login form, meaning the customer is logged in during the purchase process. + */ + + // Set user. $user = $valid_data['login_user_data']; - // login user - edd_log_user_in( $user['user_id'], $user['user_login'], $user['user_pass'] ); - } - } - - // check guest checkout - if ( false === $user && false === edd_no_guest_checkout() ) { - // set user + + // Login user. + if ( empty( $user ) || -1 === $user['user_id'] ) { + edd_set_error( 'invalid_user', __( 'The user information is invalid', 'easy-digital-downloads' ) ); + return false; + } else { + edd_log_user_in( $user['user_id'], $user['user_login'], $user['user_pass'] ); + } + } + + remove_action( 'set_logged_in_cookie', 'edd_set_logged_in_cookie' ); + } + + // Check guest checkout + if ( empty( $user ) && ( false === edd_no_guest_checkout() ) ) { $user = $valid_data['guest_user_data']; } - - // verify we have an user - if ( false === $user || empty( $user ) ) { - // return false + + // Bail if no user. + if ( empty( $user ) ) { return false; } - - // get user first name - if ( !isset( $user['user_first'] ) || strlen( trim( $user['user_first'] ) ) < 1 ) { - $user['user_first'] = isset( $_POST["edd_first"] ) ? strip_tags( trim( $_POST["edd_first"] ) ) : ''; + + // Get user first name. + if ( ! isset( $user['user_first'] ) || strlen( trim( $user['user_first'] ) ) < 1 ) { + $user['user_first'] = isset( $_POST['edd_first'] ) + ? strip_tags( trim( $_POST['edd_first'] ) ) + : ''; } - - // get user last name - if ( !isset( $user['user_last'] ) || strlen( trim( $user['user_last'] ) ) < 1 ) { - $user['user_last'] = isset( $_POST["edd_last"] ) ? strip_tags( trim( $_POST["edd_last"] ) ) : ''; + + // Get user last name. + if ( ! isset( $user['user_last'] ) || strlen( trim( $user['user_last'] ) ) < 1 ) { + $user['user_last'] = isset( $_POST['edd_last'] ) + ? strip_tags( trim( $_POST['edd_last'] ) ) + : ''; } - - // return valid user + + // Get the user's billing address details. + $user['address'] = array(); + $user['address']['line1'] = ! empty( $_POST['card_address'] ) ? sanitize_text_field( $_POST['card_address'] ) : ''; + $user['address']['line2'] = ! empty( $_POST['card_address_2'] ) ? sanitize_text_field( $_POST['card_address_2'] ) : ''; + $user['address']['city'] = ! empty( $_POST['card_city'] ) ? sanitize_text_field( $_POST['card_city'] ) : ''; + $user['address']['state'] = ! empty( $_POST['card_state'] ) ? sanitize_text_field( $_POST['card_state'] ) : ''; + $user['address']['country'] = ! empty( $_POST['billing_country'] ) ? sanitize_text_field( $_POST['billing_country'] ) : ''; + $user['address']['zip'] = ! empty( $_POST['card_zip'] ) ? sanitize_text_field( $_POST['card_zip'] ) : ''; + + // Country will always be set if address fields are present + if ( empty( $user['address']['country'] ) ) { + $user['address'] = false; + } + + // Return valid user. return $user; } +/** + * Sets the $_COOKIE global when a logged in cookie is available. + * + * We need the global to be immediately available so calls to wp_create_nonce() + * within the same session will use the newly available data. + * + * @since 2.11 + * + * @link https://wordpress.stackexchange.com/a/184055 + * + * @param string $logged_in_cookie The logged-in cookie value. + */ +function edd_set_logged_in_cookie( $logged_in_cookie ) { + $_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie; +} + +/** + * Validates the credit card info + * + * @access private + * @since 1.4.4 + * @return array + */ +function edd_purchase_form_validate_cc() { + $card_data = edd_get_purchase_cc_info(); + + // Validate the card zip + if ( ! empty( $card_data['card_zip'] ) && edd_get_cart_total() > 0.00 ) { + if ( ! edd_purchase_form_validate_cc_zip( $card_data['card_zip'], $card_data['card_country'] ) ) { + edd_set_error( 'invalid_cc_zip', __( 'The zip / postal code you entered for your billing address is invalid', 'easy-digital-downloads' ) ); + } + } + + // This should validate card numbers at some point too + return $card_data; +} /** * Get Credit Card Info * - * @access private - * @since 1.2 - * @return array -*/ - -function edd_get_purchase_cc_info( $valid_data = array() ) { - + * @access private + * @since 1.4.4 + * @return array + */ +function edd_get_purchase_cc_info() { $cc_info = array(); - $cc_info['card_name'] = isset( $_POST['card_name'] ) ? sanitize_text_field( $_POST['card_name'] ) : ''; - $cc_info['card_number'] = isset( $_POST['card_number'] ) ? sanitize_text_field( $_POST['card_number'] ) : ''; - $cc_info['card_cvc'] = isset( $_POST['card_cvc'] ) ? sanitize_text_field( $_POST['card_cvc'] ) : ''; - $cc_info['card_exp_month'] = isset( $_POST['card_exp_month'] ) ? sanitize_text_field( $_POST['card_exp_month'] ) : ''; - $cc_info['card_exp_year'] = isset( $_POST['card_exp_year'] ) ? sanitize_text_field( $_POST['card_exp_year'] ) : ''; - $cc_info['card_address'] = isset( $_POST['card_address'] ) ? sanitize_text_field( $_POST['card_address'] ) : ''; - $cc_info['card_address_2'] = isset( $_POST['card_address_2'] ) ? sanitize_text_field( $_POST['card_address_2'] ) : ''; - $cc_info['card_city'] = isset( $_POST['card_city'] ) ? sanitize_text_field( $_POST['card_city'] ) : ''; - $cc_info['card_country'] = isset( $_POST['billing_country'] )? sanitize_text_field( $_POST['billing_country'] ) : ''; - $cc_info['card_zip'] = isset( $_POST['card_zip'] ) ? sanitize_text_field( $_POST['card_zip'] ) : ''; - - switch( $cc_info['card_country'] ) : - - case 'US' : - $cc_info['card_state'] = isset( $_POST['card_state_us'] ) ? sanitize_text_field( $_POST['card_state_us'] ) : ''; - break; - case 'CA' : - $cc_info['card_state'] = isset( $_POST['card_state_ca'] ) ? sanitize_text_field( $_POST['card_state_ca'] ) : ''; - break; - default : - $cc_info['card_state'] = isset( $_POST['card_state_other'] )? sanitize_text_field( $_POST['card_state_other'] ) : ''; - break; - - endswitch; - - // return cc info + $cc_info['card_name'] = isset( $_POST['card_name'] ) ? sanitize_text_field( $_POST['card_name'] ) : ''; + $cc_info['card_number'] = isset( $_POST['card_number'] ) ? sanitize_text_field( $_POST['card_number'] ) : ''; + $cc_info['card_cvc'] = isset( $_POST['card_cvc'] ) ? sanitize_text_field( $_POST['card_cvc'] ) : ''; + $cc_info['card_exp_month'] = isset( $_POST['card_exp_month'] ) ? sanitize_text_field( $_POST['card_exp_month'] ) : ''; + $cc_info['card_exp_year'] = isset( $_POST['card_exp_year'] ) ? sanitize_text_field( $_POST['card_exp_year'] ) : ''; + $cc_info['card_address'] = isset( $_POST['card_address'] ) ? sanitize_text_field( $_POST['card_address'] ) : ''; + $cc_info['card_address_2'] = isset( $_POST['card_address_2'] ) ? sanitize_text_field( $_POST['card_address_2'] ) : ''; + $cc_info['card_city'] = isset( $_POST['card_city'] ) ? sanitize_text_field( $_POST['card_city'] ) : ''; + $cc_info['card_state'] = isset( $_POST['card_state'] ) ? sanitize_text_field( $_POST['card_state'] ) : ''; + $cc_info['card_country'] = isset( $_POST['billing_country'] ) ? sanitize_text_field( $_POST['billing_country'] ) : ''; + $cc_info['card_zip'] = isset( $_POST['card_zip'] ) ? sanitize_text_field( $_POST['card_zip'] ) : ''; + + // Return cc info return $cc_info; } - /** - * Send To Success Page + * Validate zip code based on country code * - * Sends the user to the succes page. + * @since 1.4.4 * - * @access public - * @since 1.0 - * @return void -*/ - -function edd_send_to_success_page($query_string = null) { - global $edd_options; - $redirect = get_permalink($edd_options['success_page']); - if($query_string) - $redirect .= $query_string; - - wp_redirect( apply_filters('edd_success_page_redirect', $redirect, $_POST['edd-gateway'], $query_string) ); exit; -} + * @param int $zip + * @param string $country_code + * + * @return bool|mixed|void + */ +function edd_purchase_form_validate_cc_zip( $zip = 0, $country_code = '' ) { + $ret = false; + if ( empty( $zip ) || empty( $country_code ) ) { + return $ret; + } + + $country_code = strtoupper( $country_code ); + + $zip_regex = array( + "AD" => "AD\d{3}", + "AM" => "(37)?\d{4}", + "AR" => "^([A-Z]{1}\d{4}[A-Z]{3}|[A-Z]{1}\d{4}|\d{4})$", + "AS" => "96799", + "AT" => "\d{4}", + "AU" => "^(0[289][0-9]{2})|([1345689][0-9]{3})|(2[0-8][0-9]{2})|(290[0-9])|(291[0-4])|(7[0-4][0-9]{2})|(7[8-9][0-9]{2})$", + "AX" => "22\d{3}", + "AZ" => "\d{4}", + "BA" => "\d{5}", + "BB" => "(BB\d{5})?", + "BD" => "\d{4}", + "BE" => "^[1-9]{1}[0-9]{3}$", + "BG" => "\d{4}", + "BH" => "((1[0-2]|[2-9])\d{2})?", + "BM" => "[A-Z]{2}[ ]?[A-Z0-9]{2}", + "BN" => "[A-Z]{2}[ ]?\d{4}", + "BR" => "\d{5}[\-]?\d{3}", + "BY" => "\d{6}", + "CA" => "^[ABCEGHJKLMNPRSTVXY]{1}\d{1}[A-Z]{1} *\d{1}[A-Z]{1}\d{1}$", + "CC" => "6799", + "CH" => "^[1-9][0-9][0-9][0-9]$", + "CK" => "\d{4}", + "CL" => "\d{7}", + "CN" => "\d{6}", + "CR" => "\d{4,5}|\d{3}-\d{4}", + "CS" => "\d{5}", + "CV" => "\d{4}", + "CX" => "6798", + "CY" => "\d{4}", + "CZ" => "\d{3}[ ]?\d{2}", + "DE" => "\b((?:0[1-46-9]\d{3})|(?:[1-357-9]\d{4})|(?:[4][0-24-9]\d{3})|(?:[6][013-9]\d{3}))\b", + "DK" => "^([D-d][K-k])?( |-)?[1-9]{1}[0-9]{3}$", + "DO" => "\d{5}", + "DZ" => "\d{5}", + "EC" => "([A-Z]\d{4}[A-Z]|(?:[A-Z]{2})?\d{6})?", + "EE" => "\d{5}", + "EG" => "\d{5}", + "ES" => "^([1-9]{2}|[0-9][1-9]|[1-9][0-9])[0-9]{3}$", + "ET" => "\d{4}", + "FI" => "\d{5}", + "FK" => "FIQQ 1ZZ", + "FM" => "(9694[1-4])([ \-]\d{4})?", + "FO" => "\d{3}", + "FR" => "^(F-)?((2[A|B])|[0-9]{2})[0-9]{3}$", + "GE" => "\d{4}", + "GF" => "9[78]3\d{2}", + "GL" => "39\d{2}", + "GN" => "\d{3}", + "GP" => "9[78][01]\d{2}", + "GR" => "\d{3}[ ]?\d{2}", + "GS" => "SIQQ 1ZZ", + "GT" => "\d{5}", + "GU" => "969[123]\d([ \-]\d{4})?", + "GW" => "\d{4}", + "HM" => "\d{4}", + "HN" => "(?:\d{5})?", + "HR" => "\d{5}", + "HT" => "\d{4}", + "HU" => "\d{4}", + "ID" => "\d{5}", + "IE" => "((D|DUBLIN)?([1-9]|6[wW]|1[0-8]|2[024]))?", + "IL" => "\d{5}", + "IN"=> "^[1-9][0-9][0-9][0-9][0-9][0-9]$", //india + "IO" => "BBND 1ZZ", + "IQ" => "\d{5}", + "IS" => "\d{3}", + "IT" => "^(V-|I-)?[0-9]{5}$", + "JO" => "\d{5}", + "JP" => "\d{3}-\d{4}", + "KE" => "\d{5}", + "KG" => "\d{6}", + "KH" => "\d{5}", + "KR" => "\d{5}", + "KW" => "\d{5}", + "KZ" => "\d{6}", + "LA" => "\d{5}", + "LB" => "(\d{4}([ ]?\d{4})?)?", + "LI" => "(948[5-9])|(949[0-7])", + "LK" => "\d{5}", + "LR" => "\d{4}", + "LS" => "\d{3}", + "LT" => "\d{5}", + "LU" => "\d{4}", + "LV" => "\d{4}", + "MA" => "\d{5}", + "MC" => "980\d{2}", + "MD" => "\d{4}", + "ME" => "8\d{4}", + "MG" => "\d{3}", + "MH" => "969[67]\d([ \-]\d{4})?", + "MK" => "\d{4}", + "MN" => "\d{5}", + "MP" => "9695[012]([ \-]\d{4})?", + "MQ" => "9[78]2\d{2}", + "MT" => "[A-Z]{3}[ ]?\d{2,4}", + "MU" => "(\d{3}[A-Z]{2}\d{3})?", + "MV" => "\d{5}", + "MX" => "\d{5}", + "MY" => "\d{5}", + "NC" => "988\d{2}", + "NE" => "\d{4}", + "NF" => "2899", + "NG" => "(\d{6})?", + "NI" => "((\d{4}-)?\d{3}-\d{3}(-\d{1})?)?", + "NL" => "^[1-9][0-9]{3}\s?([a-zA-Z]{2})?$", + "NO" => "\d{4}", + "NP" => "\d{5}", + "NZ" => "\d{4}", + "OM" => "(PC )?\d{3}", + "PF" => "987\d{2}", + "PG" => "\d{3}", + "PH" => "\d{4}", + "PK" => "\d{5}", + "PL" => "\d{2}-\d{3}", + "PM" => "9[78]5\d{2}", + "PN" => "PCRN 1ZZ", + "PR" => "00[679]\d{2}([ \-]\d{4})?", + "PT" => "\d{4}([\-]\d{3})?", + "PW" => "96940", + "PY" => "\d{4}", + "RE" => "9[78]4\d{2}", + "RO" => "\d{6}", + "RS" => "\d{5}", + "RU" => "\d{6}", + "SA" => "\d{5}", + "SE" => "^(s-|S-){0,1}[0-9]{3}\s?[0-9]{2}$", + "SG" => "\d{6}", + "SH" => "(ASCN|STHL) 1ZZ", + "SI" => "\d{4}", + "SJ" => "\d{4}", + "SK" => "\d{3}[ ]?\d{2}", + "SM" => "4789\d", + "SN" => "\d{5}", + "SO" => "\d{5}", + "SZ" => "[HLMS]\d{3}", + "TC" => "TKCA 1ZZ", + "TH" => "\d{5}", + "TJ" => "\d{6}", + "TM" => "\d{6}", + "TN" => "\d{4}", + "TR" => "\d{5}", + "TW" => "\d{3}(\d{2})?", + "UA" => "\d{5}", + "UK" => "^(GIR|[A-Z]\d[A-Z\d]??|[A-Z]{2}\d[A-Z\d]??)[ ]??(\d[A-Z]{2})$", + "US" => "^\d{5}([\-]?\d{4})?$", + "UY" => "\d{5}", + "UZ" => "\d{6}", + "VA" => "00120", + "VE" => "\d{4}", + "VI" => "008(([0-4]\d)|(5[01]))([ \-]\d{4})?", + "WF" => "986\d{2}", + "YT" => "976\d{2}", + "YU" => "\d{5}", + "ZA" => "\d{4}", + "ZM" => "\d{5}" + ); + + if ( ! isset ( $zip_regex[ $country_code ] ) || preg_match( "/" . $zip_regex[ $country_code ] . "/i", $zip ) ) { + $ret = true; + } + + return apply_filters( 'edd_is_zip_valid', $ret, $zip, $country_code ); +} /** - * Send Back to Checkout + * Check the purchase to ensure a banned email is not allowed through * - * Used to redirect a user back to the purchase - * page if there are errors present. - * - * @access public - * @since 1.0 + * @since 2.0 * @return void -*/ - -function edd_send_back_to_checkout($query_string = null) { - global $edd_options; - $redirect = get_permalink($edd_options['purchase_page']); - if($query_string) - $redirect .= $query_string; - - wp_redirect($redirect); exit; -} + */ +function edd_check_purchase_email( $valid_data, $posted ) { + + $banned = edd_get_banned_emails(); + + if ( empty( $banned ) ) { + return; + } + + $user_emails = array( $posted['edd_email'] ); + if ( is_user_logged_in() ) { + // The user is logged in, check that their account email is not banned + $user_data = get_userdata( get_current_user_id() ); + $user_emails[] = $user_data->user_email; + + } elseif ( isset( $posted['edd-purchase-var'] ) && $posted['edd-purchase-var'] == 'needs-to-login' ) { + + // The user is logging in, check that their email is not banned + if ( $user_data = get_user_by( 'login', $posted['edd_user_login'] ) ) { + $user_emails[] = $user_data->user_email; + } + + } + + foreach ( $user_emails as $email ) { + + // Set an error and give the customer a general error (don't alert + // them that they were banned) + if ( edd_is_email_banned( $email ) ) { + edd_set_error( 'email_banned', __( 'An internal error has occurred, please try again or contact support.', 'easy-digital-downloads' ) ); + break; + } + } + +} +add_action( 'edd_checkout_error_checks', 'edd_check_purchase_email', 10, 2 ); /** - * Get Success Page URL + * Checks the length of the user's email address. * - * Gets the success page URL. + * @since 3.1.0.5 + * @param array $valid_data The array of validated data. + * @param array $posted_data The array of posted data. + * @return void + */ +function edd_check_purchase_email_length( $valid_data, $posted_data ) { + // Customer emails are limited to 100 characters. + if ( ! empty( $posted_data['edd_email'] ) && strlen( $posted_data['edd_email'] ) > 100 ) { + edd_set_error( 'email_length', __( 'Your email address must be shorter than 100 characters.', 'easy-digital-downloads' ) ); + } +} +add_action( 'edd_checkout_error_checks', 'edd_check_purchase_email_length', 10, 2 ); + +/** + * Process a straight-to-gateway purchase * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_get_success_page_url($query_string = null) { - global $edd_options; - $success_page = get_permalink($edd_options['success_page']); - if($query_string) - $success_page .= $query_string; - return $success_page; -} \ No newline at end of file + * @since 1.7 + * @return void + */ +function edd_process_straight_to_gateway( $data ) { + + $download_id = $data['download_id']; + $options = isset( $data['edd_options'] ) ? $data['edd_options'] : array(); + $quantity = isset( $data['edd_download_quantity'] ) ? $data['edd_download_quantity'] : 1; + + if ( empty( $download_id ) || ! edd_get_download( $download_id ) ) { + return; + } + + $purchase_data = edd_build_straight_to_gateway_data( $download_id, $options, $quantity ); + $enabled_gateways = edd_get_enabled_payment_gateways(); + + if ( ! array_key_exists( $purchase_data['gateway'], $enabled_gateways ) ) { + foreach ( $purchase_data['downloads'] as $download ) { + $options = isset( $download['options'] ) ? $download['options'] : array(); + + $options['quantity'] = isset( $download['quantity'] ) ? $download['quantity'] : 1; + edd_add_to_cart( $download['id'], $options ); + } + + edd_set_error( 'edd-straight-to-gateway-error', __( 'There was an error completing your purchase. Please try again.', 'easy-digital-downloads' ) ); + edd_redirect( edd_get_checkout_uri() ); + } + + edd_set_purchase_session( $purchase_data ); + edd_send_to_gateway( $purchase_data['gateway'], $purchase_data ); +} +add_action( 'edd_straight_to_gateway', 'edd_process_straight_to_gateway' ); diff --git a/includes/query-filters.php b/includes/query-filters.php old mode 100644 new mode 100755 index b07c9e6554d..81e1f7fc348 --- a/includes/query-filters.php +++ b/includes/query-filters.php @@ -1,45 +1,155 @@ 403 ) ); } -add_filter('query_vars', 'edd_query_vars'); +add_action( 'template_redirect', 'edd_block_attachments' ); + +/** + * Removes our tracking query arg so as not to interfere with the WP query. + * + * @see https://core.trac.wordpress.org/ticket/25143 + * + * @since 2.4.3 + * + * @param WP_Query $query. + */ +function edd_unset_discount_query_arg( $query ) { + if ( is_admin() || ! $query->is_main_query() || ! empty( $query->query_vars['edd-api'] ) ) { + return; + } + $discount = $query->get( 'discount' ); + if ( ! empty( $discount ) ) { + + // Unset ref var from $wp_query. + $query->set( 'discount', null ); + + global $wp; + + // Unset ref var from $wp. + unset( $wp->query_vars['discount'] ); + + // If on home (because $wp->query_vars is empty) and 'show_on_front' is page + if ( empty( $wp->query_vars ) && get_option( 'show_on_front' ) === 'page' ) { + + // Reset and re-parse query vars. + $wp->query_vars['page_id'] = get_option( 'page_on_front' ); + $query->parse_query( $wp->query_vars ); + } + } +} +add_action( 'pre_get_posts', 'edd_unset_discount_query_arg', 999999 ); /** - * Blocks access to Download attachments - * - * @access public - * @since 1.2.2 - * @return void -*/ + * Filters on canonical redirects. + * + * @since 2.4.3 + * + * @param string $redirect_url Redirect URL. + * @param string $requested_url Requested URL. + * + * @return string + */ +function edd_prevent_canonical_redirect( $redirect_url, $requested_url ) { -function edd_block_attachments() { + if ( ! is_front_page() ) { + return $redirect_url; + } - if( ! is_attachment() ) - return; + $discount = get_query_var( 'discount' ); - $parent = get_post_field( 'post_parent', get_the_ID() ); + if ( ! empty( $discount ) || false !== strpos( $requested_url, 'discount' ) ) { + $redirect_url = $requested_url; + } - if( ! $parent ) + return $redirect_url; +} +add_action( 'redirect_canonical', 'edd_prevent_canonical_redirect', 0, 2 ); + +/** + * Auto flush permalinks wth a soft flush when a 404 error is detected on an + * EDD page. + * + * @since 2.4.3 + * + * @return string + */ +function edd_refresh_permalinks_on_bad_404() { + global $wp; + + if ( ! is_404() ) { return; + } - if( 'download' != get_post_type( $parent ) ) + if ( isset( $_GET['edd-flush'] ) ) { // WPCS: CSRF ok. return; + } + + if ( false === get_transient( 'edd_refresh_404_permalinks' ) ) { + $slug = defined( 'EDD_SLUG' ) + ? EDD_SLUG + : 'downloads'; + + $parts = explode( '/', $wp->request ); + + if ( $slug !== $parts[0] ) { + return; + } + + flush_rewrite_rules( false ); - wp_die( __( 'You do not have permission to view this file.', 'edd' ), __( 'Error', 'edd' ) ); + set_transient( 'edd_refresh_404_permalinks', 1, HOUR_IN_SECONDS * 12 ); + edd_redirect( home_url( add_query_arg( array( 'edd-flush' => 1 ), $wp->request ) ) ); + } } -add_action( 'template_redirect', 'edd_block_attachments' ); \ No newline at end of file +add_action( 'template_redirect', 'edd_refresh_permalinks_on_bad_404' ); diff --git a/includes/refund-functions.php b/includes/refund-functions.php new file mode 100644 index 00000000000..7a90596491b --- /dev/null +++ b/includes/refund-functions.php @@ -0,0 +1,64 @@ + __( 'Refundable', 'easy-digital-downloads' ), + 'nonrefundable' => __( 'Non-Refundable', 'easy-digital-downloads' ) + ); +} + +/** + * Calculate refund date. + * + * @since 3.0 + * + * @param string $date Date order was completed (Accepts UTC). + * @param int $download_id Download ID. + * + * @return string|false Date refundable (in UTC), false otherwise. + */ +function edd_get_refund_date( $date = '', $download_id = 0 ) { + + // Bail if no date was passed. + if ( empty( $date ) ) { + return false; + } + + $refund_window = absint( edd_get_option( 'refund_window', 30 ) ); + + // Refund window is infinite. + if ( 0 === $refund_window ) { + return false; + } + + if ( ! empty( $download_id ) ) { + $refund_window = edd_get_download_refund_window( $download_id ); + + $date_refundable = \Carbon\Carbon::parse( $date, 'UTC' )->addDays( $refund_window ); + } else { + $date_refundable = \Carbon\Carbon::parse( $date, 'UTC' )->addDays( $refund_window ); + } + + return $date_refundable->toDateTimeString(); +} \ No newline at end of file diff --git a/includes/register-settings.php b/includes/register-settings.php deleted file mode 100755 index 8aa58f49ee3..00000000000 --- a/includes/register-settings.php +++ /dev/null @@ -1,789 +0,0 @@ - ''); // blank option - if($pages) { - foreach ( $pages as $page ) { - $pages_options[$page->ID] = $page->post_title; - } - } - - /* white list our settings, each in their respective section - filters can be used to add more options to each section */ - $edd_settings = array( - 'general' => apply_filters('edd_settings_general', - array( - array( - 'id' => 'test_mode', - 'name' => __('Test Mode', 'edd'), - 'desc' => __('While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing.', 'edd'), - 'type' => 'checkbox' - ), - array( - 'id' => 'purchase_page', - 'name' => __('Checkout Page', 'edd'), - 'desc' => __('This is the checkout page where buyers will complete their purchases', 'edd'), - 'type' => 'select', - 'options' => $pages_options - ), - array( - 'id' => 'success_page', - 'name' => __('Success Page', 'edd'), - 'desc' => __('This is the page buyers are sent to after completing their purchases', 'edd'), - 'type' => 'select', - 'options' => $pages_options - ), - array( - 'id' => 'show_links_on_success', - 'name' => __('Download Links on Success Page', 'edd'), - 'desc' => __('Show a list of all download links on the success page after completing a purchase?', 'edd'), - 'type' => 'checkbox' - ), - array( - 'id' => 'currency_settings', - 'name' => '' . __('Currency Settings', 'edd') . '', - 'desc' => __('Configure the currency options', 'edd'), - 'type' => 'header' - ), - array( - 'id' => 'currency', - 'name' => __('Currency', 'edd'), - 'desc' => __('Choose your currency. Note that some payment gateways have currency restrictions.', 'edd'), - 'type' => 'select', - 'options' => edd_get_currencies() - ), - array( - 'id' => 'currency_position', - 'name' => __('Currency Position', 'edd'), - 'desc' => __('Choose the location of the currency sign.', 'edd'), - 'type' => 'select', - 'options' => array( - 'before' => __('Before - $10', 'edd'), - 'after' => __('After - 10$', 'edd') - ) - ), - array( - 'id' => 'thousands_separator', - 'name' => __('Thousands Separator', 'edd'), - 'desc' => __('The symbol (usually , or .) to separate thousands', 'edd'), - 'type' => 'text', - 'size' => 'small', - 'std' => ',' - ), - array( - 'id' => 'decimal_separator', - 'name' => __('Decimal Separator', 'edd'), - 'desc' => __('The symbol (usually , or .) to separate decimal points', 'edd'), - 'type' => 'text', - 'size' => 'small', - 'std' => '.' - ) - ) - ), - 'gateways' => apply_filters('edd_settings_gateways', - array( - array( - 'id' => 'gateways', - 'name' => __('Payment Gateways', 'edd'), - 'desc' => __('Choose the payment gateways you want to enable.', 'edd'), - 'type' => 'gateways', - 'options' => edd_get_payment_gateways() - ), - array( - 'id' => 'accepted_cards', - 'name' => __('Accepted Payment Method Icons', 'edd'), - 'desc' => __('Display icons for the selected payment methods', 'edd') . '
    ' . __('You will also need to configure your gateway settings if you are accepting credit cards', 'edd'), - 'type' => 'multicheck', - 'options' => apply_filters('edd_accepted_payment_icons', array( - 'mastercard' => 'Mastercard', - 'visa' => 'Visa', - 'americanexpress' => 'American Express', - 'discover' => 'Discover', - 'paypal' => 'PayPal' - ) - ) - ), - array( - 'id' => 'paypal', - 'name' => '' . __('PayPal Settings', 'edd') . '', - 'desc' => __('Configure the PayPal settings', 'edd'), - 'type' => 'header' - ), - array( - 'id' => 'paypal_email', - 'name' => __('PayPal Email', 'edd'), - 'desc' => __('Enter your PayPal account\'s email', 'edd'), - 'type' => 'text', - 'size' => 'regular' - ), - array( - 'id' => 'paypal_alternate_verification', - 'name' => __('Alternate PayPal Purchase Verification', 'edd'), - 'desc' => __('If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal.', 'edd'), - 'type' => 'checkbox' - ), - array( - 'id' => 'disable_paypal_verification', - 'name' => __('Disable PayPal IPN Verification', 'edd'), - 'desc' => __('If payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases.', 'edd'), - 'type' => 'checkbox' - ) - ) - ), - 'emails' => apply_filters('edd_settings_emails', - array( - array( - 'id' => 'email_template', - 'name' => __('Email Template', 'edd'), - 'desc' => __('Choose a template. Click "Save Changes" then "Preview Purchase Receipt" to see the new template.', 'edd'), - 'type' => 'select', - 'options' => edd_get_email_templates() - ), - array( - 'id' => 'email_settings', - 'name' => '', - 'desc' => '', - 'type' => 'hook', - ), - array( - 'id' => 'from_name', - 'name' => __('From Name', 'edd'), - 'desc' => __('The name purchase receipts are said to come from. This should probably be your site or shop name.', 'edd'), - 'type' => 'text' - ), - array( - 'id' => 'from_email', - 'name' => __('From Email', 'edd'), - 'desc' => __('Email to send purchase receipts from. This will act as the "from" and "reply-to" address.', 'edd'), - 'type' => 'text' - ), - array( - 'id' => 'purchase_subject', - 'name' => __('Purchase Email Subject', 'edd'), - 'desc' => __('Enter the subject line for the purchase receipt email', 'edd'), - 'type' => 'text' - ), - array( - 'id' => 'purchase_receipt', - 'name' => __('Purchase Receipt', 'edd'), - 'desc' => __('Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:', 'edd') . '
    ' . - '{download_list} - ' . __('A list of download URLs for each download purchased', 'edd') . '
    ' . - '{name} - ' . __('The buyer\'s name', 'edd') . '
    ' . - '{date} - ' . __('The date of the purchase', 'edd') . '
    ' . - '{price} - ' . __('The total price of the purchase', 'edd') . '
    ' . - '{receipt_id} - ' . __('The unique ID number for this purchase receipt', 'edd') . '
    ' . - '{payment_method} - ' . __('The method of payment used for this purchase', 'edd') . '
    ' . - '{sitename} - ' . __('Your site name', 'edd'), - 'type' => 'rich_editor' - ) - ) - ), - 'styles' => apply_filters('edd_settings_styles', - array( - array( - 'id' => 'disable_styles', - 'name' => __('Disable Styles', 'edd'), - 'desc' => __('Check this to disable all included styling', 'edd'), - 'type' => 'checkbox' - ), - array( - 'id' => 'buton_header', - 'name' => '' . __('Buttons', 'edd') . '', - 'desc' => __('Options for add to cart and purchase buttons', 'edd'), - 'type' => 'header' - ), - array( - 'id' => 'button_style', - 'name' => __('Default Button Style', 'edd'), - 'desc' => __('Choose the style you want to use for the buttons.', 'edd'), - 'type' => 'select', - 'options' => edd_get_button_styles() - ), - array( - 'id' => 'checkout_color', - 'name' => __('Default Button Color', 'edd'), - 'desc' => __('Choose the color you want to use for the buttons.', 'edd'), - 'type' => 'select', - 'options' => edd_get_button_colors() - ) - ) - ), - 'misc' => apply_filters('edd_settings_misc', - array( - array( - 'id' => 'disable_ajax_cart', - 'name' => __('Disable Ajax', 'edd'), - 'desc' => __('Check this to disable AJAX for the shopping cart.', 'edd'), - 'type' => 'checkbox' - ), - array( - 'id' => 'jquery_validation', - 'name' => __('Enable jQuery Validation', 'edd'), - 'desc' => __('Check this to enable jQuery validation on the checkout form.', 'edd'), - 'type' => 'checkbox' - ), - array( - 'id' => 'logged_in_only', - 'name' => __('Disable Guest Checkout', 'edd'), - 'desc' => __('Require that users be logged-in to purchase files.', 'edd'), - 'type' => 'checkbox' - ), - array( - 'id' => 'show_register_form', - 'name' => __('Show Register / Login Form?', 'edd'), - 'desc' => __('Display the registration and login forms on the checkout page for non-logged-in users.', 'edd'), - 'type' => 'checkbox', - ), - array( - 'id' => 'download_link_expiration', - 'name' => __('Download Link Expiration', 'edd'), - 'desc' => __('How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours.', 'edd'), - 'type' => 'text', - 'size' => 'small' - ), - array( - 'id' => 'disable_redownload', - 'name' => __('Disable Redownload?', 'edd'), - 'desc' => __('Check this if you do not want to allow users to redownload items from their purchase history.', 'edd'), - 'type' => 'checkbox', - ), - array( - 'id' => 'terms', - 'name' => '' . __('Terms of Agreement', 'edd') . '', - 'desc' => '', - 'type' => 'header', - ), - array( - 'id' => 'show_agree_to_terms', - 'name' => __('Agree to Terms', 'edd'), - 'desc' => __('Check this to show an agree to terms on the checkout that users must agree to before purchasing.', 'edd'), - 'type' => 'checkbox', - ), - array( - 'id' => 'agree_label', - 'name' => __('Agree to Terms Label', 'edd'), - 'desc' => __('Label shown next to the agree to terms check box.', 'edd'), - 'type' => 'text', - 'size' => 'regular' - ), - array( - 'id' => 'agree_text', - 'name' => __('Agreement Text', 'edd'), - 'desc' => __('If Agree to Terms is checked, enter the agreement terms here.', 'edd'), - 'type' => 'rich_editor', - ), - array( - 'id' => 'checkout_label', - 'name' => __('Complete Purchase Text', 'edd'), - 'desc' => __('The button label for completing a purchase.', 'edd'), - 'type' => 'text', - ), - array( - 'id' => 'add_to_cart_text', - 'name' => __('Add to Cart Text', 'edd'), - 'desc' => __('Text shown on the Add to Cart Buttons', 'edd'), - 'type' => 'text' - ) - ) - ) - ); - - if( false == get_option( 'edd_settings_general' ) ) { - add_option( 'edd_settings_general' ); - } - if( false == get_option( 'edd_settings_gateways' ) ) { - add_option( 'edd_settings_gateways' ); - } - if( false == get_option( 'edd_settings_emails' ) ) { - add_option( 'edd_settings_emails' ); - } - if( false == get_option( 'edd_settings_styles' ) ) { - add_option( 'edd_settings_styles' ); - } - if( false == get_option( 'edd_settings_misc' ) ) { - add_option( 'edd_settings_misc' ); - } - - - add_settings_section( - 'edd_settings_general', - __('General Settings', 'edd'), - 'edd_settings_general_description_callback', - 'edd_settings_general' - ); - - foreach($edd_settings['general'] as $option) { - add_settings_field( - 'edd_settings_general[' . $option['id'] . ']', - $option['name'], - 'edd_' . $option['type'] . '_callback', - 'edd_settings_general', - 'edd_settings_general', - array( - 'id' => $option['id'], - 'desc' => $option['desc'], - 'name' => $option['name'], - 'section' => 'general', - 'size' => isset($option['size']) ? $option['size'] : null, - 'options' => isset($option['options']) ? $option['options'] : '', - 'std' => isset($option['std']) ? $option['std'] : '' - ) - ); - } - - add_settings_section( - 'edd_settings_gateways', - __('Payment Gateway Settings', 'edd'), - 'edd_settings_gateways_description_callback', - 'edd_settings_gateways' - ); - - foreach($edd_settings['gateways'] as $option) { - add_settings_field( - 'edd_settings_gateways[' . $option['id'] . ']', - $option['name'], - 'edd_' . $option['type'] . '_callback', - 'edd_settings_gateways', - 'edd_settings_gateways', - array( - 'id' => $option['id'], - 'desc' => $option['desc'], - 'name' => $option['name'], - 'section' => 'gateways', - 'size' => isset($option['size']) ? $option['size'] : null, - 'options' => isset($option['options']) ? $option['options'] : '', - 'std' => isset($option['std']) ? $option['std'] : '' - ) - ); - } - - add_settings_section( - 'edd_settings_emails', - __('Email Settings', 'edd'), - 'edd_settings_emails_description_callback', - 'edd_settings_emails' - ); - - foreach($edd_settings['emails'] as $option) { - add_settings_field( - 'edd_settings_emails[' . $option['id'] . ']', - $option['name'], - 'edd_' . $option['type'] . '_callback', - 'edd_settings_emails', - 'edd_settings_emails', - array( - 'id' => $option['id'], - 'desc' => $option['desc'], - 'name' => $option['name'], - 'section' => 'emails', - 'size' => isset($option['size']) ? $option['size'] : null, - 'options' => isset($option['options']) ? $option['options'] : '', - 'std' => isset($option['std']) ? $option['std'] : '' - ) - ); - } - - add_settings_section( - 'edd_settings_styles', - __('Style Settings', 'edd'), - 'edd_settings_styles_description_callback', - 'edd_settings_styles' - ); - - foreach($edd_settings['styles'] as $option) { - add_settings_field( - 'edd_settings_styles[' . $option['id'] . ']', - $option['name'], - 'edd_' . $option['type'] . '_callback', - 'edd_settings_styles', - 'edd_settings_styles', - array( - 'id' => $option['id'], - 'desc' => $option['desc'], - 'name' => $option['name'], - 'section' => 'styles', - 'size' => isset($option['size']) ? $option['size'] : '' , - 'options' => isset($option['options']) ? $option['options'] : '', - 'std' => isset($option['std']) ? $option['std'] : '' - ) - ); - } - - - add_settings_section( - 'edd_settings_misc', - __('Misc Settings', 'edd'), - 'edd_settings_misc_description_callback', - 'edd_settings_misc' - ); - - foreach($edd_settings['misc'] as $option) { - add_settings_field( - 'edd_settings_misc[' . $option['id'] . ']', - $option['name'], - 'edd_' . $option['type'] . '_callback', - 'edd_settings_misc', - 'edd_settings_misc', - array( - 'id' => $option['id'], - 'desc' => $option['desc'], - 'name' => $option['name'], - 'section' => 'misc', - 'size' => isset($option['size']) ? $option['size'] : '' , - 'options' => isset($option['options']) ? $option['options'] : '', - 'std' => isset($option['std']) ? $option['std'] : '' - ) - ); - } - - // creates our settings in the options table - register_setting('edd_settings_general', 'edd_settings_general', 'edd_settings_sanitize'); - register_setting('edd_settings_gateways', 'edd_settings_gateways', 'edd_settings_sanitize'); - register_setting('edd_settings_emails', 'edd_settings_emails', 'edd_settings_sanitize'); - register_setting('edd_settings_styles', 'edd_settings_styles', 'edd_settings_sanitize'); - register_setting('edd_settings_misc', 'edd_settings_misc', 'edd_settings_sanitize'); -} -add_action('admin_init', 'edd_register_settings'); - - -/** - * Settings General Description Callback - * - * Renders the general section description. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_settings_general_description_callback() { - //echo __('Configure the settings below', 'edd'); -} - - -/** - * Settings Gateways Description Callback - * - * Renders the gateways section description. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_settings_gateways_description_callback() { - //echo __('Configure the settings below', 'edd'); -} - - -/** - * Settings Emails Description Callback - * - * Renders the emails section description. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_settings_emails_description_callback() { - //echo __('Configure the settings below', 'edd'); -} - - -/** - * Settings Styles Description Callback - * - * Renders the styles section description. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_settings_styles_description_callback() { - //echo __('Configure the settings below', 'edd'); -} - - -/** - * Settings Misc Description Callback - * - * Renders the misc section description. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_settings_misc_description_callback() { - //echo __('Configure the settings below', 'edd'); -} - - -/** - * Header Callback - * - * Renders the header. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_header_callback($args) { - echo ''; -} - - -/** - * Checkbox Callback - * - * Renders checkboxes. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_checkbox_callback($args) { - - global $edd_options; - - $checked = isset($edd_options[$args['id']]) ? checked(1, $edd_options[$args['id']], false) : ''; - $html = ''; - $html .= ''; - - echo $html; - -} - - -/** - * Multicheck Callback - * - * Renders multiple checkboxes. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_multicheck_callback($args) { - - global $edd_options; - - foreach($args['options'] as $key => $option) : - if(isset($edd_options[$args['id']][$key])) { $enabled = $option; } else { $enabled = NULL; } - echo ' '; - echo '
    '; - endforeach; - echo '

    ' . $args['desc'] . '

    '; - -} - - -/** - * Gateways Callback - * - * Renders gateways fields. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_gateways_callback($args) { - - global $edd_options; - - foreach($args['options'] as $key => $option) : - if(isset($edd_options['gateways'][$key])) { $enabled = '1'; } else { $enabled = NULL; } - echo ' '; - echo '
    '; - endforeach; - -} - - -/** - * Text Callback - * - * Renders text fields. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_text_callback($args) { - - global $edd_options; - - if(isset($edd_options[$args['id']])) { $value = $edd_options[$args['id']]; } else { $value = isset($args['std']) ? $args['std'] : ''; } - $size = isset($args['size']) && !is_null($args['size']) ? $args['size'] : 'regular'; - $html = ''; - $html .= ''; - - echo $html; - -} - - -/** - * Select Callback - * - * Renders select fields. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_select_callback($args) { - - global $edd_options; - - $html = ''; - $html .= ''; - - echo $html; - -} - - -/** - * Rich Editor Callback - * - * Renders rich editor fields. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_rich_editor_callback($args) { - - global $edd_options, $wp_version; - - if(isset($edd_options[$args['id']])) { $value = $edd_options[$args['id']]; } else { $value = isset($args['std']) ? $args['std'] : ''; } - if($wp_version >= 3.3 && function_exists('wp_editor')) { - $html = wp_editor($value, 'edd_settings_' . $args['section'] . '[' . $args['id'] . ']', array('textarea_name' => 'edd_settings_' . $args['section'] . '[' . $args['id'] . ']')); - } else { - $html = ''; - } - $html .= '
    '; - - echo $html; - -} - - -/** - * Upload Callback - * - * Renders upload fields. - * - * @access private - * @since 1.0 - * @return void -*/ - -function edd_upload_callback($args) { - - global $edd_options; - - if(isset($edd_options[$args['id']])) { $value = $edd_options[$args['id']]; } else { $value = isset($args['std']) ? $args['std'] : ''; } - $size = isset($args['size']) && !is_null($args['size']) ? $args['size'] : 'regular'; - $html = ''; - $html .= ' '; - $html .= ''; - - echo $html; - -} - - -/** - * Hook Callback - * - * Adds a do_action() hook in place of the field - * - * @access private - * @since 1.0.8.2 - * @return void -*/ - -function edd_hook_callback($args) { - - do_action('edd_' . $args['id']); - -} - - - -/** - * Settings Sanitization - * - * Adds a settings error (for the updated message) - * At some point this will validate input - * - * @access private - * @since 1.0.8.2 - * @return void -*/ - -function edd_settings_sanitize( $input ) { - add_settings_error('edd-notices', '', __('Settings Updated', 'edd'), 'updated'); - return $input; -} - - -/** - * Get Settings - * - * Retrieves all plugin settings and returns them - * as a combined array. - * - * @access public - * @since 1.0 - * @return array -*/ - -function edd_get_settings() { - $general_settings = is_array(get_option('edd_settings_general')) ? get_option('edd_settings_general') : array(); - $gateway_settings = is_array(get_option('edd_settings_gateways')) ? get_option('edd_settings_gateways') : array(); - $email_settings = is_array(get_option('edd_settings_emails')) ? get_option('edd_settings_emails') : array(); - $style_settings = is_array(get_option('edd_settings_styles')) ? get_option('edd_settings_styles') : array(); - $misc_settings = is_array(get_option('edd_settings_misc')) ? get_option('edd_settings_misc') : array(); - - return array_merge($general_settings, $gateway_settings, $email_settings, $style_settings, $misc_settings); -} \ No newline at end of file diff --git a/includes/reports/class-init.php b/includes/reports/class-init.php new file mode 100644 index 00000000000..42a1a79b5e3 --- /dev/null +++ b/includes/reports/class-init.php @@ -0,0 +1,261 @@ +legacy_reports( $reports ); + + $reports = $this->register_core_endpoint_views( $reports ); + + /** + * Fires when the Reports API is initialized. + * + * Use this hook to register new reports and endpoints. + * + * Example: + * + * add_action( 'edd_reports_init', function( $reports ) { + * + * try { + * $reports->add_report( 'test', array( + * 'label' => 'Test', + * 'priority' => 11, + * 'endpoints' => array( + * + * // Endpoints supporting multiple view groups can be reused: + * 'tiles' => array( 'test_endpoint', ... ), + * 'tables' => array( 'test_endpoint' ), + * ), + * ) ); + * + * $reports->register_endpoint( 'test_endpoint', array( + * 'label' => 'Test Endpoint', + * 'views' => array( + * + * // Possible to register a single endpoint for multiple view groups. + * 'tile' => array( + * 'data_callback' => '__return_true', + * 'display_args' => array( + * 'context' => 'secondary', + * 'comparison_label' => 'Filtered by ...', + * ), + * ), + * 'table' => array( ... ), + * ), + * ) ); + * } catch ( \EDD_Exception $exception ) { + * + * edd_debug_log_exception( $exception ); + * + * } + * + * } ); + * + * Reports and endpoints can also be registered using standalone functions: + * + * add_action( 'edd_reports_init', function() { + * + * \EDD\Reports\add_report( 'test', array( ... ) ); + * + * \EDD\Reports\register_endpoint( 'test_endpoint', array( ... ) ); + * + * } ); + * + * @since 3.0 + * + * @param Data\Report_Registry $reports Report registry instance, + * passed by reference. + */ + do_action_ref_array( 'edd_reports_init', array( &$reports ) ); + } + + /** + * Maybe add legacy reports if any exist + * + * @since 3.0 + * + * @param Data\Report_Registry $reports Reports registry instance. + * @return Data\Report_Registry Reports registry. + */ + private function legacy_reports( $reports ) { + + // Bail if no legacy reports + if ( ! has_filter( 'edd_report_views' ) ) { + return $reports; + } + + /** + * Filters legacy 'Reports' tab views. + * + * @since 1.4 + * @deprecated 3.0 Use {@see 'edd_reports_get_reports'} + * @see 'edd_reports_get_reports' + * + * @param array $views 'Reports' tab views. + */ + $legacy_views = edd_apply_filters_deprecated( 'edd_report_views', array( array() ), '3.0', 'edd_reports_get_reports' ); + + // Bail if no legacy views + if ( empty( $legacy_views ) ) { + return $reports; + } + + // Default legacy report priority to position them towards the bottom + $priority = 800; + + // Loop through views and try to convert them + foreach ( $legacy_views as $report_id => $label ) { + + // Legacy "_tab_" action + if ( has_action( "edd_reports_tab_{$report_id}" ) ) { + $hook = "edd_reports_tab_{$report_id}"; + + // Legacy "_view_" action + } elseif ( has_action( "edd_reports_view_{$report_id}" ) ) { + $hook = "edd_reports_view_{$report_id}"; + + // Skip + } else { + continue; + } + + // Create a callback function + $callback = function() use ( $hook ) { + /** + * Legacy: Fires inside the content area of the currently active Reports tab. + * + * The dynamic portion of the hook name, `$report_id` refers to the slug of + * the current reports tab. + * + * @since 1.0 + * @deprecated 3.0 Use the new Reports API to register new tabs. + * @see \EDD\Reports\add_report() + * + * @param \EDD\Reports\Data\Report|\WP_Error $report The current report object, + * or WP_Error if invalid. + */ + edd_do_action_deprecated( $hook, array(), '3.0', '\EDD\Reports\add_report' ); + }; + + // Legacy label + $legacy_label = $label . '' . __( 'Legacy', 'easy-digital-downloads' ) . ''; + + try { + // Add report + $reports->add_report( $report_id, array( + 'label' => $legacy_label, + 'group' => 'core', + 'icon' => 'chart-area', + 'priority' => $priority, + 'display_callback' => $callback, + 'filters' => array( + 'dates', + 'taxes', + ), + ) ); + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } + + // Bump the priority + ++$priority; + } + + // Return reports array + return $reports; + } + + /** + * Registers the core endpoint views. + * + * @since 3.0 + * + * @param Data\Report_Registry $reports Reports registry instance. + */ + private function register_core_endpoint_views( $reports ) { + $views = Data\Endpoint_View_Registry::instance(); + $core_views = $views->get_core_views(); + + try { + foreach ( $core_views as $view_id => $atts ) { + $views->register_endpoint_view( $view_id, $atts ); + } + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } + + return $reports; + } + + +} diff --git a/includes/reports/class-registry.php b/includes/reports/class-registry.php new file mode 100644 index 00000000000..a56267e01ae --- /dev/null +++ b/includes/reports/class-registry.php @@ -0,0 +1,102 @@ + $value ) { + if ( in_array( $attribute, $skip, true ) ) { + continue; + } + + if ( empty( $value ) ) { + throw Reports_Exceptions\Invalid_Parameter::from( $attribute, __METHOD__, $item_id ); + } + } + } + + /** + * Retrieves all registered items with a given sorting scheme. + * + * @since 3.0 + * + * @param string $sort Optional. How to sort the list of registered items before retrieval. + * Accepts 'priority' or 'ID' (alphabetized by item ID), or empty (none). + * Default empty. + * @return array An array of all registered items, sorted if `$sort` is not empty. + */ + public function get_items_sorted( $sort = '' ) { + // If sorting, handle it before retrieval from the ArrayObject. + switch( $sort ) { + case 'ID': + parent::ksort(); + break; + + case 'priority': + parent::uasort( array( $this, 'priority_sort' ) ); + break; + + default: break; + } + + return parent::get_items(); + } + + /** + * Sorting helper to sort items by priority. + * + * @since 3.0 + * + * @param array $a Item A. + * @param array $b Item B + * @return int Zero (0) if `$a` equals `$b`. Minus one (-1) if `$a` is less than `$b`, otherwise one (1). + */ + public function priority_sort( $a, $b ) { + if ( $a['priority'] == $b['priority'] ) { + return 0; + } + + return ( $a['priority'] < $b['priority'] ) ? -1 : 1; + } + +} diff --git a/includes/reports/data/charts/v2/class-bar-dataset.php b/includes/reports/data/charts/v2/class-bar-dataset.php new file mode 100644 index 00000000000..1fdce8e1031 --- /dev/null +++ b/includes/reports/data/charts/v2/class-bar-dataset.php @@ -0,0 +1,34 @@ +setup_error_logger(); + + $this->set_id( $dataset_id ); + $this->set_endpoint( $endpoint ); + $this->validate( $options ); + } + + /** + * Retrieves the dataset ID. + * + * @since 3.0 + * + * @return string Dataset ID. + */ + public function get_id() { + return $this->dataset_id; + } + + /** + * Sets the dataset ID. + * + * @since 3.0 + * + * @param string $dataset_id Dataset ID + */ + private function set_id( $dataset_id ) { + $this->dataset_id = sanitize_key( $dataset_id ); + } + + /** + * Sets the chart endpoint object. + * + * @since 3.0 + * + * @param EDD\Reports\Data\Chart_Endpoint $endpoint Chart_Endpoint object. + */ + private function set_endpoint( $endpoint ) { + $this->endpoint = $endpoint; + } + + /** + * Retrieves the raw dataset options. + * + * @since 3.0 + * + * @return array Dataset options (raw). + */ + public function get_options() { + return $this->options; + } + + /** + * Retrieves the chart endpoint object for this dataset. + * + * @since 3.0 + * + * @return Chart_Endpoint Chart endpoint. + */ + public function get_endpoint() { + return $this->endpoint; + } + + /** + * Retrieves the list of local fields. + * + * @since 3.0 + * + * @return array List of local fields. + */ + public function get_fields() { + return $this->fields; + } + + /** + * Retrieves the list of global fields. + * + * @since 3.0 + * + * @return array List of global fields. + */ + public function get_global_fields() { + return $this->global_fields; + } + + /** + * Retrieves the list of fields for the current dataset. + * + * Includes the global fields. + * + * @since 3.0 + * + * @return array List of fields available to the dataset. + */ + public function get_all_fields() { + $fields = array_merge( $this->get_global_fields(), $this->get_fields() ); + + /** + * Filters the fields available to a ChartJS graph. + * + * @since 3.0 + * + * @param array $fields ChartJS fields (global and local). + * @param Dataset $this Dataset instance. + */ + return apply_filters( 'edd_reports_chart_fields', $fields, $this ); + } + + /** + * Attempts to retrieve data associated with the current dataset. + * + * @since 3.0 + * + * @return mixed Data associated with the current dataset. + */ + public function get_data() { + return $this->get_endpoint()->get_data_by_set( $this->get_id() ); + } + + /** + * Performs validation on incoming dataset options. + * + * @since 3.0 + * + * @param array $options Dataset options. + */ + public function validate( $options ) { + $fields = $this->get_all_fields(); + + // Strip invalid options. + foreach ( $options as $key => $value ) { + if ( ! in_array( $key, $fields, true ) ) { + unset( $options[ $key ] ); + } + } + + $data = $this->get_data(); + $processed = array(); + + if ( ! empty( $data ) ) { + + $options['data'] = $this->parse_data_for_output( $data ); + + $this->options = $options; + + } else { + + $message = sprintf( 'The data for the \'%1$s\' dataset for the \'%2$s\' endpoint in the \'%3$s\' report is missing or invalid.', + $this->get_id(), + $this->get_endpoint()->get_id(), + $this->get_endpoint()->get_report_id() + ); + + $this->errors->add( 'missing_chart_data', $message, $data ); + } + } + + /** + * Parses the dataset data for output via JS. + * + * @since 3.0 + * + * @param array $data Dataset data. + * @return array Processed data. + */ + public function parse_data_for_output( $data ) { + + if ( $this instanceof Pie_Dataset ) { + + $processed = $data; + + } else { + + foreach ( $data as $key => $values ) { + if ( is_array( $values ) && isset( $values[1] ) ) { + $processed[ $key ] = array( + 'x' => $this->adjust_time_string( $values[0] ), + 'y' => $values[1], + ); + } else { + $processed[ $key ] = array( + 'x' => $this->adjust_time_string( $values ), + ); + } + } + } + + return $processed; + } + + /** + * Given a date as a string or numeric timestamp, adjust it for a specific timezone. + * + * This allows the points on the graph to line up with the ticks, which are already adjusted. + * + * @since 3.1 + * + * @param string|int $time_string The time string to possibly adjust. + * + * @return string If a timestamp, it's adjusted for the timezone of the store. + */ + private function adjust_time_string( $time_string ) { + if ( is_numeric( $time_string ) ) { + $timezone = new \DateTimeZone( edd_get_timezone_id() ); + $date_on_chart = new \DateTime( '@' . $time_string ); + + $time_string = $date_on_chart->setTimeZone( $timezone )->format( 'Y-m-d H:i:s' ); + } + + return $time_string; + } + + /** + * Determines whether the dataset has generated errors during instantiation. + * + * @since 3.0 + * + * @return bool True if errors have been logged, otherwise false. + */ + public function has_errors() { + if ( method_exists( $this->errors, 'has_errors' ) ) { + return $this->errors->has_errors(); + } else { + $errors = $this->errors->get_error_codes(); + + return ! empty( $errors ); + } + } + + /** + * Retrieves any logged errors for the dataset. + * + * @since 3.0 + * + * @return \WP_Error WP_Error object for the current dataset. + */ + public function get_errors() { + return $this->errors; + } + + /** + * Sets up the WP_Error instance. + * + * @since 3.0 + */ + public function setup_error_logger() { + if ( ! isset( $this->errors ) ) { + $this->errors = new \WP_Error(); + } + } + +} diff --git a/includes/reports/data/charts/v2/class-line-dataset.php b/includes/reports/data/charts/v2/class-line-dataset.php new file mode 100644 index 00000000000..3c05bfff09c --- /dev/null +++ b/includes/reports/data/charts/v2/class-line-dataset.php @@ -0,0 +1,39 @@ +setup_error_logger(); + $this->set_type( $endpoint->get_type() ); + $this->set_endpoint( $endpoint ); + + $options = $endpoint->get_options(); + + if ( $this->is_pie_manifest() && ! empty( $options['labels'] ) ) { + $this->set_labels( $options['labels'] ); + + unset( $options['labels'] ); + } + + $this->set_options( $options ); + } + + /** + * Retrieves the chart type. + * + * @since 3.0 + * + * @return string Chart type. + */ + public function get_type() { + return $this->type; + } + + /** + * Sets the chart type for the manifest. + * + * @since 3.0 + * + * @param string $type Chart type to be manifested. + */ + private function set_type( $type ) { + $this->type = sanitize_key( $type ); + } + + /** + * Retrieves the chart endpoint object for this manifest. + * + * @since 3.0 + * + * @return Chart_Endpoint Chart endpoint. + */ + public function get_endpoint() { + return $this->endpoint; + } + + /** + * Sets the chart endpoint object. + * + * @since 3.0 + * + * @param EDD\Reports\Data\Chart_Endpoint $endpoint Chart_Endpoint object. + */ + private function set_endpoint( $endpoint ) { + $this->endpoint = $endpoint; + } + + /** + * Stores the unfiltered chart options for later access. + * + * @since 3.0 + * + * @param array $options Chart options and datasets. + */ + private function set_options( $options ) { + if ( ! empty( $options['datasets'] ) && is_array( $options['datasets'] ) ) { + + foreach ( $options['datasets'] as $id => $data ) { + $this->add_dataset( $id, $data ); + } + + } else { + + $message = sprintf( 'The %s endpoint has no datasets.', $this->get_endpoint()->get_id() ); + + $this->errors->add( 'missing_chart_datasets', $message, $this->get_endpoint() ); + + } + + unset( $options['datasets'] ); + + $this->options = $options; + } + + /** + * Retrieves parsed options for the chart manifest. + * + * @since 3.0 + * + * @return array Chart options. + */ + public function get_options() { + return $this->options; + } + + /** + * Retrieves the manifest datasets. + * + * @since 3.0 + * + * @return Dataset[] Datasets for this chart if any are defined, otherwise an empty array. + */ + public function get_datasets() { + return $this->datasets; + } + + /** + * Determines whether the current chart manifest contains any datasets. + * + * @since 3.0 + * + * @return bool True if there are datasets, otherwise false. + */ + public function has_datasets() { + $datasets = $this->get_datasets(); + + return ! empty( $datasets ); + } + + /** + * Sets the labels property (for pie and doughnut charts). + * + * @since 3.0 + * + * @param array $labels Array of pie or doughnut chart labels. + */ + private function set_labels( $labels ) { + $this->labels = $labels; + } + + /** + * Retrieves the manifest labels (for pie and doughnut charts). + * + * @since 3.0 + */ + public function get_labels() { + return $this->labels; + } + + /** + * Determines whether the current chart manifest contains any labels (for pie and doughnut charts). + * + * @since 3.0 + * + * @return bool True if there are labels, otherwise false. + */ + public function has_labels() { + $labels = $this->get_labels(); + + return ! empty( $labels ); + } + + /** + * Adds a dataset. + * + * @since 3.0 + * + * @param string $dataset_id ID to associate the dataset with. + * @param array $options Dataset options. + * @return bool True if the dataset was added, otherwise false. + */ + public function add_dataset( $dataset_id, $options ) { + $handler = $this->get_dataset_handler(); + + if ( ! empty( $handler ) && class_exists( $handler ) ) { + /** @var Dataset $dataset */ + $dataset = new $handler( $dataset_id, $this->get_endpoint(), $options ); + + if ( ! $dataset->has_errors() ) { + + $this->datasets[ $dataset_id ] = $dataset; + + + return true; + + } else { + + $this->errors->add( 'dataset_errors_passthrough', 'Errors have been passed through from dataset parsing.', $dataset->get_errors() ); + + } + + } + + return false; + } + + /** + * Retrieves the handler class for the current dataset type. + * + * @since 3.0 + * + * @return string Dataset handler class. + */ + public function get_dataset_handler() { + $handler = ''; + + switch( $this->get_type() ) { + + case 'doughnut': + case 'pie': + $handler = 'EDD\Reports\Data\Charts\v2\Pie_Dataset'; + break; + + case 'bar': + $handler = 'EDD\Reports\Data\Charts\v2\Bar_Dataset'; + break; + + case 'line': + $handler = 'EDD\Reports\Data\Charts\v2\Line_Dataset'; + break; + + + } + + return $handler; + } + + /** + * Generate the name of an element used to reference a rendered chart. + * + * @since 3.0 + * + * @return string + */ + public function get_target_el() { + $endpoint = $this->get_endpoint(); + $default = "edd_reports_graph_{$endpoint->get_id()}"; + + return $endpoint->get_display_arg( 'target', $default ); + } + + /** + * Renders the manifest in JS form. + * + * @since 3.0 + */ + public function render() { + // Render a element to inject the chart in to. + printf( '', esc_attr( $this->get_target_el() ) ); + + // Enqueue script and configuration to render the chart. + wp_enqueue_script( 'edd-admin-reports' ); + + wp_add_inline_script( + 'edd-admin-reports', + sprintf( 'window.edd.renderChart(%s)', wp_json_encode( $this->build_config() ) ) + ); + } + + /** + * Builds the chart config. + * + * @since 3.0 + * + * @return object Config object. + */ + public function build_config() { + $config = new \stdClass(); + + // Dates. + $dates = Reports\get_dates_filter( 'objects' ); + $day_by_day = Reports\get_dates_filter_day_by_day(); + $hour_by_hour = Reports\get_dates_filter_hour_by_hour(); + + // Adjust end date forward by 1 second to push into the next day (for ChartJS display purposes). + $dates['end']->addSeconds( 1 ); + + // Get the timezone ID for parsing. + $timezone = edd_get_timezone_id(); + + // Apply UTC offset. + $dates['start']->setTimezone( $timezone ); + $dates['end']->setTimezone( $timezone ); + + $time_format = 'MMM YYYY'; + + if ( $hour_by_hour ) { + $time_format = 'hA'; + } else if ( $day_by_day ) { + $time_format = 'MMM D'; + } + + $config->type = $this->get_type(); + $config->data = $this->get_chart_data(); + $config->options = $this->get_chart_options(); + $config->target = $this->get_target_el(); + $config->dates = array_merge( + $dates, + array( + 'hour_by_hour' => $hour_by_hour, + 'day_by_day' => $day_by_day, + 'utc_offset' => esc_js( EDD()->utils->get_gmt_offset() / HOUR_IN_SECONDS ), + 'timezone' => $timezone, + 'time_format' => $time_format, + ) + ); + + return $config; + } + + /** + * Retrieves the parsed chart datasets as an object. + * + * @since 3.0 + * + * @return array Parsed chart data. + */ + public function get_chart_data() { + $data = array(); + + if ( $this->has_datasets() ) { + $datasets = $this->get_datasets(); + + $data['datasets'] = array(); + + foreach ( $datasets as $id => $set ) { + if ( $set->has_errors() ) { + continue; + } + + $data['datasets'][] = $set->get_options(); + } + } + + if ( $this->is_pie_manifest() ) { + $data['labels'] = $this->get_labels(); + } + + return $data; + } + + /** + * Retrieves the parsed chart options as an object. + * + * @since 3.0 + * + * @return array Parsed chart options. + */ + public function get_chart_options() { + $endpoint_options = $this->get_endpoint()->get_options(); + + if ( $this->is_pie_manifest() ) { + $defaults = array( + 'animation' => array( + 'duration' => 0, + ), + 'responsive' => true, + 'legend' => array( + 'position' => 'left', + ), + ); + } else { + $day_by_day = Reports\get_dates_filter_day_by_day(); + $hour_by_hour = Reports\get_dates_filter_hour_by_hour(); + + $time_unit = 'month'; + $time_format = 'MMM YYYY'; + + if ( $hour_by_hour ) { + $time_unit = 'hour'; + $time_format = 'hA'; + } else if ( $day_by_day ) { + $time_unit = 'day'; + $time_format = 'MMM D'; + } + + $defaults = array( + 'animation' => array( + 'duration' => 0, + ), + 'responsive' => true, + 'hoverMode' => 'index', + 'stacked' => false, + 'title' => array( + 'display' => $this->get_endpoint()->get_label() && $this->get_endpoint()->get( 'show_chart_title' ), + 'text' => $this->get_endpoint()->get_label(), + ), + 'scales' => array( + 'xAxes' => array(), + 'yAxes' => array(), + ), + ); + + $default_xAxes = array( + array( + 'type' => 'time', + 'display' => true, + 'ticks' => array( + 'source' => 'auto', + 'maxRotation' => 0, + ), + 'position' => 'bottom', + 'time' => array( + 'unit' => $time_unit, + 'tooltipFormat' => $time_format, + ), + ), + ); + + $default_yAxes = array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'formattingType' => 'format', + 'beginAtZero' => true, + 'suggestedMin' => 0, + ), + ), + ); + + // Check if specific axes are missing from the endpoint options and load them from defaults. + foreach ( array( 'xAxes', 'yAxes' ) as $axes_name) { + if ( empty( $endpoint_options['scales'][ $axes_name ] ) ) { + $endpoint_options['scales'][ $axes_name ] = ${ "default_{$axes_name}" }; + } + } + + } + + return array_merge( $defaults, $endpoint_options ); + } + + /** + * Determines whether the chart manifest is for a pie or doughnut chart. + * + * @since 3.0 + * + * @return bool True if the manifest is for a pie or doughnut chart, otherwise false. + */ + public function is_pie_manifest() { + return in_array( $this->get_type(), array( 'pie', 'doughnut' ), true ); + } + + /** + * Determines whether the dataset has generated errors during instantiation. + * + * @since 3.0 + * + * @return bool True if errors have been logged, otherwise false. + */ + public function has_errors() { + if ( method_exists( $this->errors, 'has_errors' ) ) { + return $this->errors->has_errors(); + } else { + $errors = $this->errors->get_error_codes(); + + return ! empty( $errors ); + } + } + + /** + * Retrieves any logged errors for the dataset. + * + * @since 3.0 + * + * @return \WP_Error WP_Error object for the current dataset. + */ + public function get_errors() { + return $this->errors; + } + + /** + * Sets up the WP_Error instance. + * + * @since 3.0 + */ + public function setup_error_logger() { + if ( ! isset( $this->errors ) ) { + $this->errors = new \WP_Error(); + } + } + +} diff --git a/includes/reports/data/charts/v2/class-pie-dataset.php b/includes/reports/data/charts/v2/class-pie-dataset.php new file mode 100644 index 00000000000..e1f1d557dfe --- /dev/null +++ b/includes/reports/data/charts/v2/class-pie-dataset.php @@ -0,0 +1,34 @@ +setup_error_logger(); + $this->set_props( $args ); + } + + /** + * Sets props for the object. + * + * @since 3.0 + * + * @param array $attributes Object attributes. + */ + public function set_props( $attributes ) { + if ( ! empty( $attributes['id'] ) ) { + + $this->set_id( $attributes['id'] ); + + } else { + + $this->errors->add( 'missing_object_id', 'The object ID is missing.', $attributes ); + + } + + if ( ! empty( $attributes['label'] ) ) { + + $this->set_label( $attributes['label'] ); + + } else { + + $this->errors->add( 'missing_object_label', 'The object label is missing.', $attributes ); + + } + } + + /** + * Retrieves the object ID. + * + * @since 3.0 + * + * @return string Object ID. + */ + public function get_id() { + return $this->object_id; + } + + /** + * Sets the object ID. + * + * @since 3.0 + * + * @param string $object_id Object ID + * @return void + */ + private function set_id( $object_id ) { + $this->object_id = sanitize_key( $object_id ); + } + + /** + * Retrieves the global label for the current object. + * + * @since 3.0 + * + * @return string Object label string. + */ + public function get_label() { + return $this->label; + } + + /** + * Sets the object label. + * + * @since 3.0 + * + * @param string $label Object label. + * @return void + */ + private function set_label( $label ) { + $this->label = $label; + } + + /** + * Renders the object via its display callback. + * + * Each sub-class must define its own display() method. + * + * @since 3.0 + */ + abstract public function display(); + + /** + * Determines whether the object has generated errors during instantiation. + * + * @since 3.0 + * + * @return bool True if errors have been logged, otherwise false. + */ + public function has_errors() { + if ( method_exists( $this->errors, 'has_errors' ) ) { + return $this->errors->has_errors(); + } else { + $errors = $this->errors->get_error_codes(); + + return ! empty( $errors ); + } + } + + /** + * Retrieves any logged errors for the object. + * + * @since 3.0 + * + * @return \WP_Error WP_Error object for the current object. + */ + public function get_errors() { + return $this->errors; + } + + /** + * Sets up the WP_Error instance for logging errors. + * + * @since 3.0 + */ + public function setup_error_logger() { + if ( ! isset( $this->errors ) ) { + $this->errors = new \WP_Error(); + } + } + +} diff --git a/includes/reports/data/class-chart-endpoint.php b/includes/reports/data/class-chart-endpoint.php new file mode 100644 index 00000000000..49726672809 --- /dev/null +++ b/includes/reports/data/class-chart-endpoint.php @@ -0,0 +1,275 @@ +errors = new \WP_Error(); + + // ID and Label. + $this->set_props( $args ); + + $args = $this->parse_display_props( $args ); + + // Common values set last to account for overrides. + parent::__construct( $args ); + + // Chart props. + $this->setup_chart( $args ); + + } + + /** + * Sets up the chart props needed for rendering. + * + * @since 3.0 + * + * @param array $atts Endpoint attributes. + */ + private function setup_chart( $atts ) { + $view_type = $this->get_view(); + + if ( ! empty( $atts['views'][ $view_type ] ) ) { + + $view_atts = $atts['views'][ $view_type ]; + + if ( ! empty( $view_atts['type'] ) ) { + $this->set_type( $view_atts['type'] ); + } else { + $this->errors->add( + 'missing_chart_type', + sprintf( 'The chart type for \'%1$s\' endpoint is missing.', $this->get_id() ) + ); + } + + if ( ! empty( $view_atts['options'] ) ) { + $this->set_options( $view_atts['options'] ); + } else { + $this->errors->add( + 'missing_chart_options', + sprintf( 'The chart options for the \'%1$s\' endpoint is missing.', $this->get_id() ) + ); + } + + if ( isset( $view_atts['render_js'] ) && is_callable( $view_atts['render_js'] ) ) { + $this->js_callback = $atts['render_js']; + } + + } + + if ( null === $this->js_callback ) { + // Due to the parent constructor firing last, make sure the report gets set for the benefit of the manifest. + if ( ! empty( $atts['report'] ) ) { + parent::set_report_id( $atts['report'] ); + } + + $this->build_manifest(); + } + + } + + /** + * Sets display-related properties for the Endpoint. + * + * @since 3.0 + * + * @param array $atts Endpoint attributes. + */ + private function parse_display_props( $atts ) { + + $view_type = $this->get_view(); + + if ( ! empty( $atts['views'][ $view_type ] ) ) { + + $atts['views'][ $view_type ] = $this->maybe_convert_callbacks_to_methods( $atts['views'][ $view_type ] ); + + } + + return $atts; + } + + /** + * Retrieves the graphing library options set for the current endpoint. + * + * @since 3.0 + * + * @return array Options set for the current graph endpoint. + */ + public function get_options() { + return $this->options; + } + + /** + * Sets options for displaying the graph. + * + * @since 3.0 + * + * @param array $options Options for displaying the graph via the graphing library. + */ + protected function set_options( $options ) { + $this->options = $options; + } + + /** + * Retrieves the value of a graph option if set. + * + * @since 3.0 + * + * @param string $key Option key to retrieve a value for. + * @return mixed Value of the option key if set, otherwise an empty string. + */ + public function get( $key ) { + if ( isset( $this->options[ $key ] ) ) { + $value = $this->options[ $key ]; + } else { + $value = ''; + } + + return $value; + } + + /** + * Retrieves the chart type. + * + * @since 3.0 + * + * @return string Chart type. + */ + public function get_type() { + return $this->type; + } + + /** + * Sets the chart type. + * + * @since 3.0 + * + * @param string $type Chart type to set. + */ + private function set_type( $type ) { + $this->type = sanitize_key( $type ); + } + + /** + * Retrieves the manifest instance. + * + * @since 3.0 + * + * @return Chart\Manifest Chart manifest. + */ + public function get_manifest() { + return $this->manifest; + } + + /** + * Instantiates the manifest based on chart type and options. + * + * @since 3.0 + */ + private function build_manifest() { + $this->manifest = new Chart\Manifest( $this ); + } + + /** + * Retrieves a specific axis' data if set. + * + * @since 3.0 + * + * @param string $set Dataset to retrieve corresponding data for. + * @return array Data corresponding to `$set` if it's set, otherwise an empty array. + */ + public function get_data_by_set( $set ) { + $data = $this->get_data(); + + if ( isset( $data[ $set ] ) ) { + return $data[ $set ]; + } else { + return array(); + } + } + + /** + * Builds and outputs the graph JS to the page. + * + * @since 3.0 + */ + public function display() { + // JS callback override. + if ( is_callable( $this->js_callback ) ) { + call_user_func( $this->js_callback, $this->get_display_args() ); + + return; + } + + // Start parsing the manifest for output as JS. + $manifest = $this->get_manifest(); + + $manifest->render(); + } + +} diff --git a/includes/reports/data/class-endpoint-registry.php b/includes/reports/data/class-endpoint-registry.php new file mode 100644 index 00000000000..0570276ce56 --- /dev/null +++ b/includes/reports/data/class-endpoint-registry.php @@ -0,0 +1,314 @@ +get_items_sorted( $endpoint_id_or_sort ); + } + } + + /** + * Registers a new data endpoint to the master registry. + * + * @since 3.0 + * + * @throws \EDD_Exception if the endpoint could not be validated. + * + * @param string $endpoint_id Reports data endpoint ID. + * @param array $attributes { + * Endpoint attributes. All arguments are required unless otherwise noted. + * + * @type string $label Endpoint label. + * @type int $priority Optional. Priority by which to retrieve the endpoint. Default 10. + * @type array $views { + * Array of view handlers by type. + * + * @type array $view_type { + * View type slug, with array beneath it. + * + * @type callable $data_callback Callback used to retrieve data for the view. + * @type callable $display_callback Callback used to render the view. + * @type array $display_args Optional. Array of arguments to pass to the + * display_callback (if any). Default empty array. + * } + * } + * } + * @return bool True if the endpoint was successfully registered, otherwise false. + */ + public function register_endpoint( $endpoint_id, $attributes ) { + + $defaults = array( + 'label' => '', + 'priority' => 10, + 'views' => array(), + ); + + $attributes = array_merge( $defaults, $attributes ); + + $attributes['id'] = $endpoint_id; + $attributes['views'] = Reports\parse_endpoint_views( $attributes['views'] ); + + // Bail if this endpoint ID is already registered. + if ( $this->offsetExists( $endpoint_id ) ) { + $message = sprintf( 'The \'%1$s\' endpoint already exists and cannot be registered.', $endpoint_id ); + + throw new Utils\Exception( $message ); + } + + try { + $valid = $this->validate_endpoint( $endpoint_id, $attributes ); + } catch ( \EDD_Exception $exception ) { + throw $exception; + } + + if ( false === $valid ) { + return false; + } else { + try { + $return_value = parent::add_item( $endpoint_id, $attributes ); + } catch ( \EDD_Exception $exception ) { + throw $exception; + } + return $return_value; + } + } + + /** + * Validates the endpoint attributes. + * + * @since 3.0 + * + * @throws \EDD_Exception if the `$label` or `$views` attributes are empty. + * @throws \EDD_Exception if any of the `$views` sub-attributes are empty, except `$filters`. + * + * @param string $endpoint_id Reports data endpoint ID. + * @param array $attributes Endpoint attributes. See register_endpoint() for full accepted attributes. + * @return bool True if the endpoint is considered 'valid', otherwise false. + */ + public function validate_endpoint( $endpoint_id, $attributes ) { + $is_valid = true; + + try { + + $this->validate_attributes( $attributes, $endpoint_id ); + + try { + $this->validate_views( $attributes['views'], $endpoint_id ); + + } catch( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + + $is_valid = false; + + throw $exception; + } + + } catch( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + + $is_valid = false; + + throw $exception; + } + + return $is_valid; + } + + /** + * Builds an endpoint object from a registry entry. + * + * @since 3.0 + * + * @param string|Endpoint $endpoint Endpoint ID or object. + * @param string $view_type View type to use when building the object. + * @param string $report Optional. Report ID. Default null. + * @return Endpoint|\WP_Error Endpoint object on success, otherwise a WP_Error object. + */ + public function build_endpoint( $endpoint, $view_type, $report = null ) { + + // If an endpoint object was passed, just return it. + if ( $endpoint instanceof Endpoint ) { + return $endpoint; + } + + try { + $_endpoint = $this->get_endpoint( $endpoint ); + + } catch( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + + return new \WP_Error( 'invalid_endpoint', $exception->getMessage(), $endpoint ); + } + + if ( ! empty( $_endpoint ) ) { + + if ( Reports\validate_endpoint_view( $view_type ) ) { + $_endpoint['report'] = $report; + + $handler = Reports\get_endpoint_handler( $view_type ); + + if ( ! empty( $handler ) && class_exists( $handler ) ) { + $_endpoint = new $handler( $_endpoint ); + + } else { + $_endpoint = new \WP_Error( + 'invalid_handler', + sprintf( 'The handler for the \'%1$s\' view is invalid.', $view_type ), + $handler + ); + } + + } else { + $_endpoint = new \WP_Error( + 'invalid_view', + sprintf( 'The \'%1$s\' view is invalid.', $view_type ) + ); + } + } + + return $_endpoint; + } + + /** + * Validates view properties for an incoming endpoint. + * + * @since 3.0 + * + * @throws \EDD_Exception if the view attributes is empty or it's not a valid view. + * + * @param array $views List of attributes to check. + * @param string $endpoint_id Endpoint ID. + * @return void + */ + public function validate_views( $views, $endpoint_id ) { + $valid_views = Reports\get_endpoint_views(); + + $this->validate_attributes( $views, $endpoint_id ); + + foreach ( $views as $view => $attributes ) { + if ( array_key_exists( $view, $valid_views ) ) { + if ( ! empty( $valid_views[ $view ]['allow_empty'] ) ) { + $skip = $valid_views[ $view ]['allow_empty']; + } else { + $skip = array(); + } + + // View atts have already been parsed at this point, just validate them. + $this->validate_view_attributes( $attributes, $view, $skip ); + } else { + throw Reports_Exceptions\Invalid_View::from( $view, __METHOD__, $endpoint_id ); + } + } + } + + /** + * Validates a list of endpoint view attributes. + * + * @since 3.0 + * + * @throws \EDD_Exception if a required view attribute is empty. + * + * @param array $attributes List of view attributes to check for emptiness. + * @param string $view View slug. + * @param array $skip Optional. List of view attributes to skip validating. + * Default empty array. + * @return void + */ + public function validate_view_attributes( $attributes, $view, $skip = array() ) { + foreach ( $attributes as $attribute => $value ) { + if ( in_array( $attribute, $skip, true ) ) { + continue; + } + + if ( empty( $value ) ) { + throw Reports_Exceptions\Invalid_View_Parameter::from( $attribute, __METHOD__, $view ); + } + } + } +} diff --git a/includes/reports/data/class-endpoint-view-registry.php b/includes/reports/data/class-endpoint-view-registry.php new file mode 100644 index 00000000000..cc07e66e9ad --- /dev/null +++ b/includes/reports/data/class-endpoint-view-registry.php @@ -0,0 +1,244 @@ +get_core_view( $view_id ); + + if ( empty( $view_atts ) ) { + throw new Utils\Exception( sprintf( 'The \'%1$s\' endpoint view is invalid.', $view_id ) ); + } + + if ( ! empty( $attributes['group_callback'] ) ) { + $view_atts['group_callback'] = $attributes['group_callback']; + } + + if ( ! empty( $attributes['handler'] ) ) { + $view_atts['handler'] = $attributes['handler']; + } + + if ( ! empty( $attributes['fields']['display_callback'] ) ) { + $view_atts['fields']['display_callback'] = $attributes['fields']['display_callback']; + } + + try { + $this->validate_attributes( $view_atts, $view_id ); + } catch ( \EDD_Exception $exception ) { + $error = true; + + throw $exception; + } + + if ( true === $error ) { + return false; + + } else { + return parent::add_item( $view_id, $view_atts ); + } + } + + /** + * Retrieves registered endpoint views. + * + * @since 3.0 + * + * @return array Endpoint view records. + */ + public function get_endpoint_views() { + return $this->get_items_sorted( 'ID' ); + } + + /** + * Prevents removing items from the registry. + * + * @since 3.0 + * + * @param string $item_id Item ID. + */ + public function remove_item( $item_id ) { + return; + } + + /** + * Prevents removing items from the registry. + * + * @since 3.0 + * + * @param mixed $index Item index to check. + */ + public function offsetUnset( $index ) { + return; + } + + /** + * Retrieves the core-defined views and their (mostly) immutable defaults. + * + * @since 3.0 + * + * @param string $view_id View ID. + * @return array List of attributes for the given view ID if it exists, otherwise an empty array. + */ + public function get_core_view( $view_id ) { + $views = $this->get_core_views(); + + $attributes = array(); + + if ( array_key_exists( $view_id, $views ) ) { + $attributes = $views[ $view_id ]; + } + + return $attributes; + } + + /** + * Retrieves the core-defined views and their (mostly) immutable defaults. + * + * @since 3.0 + * + * @return array List of supported endpoint types and their attributes. + */ + public function get_core_views() { + return array( + 'tile' => array( + 'group' => 'tiles', + 'group_callback' => 'EDD\Reports\default_display_tiles_group', + 'handler' => 'EDD\Reports\Data\Tile_Endpoint', + 'fields' => array( + 'data_callback' => '::get_data', + 'display_callback' => 'EDD\Reports\default_display_tile', + 'display_args' => array( + 'type' => '', + 'context' => 'primary', + 'comparison_label' => __( 'All time', 'easy-digital-downloads' ), + ), + ), + ), + 'chart' => array( + 'group' => 'charts', + 'group_callback' => 'EDD\Reports\default_display_charts_group', + 'handler' => 'EDD\Reports\Data\Chart_Endpoint', + 'fields' => array( + 'type' => 'line', + 'options' => array(), + 'data_callback' => '::get_data', + 'display_callback' => '::display', + 'display_args' => array( + 'colors' => 'core', + ), + ), + ), + 'table' => array( + 'group' => 'tables', + 'group_callback' => 'EDD\Reports\default_display_tables_group', + 'handler' => 'EDD\Reports\Data\Table_Endpoint', + 'fields' => array( + 'data_callback' => '::prepare_items', + 'display_callback' => '::display', + 'display_args' => array( + 'class_name' => '', + 'class_file' => '', + ), + ), + ), + ); + } +} diff --git a/includes/reports/data/class-endpoint.php b/includes/reports/data/class-endpoint.php new file mode 100644 index 00000000000..dc3eaff40e0 --- /dev/null +++ b/includes/reports/data/class-endpoint.php @@ -0,0 +1,460 @@ +check_view(); + + if ( ! empty( $args['report'] ) ) { + $this->set_report_id( $args['report'] ); + } + + $this->set_display_props( $args ); + } + + /** + * Displays the endpoint based on the view (type). + * + * @since 3.0 + * + * @return void + */ + public function display() { + $callback = $this->get_display_callback(); + + if ( is_callable( $callback ) ) { + call_user_func_array( $callback, array( + $this, // Endpoint + $this->get_data(), // Data + $this->get_display_args(), // Args + ) ); + } + } + + /** + * Retrieves the data for the endpoint view (type). + * + * @since 3.0 + * + * @return mixed Endpoint data. + */ + public function get_data() { + $data_callback = $this->get_data_callback(); + + if ( is_callable( $data_callback ) ) { + $data = call_user_func( $data_callback ); + } else { + $data = ''; + } + + /** + * Filters data for the current endpoint. + * + * @since 3.0 + * + * @param mixed|string $data Endpoint data. + * @param Endpoint $this Endpoint object. + */ + return apply_filters( 'edd_reports_endpoint_data', $data, $this ); + } + + /** + * Retrieves the endpoint view (type). + * + * @since 3.0 + * + * @return string Endpoint view. + */ + public function get_view() { + return $this->view; + } + + /** + * Checks the endpoint view (type) against the list of available views.. + * + * @since 3.0 + * + * @param string $view_type Endpoint type. + */ + protected function check_view() { + $views = Reports\get_endpoint_views(); + + if ( ! array_key_exists( $this->get_view(), $views ) ) { + $this->errors->add( + 'invalid_view', + sprintf( 'The \'%1$s\' view is invalid.', $this->get_view() ), + $this + ); + } + } + + /** + * Retrieves the ID of the report currently associated with the endpoint. + * + * @since 3.0 + * + * @return string|null Report ID if set, otherwise null. + */ + public function get_report_id() { + return $this->report_id; + } + + /** + * Sets the ID for the report currently associated with the endpoint at the point of render. + * + * @since 3.0 + * + * @param string $report Report ID. + */ + protected function set_report_id( $report ) { + $this->report_id = $report; + } + + /** + * Sets display-related properties for the Endpoint. + * + * @since 3.0 + * + * @param array $endpoint Endpoint record from the registry. + */ + protected function set_display_props( $endpoint ) { + + $view_type = $this->get_view(); + + if ( ! empty( $endpoint['views'][ $view_type ] ) ) { + + $view_atts = $endpoint['views'][ $view_type ]; + + // display_args is optional. + if ( ! empty( $view_atts['display_args'] ) ) { + $this->set_display_args( $view_atts['display_args'] ); + } + + // display_callback + if ( ! empty( $view_atts['display_callback'] ) ) { + $this->set_display_callback( $view_atts['display_callback'] ); + } else { + $this->flag_missing_view_arg( 'display_callback' ); + } + + // data_callback + if ( ! empty( $view_atts['data_callback'] ) ) { + $this->set_data_callback( $view_atts['data_callback'] ); + } else { + $this->flag_missing_view_arg( 'data_callback' ); + } + + } else { + + $message = sprintf( 'The \'%1$s\' view type is not defined for the \'%2$s\' endpoint.', + $view_type, + $this->get_id() + ); + + $this->errors->add( 'view_not_defined', $message, array( + 'view_type' => $view_type, + 'endpoint_id' => $this->get_id(), + ) ); + + } + } + + /** + * Retrieves the value of a given display argument if set. + * + * @since 3.0 + * + * @param string $key Display argument key. + * @param string $default Optional. Default value to return in the event the argument isn't set. + * Default empty string. + * @return mixed|string Value of the display argument if set, otherwise an empty string. + */ + public function get_display_arg( $key, $default = '' ) { + $display_args = $this->get_display_args(); + + if ( isset( $display_args[ $key ] ) ) { + $value = $display_args[ $key ]; + } else { + $value = $default; + } + + return $value; + } + + /** + * Retrieves the display arguments for the view (type). + * + * @since 3.0 + * + * @return array Display arguments. + */ + public function get_display_args() { + /** + * Filters the display arguments for the current endpoint. + * + * @since 3.0 + * + * @param array $display_args Display arguments. + * @param Endpoint $this Endpoint object. + */ + return apply_filters( 'edd_reports_endpoint_display_args', $this->display_args, $this ); + } + + /** + * Validates and sets the display_args prop. + * + * @since 3.0 + * + * @param array|mixed $display_args Display arguments. + * @return void + */ + protected function set_display_args( $display_args ) { + if ( is_array( $display_args ) ) { + + $this->display_args = $display_args; + + } else { + + $this->flag_invalid_view_arg_type( 'display_args', 'array' ); + + } + } + + /** + * Retrieves the display callback for the endpoint view (type). + * + * @since 3.0 + * + * @return callable Display callback. + */ + public function get_display_callback() { + /** + * Filters the display callback for the current endpoint. + * + * @since 3.0 + * + * @param callable $display_callback Display callback. + * @param Endpoint $this Endpoint object. + */ + return apply_filters( 'edd_reports_endpoint_display_callback', $this->display_callback, $this ); + } + + /** + * Validates and sets the display_args prop. + * + * @since 3.0 + * + * @param callable|mixed $display_callback Display callback. + * @return void + */ + private function set_display_callback( $display_callback ) { + if ( is_callable( $display_callback ) ) { + + $this->display_callback = $display_callback; + + } elseif ( is_string( $display_callback ) && '::' === substr( $display_callback, 0, 2 ) ) { + + $method = str_replace( '::', '', $display_callback ); + + $display_callback = array( $this, $display_callback ); + + $this->set_display_callback( $display_callback ); + + } else { + + $this->flag_invalid_view_arg_type( 'display_callback', 'callable' ); + + } + } + + /** + * Retrieves the data callback for the endpoint view (type). + * + * @since 3.0 + * + * @return callable Data callback. + */ + public function get_data_callback() { + /** + * Filters the data callback for the current endpoint. + * + * @since 3.0 + * + * @param callable $data_callback Data callback. + * @param Endpoint $this Endpoint object. + */ + return apply_filters( 'edd_reports_endpoint_data_callback', $this->data_callback, $this ); + } + + /** + * Validates and sets the display_args prop. + * + * @since 3.0 + * + * @param callable|mixed $data_callback Data callback. + * @return void + */ + private function set_data_callback( $data_callback ) { + if ( is_callable( $data_callback ) ) { + + $this->data_callback = $data_callback; + + } elseif ( is_string( $data_callback ) && '::' === substr( $data_callback, 0, 2 ) ) { + + $method = str_replace( '::', '', $data_callback ); + + $data_callback = array( $this, $data_callback ); + + $this->set_data_callback( $data_callback ); + + } else { + + $this->flag_invalid_view_arg_type( 'data_callback', 'callable' ); + + } + } + + /** + * Flags an error for an invalid view argument type. + * + * @since 3.0 + * + * @param string $argument Argument name. + * @return void + */ + protected function flag_invalid_view_arg_type( $argument, $expected_type ) { + $message = sprintf( 'The \'%1$s\' argument must be of type %2$s for the \'%3$s\' endpoint \'%4$s\' view.', + $argument, + $expected_type, + $this->get_view(), + $this->get_id() + ); + + $this->errors->add( 'invalid_view_arg_type', $message, array( + 'view_type' => $this->get_view(), + 'endpoint_id' => $this->get_id(), + ) ); + } + + /** + * Flags an error for a missing required view argument. + * + * @since 3.0 + * + * @param string $argument Argument name. + * @return void + */ + protected function flag_missing_view_arg( $argument ) { + $message = sprintf( 'The \'%1$s\' argument must be set for the \'%2$s\' endpoint \'%3$s\' view.', + $argument, + $this->get_id(), + $this->get_view() + ); + + $this->errors->add( "missing_{$argument}", $message, array( + 'view_type' => $this->get_view(), + 'endpoint_id' => $this->get_id(), + ) ); + } + + /** + * Converts callback attributes signified as methods (prefixed with '::') + * to methods under the given object. + * + * This conversion can only really happen once the Endpoint is generated + * because the object context doesn't yet exist during registration. + * + * @since 3.0 + * + * @param array $atts View attributes for an endpoint. + * @param object $object Optional. Object under which the method should be assigned. + * Default is the current Endpoint object. + * @return array (Maybe) adjusted list of view attributes. + */ + protected function maybe_convert_callbacks_to_methods( $atts, $object = null ) { + $callbacks = array( 'display_callback', 'data_callback' ); + + if ( null === $object ) { + $object = $this; + } + + foreach ( $callbacks as $callback ) { + if ( ! empty( $atts[ $callback ] ) + && ( is_string( $atts[ $callback ] ) && '::' === substr( $atts[ $callback ], 0, 2 ) ) + ) { + $method = str_replace( '::', '', $atts[ $callback ] ); + + $atts[ $callback ] = array( $object, $method ); + } + } + + return $atts; + } + +} diff --git a/includes/reports/data/class-report-registry.php b/includes/reports/data/class-report-registry.php new file mode 100644 index 00000000000..5b63005f47e --- /dev/null +++ b/includes/reports/data/class-report-registry.php @@ -0,0 +1,292 @@ + '', + 'priority' => 10, + 'group' => 'core', + 'capability' => 'view_shop_reports', + 'filters' => array( + 'dates', + 'taxes', + ) + ); + + $attributes['id'] = $report_id; + $attributes = array_merge( $defaults, $attributes ); + + try { + // Filters can be empty. + $this->validate_attributes( $attributes, $report_id, array( 'filters' ) ); + } catch ( \EDD_Exception $exception ) { + $error = true; + + throw $exception; + } + + if ( isset( $attributes['endpoints'] ) && is_array( $attributes['endpoints'] ) ) { + foreach ( $attributes['endpoints'] as $view_group => $endpoints ) { + foreach ( $endpoints as $index => $endpoint ) { + if ( ! is_string( $endpoint ) && ! ( $endpoint instanceof \EDD\Reports\Data\Endpoint ) ) { + unset( $attributes['endpoints'][ $view_group ][ $index ] ); + + throw new Utils\Exception( sprintf( 'The \'%1$s\' report contains one or more invalidly defined endpoints.', $report_id ) ); + } + } + } + } + + if ( isset( $attributes['filters'] ) && is_array( $attributes['filters'] ) ) { + foreach ( $attributes['filters'] as $index => $filter ) { + if ( ! Reports\validate_filter( $filter ) ) { + $message = sprintf( 'The \'%1$s\' report contains one or more invalid filters.', $report_id ); + + unset( $attributes['filters'][ $index ] ); + + throw new Utils\Exception( $message ); + } + } + } + + if ( true === $error ) { + return false; + + } else { + return parent::add_item( $report_id, $attributes ); + } + } + + /** + * Retrieves registered reports. + * + * @since 3.0 + * + * @param string $sort Optional. How to sort the list of registered reports before retrieval. + * Accepts 'priority' or 'ID' (alphabetized by item ID), or empty (none). + * Default empty. + * @param string $group Optional. The reports group to retrieve reports for. Default 'core'. + * @return + */ + public function get_reports( $sort = '', $group = 'core' ) { + $reports = $this->get_items_sorted( $sort ); + + foreach ( $reports as $report_id => $atts ) { + if ( $group !== $atts['group'] ) { + unset( $reports[ $report_id ] ); + } + } + + return $reports; + } + + /** + * Registers a new data endpoint to the master endpoints registry. + * + * @since 3.0 + * + * @throws \EDD_Exception if the `$label` or `$views` attributes are empty. + * @throws \EDD_Exception if any of the required `$views` sub-attributes are empty. + * + * @see \EDD\Reports\Data\Endpoint_Registry::register_endpoint() + * + * @param string $endpoint_id Reports data endpoint ID. + * @param array $attributes Attributes of the endpoint. See Endpoint_Registry::register_endpoint() + * for more information on expected arguments. + * @return bool True if the endpoint was successfully registered, otherwise false. + */ + public function register_endpoint( $endpoint_id, $attributes ) { + /** @var \EDD\Reports\Data\Endpoint_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports:endpoints' ); + + if ( is_wp_error( $registry ) ) { + return false; + } + + return $registry->register_endpoint( $endpoint_id, $attributes ); + } + + /** + * Unregisters a data endpoint from the master endpoints registry. + * + * @since 3.0 + * + * @see \EDD\Reports\Data\Endpoint_Registry::unregister_endpoint() + * + * @param string $endpoint_id Endpoint ID. + */ + public function unregister_endpoint( $endpoint_id ) { + /** @var \EDD\Reports\Data\Endpoint_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports:endpoints' ); + + if ( ! is_wp_error( $registry ) ) { + $registry->unregister_endpoint( $endpoint_id ); + } + } + + /** + * Registers an endpoint view to the master endpoint views registry. + * + * @since 3.0 + * + * @throws \EDD_Exception if all expected attributes are not set. + * + * @see \EDD\Reports\Data\Endpoint_View_Registry::register_endpoint_view() + * + * @param string $view_id View ID. Currently only core endpoint views can be added. + * @param array $attributes Attributes of the endpoint view. See Endpoint_View_Registry::register_endpoint_view() + * for more information on expected/allowed arguments. + * @return bool True if the endpoint view was successfully registered, otherwise false. + */ + public function register_endpoint_view( $view_id, $attributes ) { + /** @var \EDD\Reports\Data\Endpoint_View_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports:endpoints:views' ); + + if ( is_wp_error( $registry ) ) { + return false; + } + + return $registry->register_endpoint_view( $view_id, $attributes ); + } + + /** + * Builds and retrieves a Report object. + * + * @since 3.0 + * + * @param string|Report $report Report ID or object. + * @param bool $build_endpoints Optional. Whether to build the endpoints (includes + * registering any endpoint dependencies, such as + * registering meta boxes). Default true. + * @return Report|\WP_Error Report object on success, otherwise a WP_Error object. + */ + public function build_report( $report, $build_endpoints = true ) { + + // If a report object was passed, just return it. + if ( $report instanceof Report ) { + return $report; + } + + try { + $_report = $this->get_report( $report ); + + } catch( \EDD_Exception $exception ) { + + edd_debug_log_exception( $exception ); + + return new \WP_Error( 'invalid_report', $exception->getMessage(), $report ); + } + + if ( ! empty( $_report ) ) { + $_report = new Report( $_report ); + + if ( true === $build_endpoints ) { + $_report->build_endpoints(); + } + } + + return $_report; + } +} diff --git a/includes/reports/data/class-report.php b/includes/reports/data/class-report.php new file mode 100644 index 00000000000..f28a51c2136 --- /dev/null +++ b/includes/reports/data/class-report.php @@ -0,0 +1,447 @@ +raw_endpoints = $args['endpoints']; + } else { + $this->errors->add( 'missing_endpoints', 'No endpoints are defined for the report.', $args ); + } + + if ( ! empty( $args['capability'] ) ) { + $this->set_capability( $args['capability'] ); + } else { + $this->errors->add( 'missing_capability', 'No capability is defined for the report.', $args ); + } + + if ( ! empty( $args['display_callback'] ) ) { + $this->set_display_callback( $args['display_callback'] ); + } + + if ( ! empty( $args['filters'] ) ) { + $this->set_filters( $args['filters'] ); + } + + if ( ! empty( $args['group'] ) ) { + $this->set_group( $args['group'] ); + } + } + + /** + * Triggers building the report's endpoints if defined and the current user + * has the ability to view them. + * + * This is abstracted away from instantiation to allow for building Report objects + * without always registering meta boxes and other endpoint dependencies for display. + * + * @since 3.0 + */ + public function build_endpoints() { + if ( ! empty( $this->raw_endpoints ) && current_user_can( $this->get_capability() ) ) { + try { + $this->parse_endpoints( $this->raw_endpoints ); + + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + } + + } else { + $this->errors->add( 'missing_endpoints', 'No endpoints are defined for the report.' ); + } + } + + /** + * Parses Endpoint objects for each endpoint in the report. + * + * @since 3.0 + * + * @throws \EDD_Exception + * + * @param array $endpoints Endpoints, keyed by view type. + */ + public function parse_endpoints( $report_endpoints ) { + /** @var \EDD\Reports\Data\Endpoint_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports:endpoints' ); + + if ( is_wp_error( $registry ) ) { + throw new Utils\Exception( $registry->get_error_message() ); + } + + $view_groups = $this->parse_view_groups(); + + // Loop through all passed endpoints using view groups. + foreach ( $report_endpoints as $group => $endpoints ) { + + // Skip any invalid views based on view group. + if ( ! array_key_exists( $group, $view_groups ) ) { + throw new Utils\Exception( sprintf( + 'The \'%1$s\' view group does not correspond to a known endpoint view type.', + $group + ) ); + } + + // Loop through all endpoints for each view group and build endpoint objects. + foreach ( $endpoints as $endpoint ) { + + $endpoint = $registry->build_endpoint( $endpoint, $view_groups[ $group ], $this->get_id() ); + + $this->validate_endpoint( $group, $endpoint ); + } + } + } + + /** + * Parses the views whitelist to retrieve corresponding view groups. + * + * @since 3.0 + * + * @return array List of view group and view slug pairs. + */ + public function parse_view_groups() { + $views = Reports\get_endpoint_views(); + + $view_groups = array(); + + foreach ( $views as $view_type => $atts ) { + if ( ! empty( $atts['group'] ) ) { + $view_group = $atts['group']; + + $view_groups[ $view_group ] = $view_type; + } + } + + return $view_groups; + } + + /** + * Validates an endpoint for rendering. + * + * @since 3.0 + * + * @see \EDD\Reports\Data\Report::$valid_endpoints + * + * @param string $view_group View group corresponding to the endpoint view. + * @param Data\Endpoint|\WP_Error $endpoint Endpoint object. + */ + public function validate_endpoint( $view_group, $endpoint ) { + if ( is_wp_error( $endpoint ) ) { + $this->errors->add( + $endpoint->get_error_code(), + $endpoint->get_error_message(), + $endpoint->get_error_data() + ); + + } elseif ( ! is_wp_error( $endpoint ) && $endpoint->has_errors() ) { + $message = sprintf( 'The \'%1$s\' endpoint is invalid.', $endpoint->get_id() ); + + $this->errors->add( 'invalid_endpoint', $message, $endpoint->get_errors() ); + + // Valid. + } else { + $this->endpoints[ $view_group ][ $endpoint->get_id() ] = $endpoint; + } + } + + /** + * Retrieves a list of validated endpoints for the current report. + * + * @since 3.0 + * + * @param string $view_group Optional. View group for the type of endpoints to retrieve. + * Default empty (all valid endpoints). + * @return Endpoint[] List of validated endpoints by view view group. + */ + public function get_endpoints( $view_group = '' ) { + if ( ! empty( $view_group ) && ! empty( $this->endpoints[ $view_group ] ) ) { + return $this->endpoints[ $view_group ]; + } else { + return $this->endpoints; + } + } + + /** + * Determines whether the report has any valid endpoints. + * + * @since 3.0 + * + * @param string $view_group Optional. View group for the type of endpoints + * to check the existence of. Default empty. + * @return bool True if there is at least one valid endpoint, otherwise false. + */ + public function has_endpoints( $view_group = '' ) { + if ( ! empty( $view_group ) ) { + $has_endpoints = ! empty( $this->endpoints[ $view_group ] ); + } else { + $has_endpoints = ! empty( $this->endpoints ); + } + + return $has_endpoints; + } + + /** + * Retrieves a given endpoint by view group. + * + * @since 3.0 + * + * @param string $endpoint_id Endpoint ID. + * @param string $view_group Endpoint view group. + * @return Endpoint|\WP_Error Endpoint object if it exists, otherwise a WP_Error object. + */ + public function get_endpoint( $endpoint_id, $view_group ) { + $endpoints = $this->get_endpoints( $view_group ); + + if ( isset( $endpoints[ $endpoint_id ] ) ) { + $endpoint = $endpoints[ $endpoint_id ]; + + } else { + $message = sprintf( 'The \'%1$s\' endpoint does not exist for the \'%2$s\' view group in the \'%3$s\' report.', + $endpoint_id, + $view_group, + $this->get_id() + ); + + $endpoint = new \WP_Error( 'invalid_report_endpoint', $message ); + } + + return $endpoint; + } + + /** + * Retrieves the capability needed to view the rendered report. + * + * @since 3.0 + * + * @return string Report capability. + */ + public function get_capability() { + return $this->capability; + } + + /** + * Sets the capability needed for the current user to view the report. + * + * @since 3.0 + * + * @param string $capability Capability. + */ + private function set_capability( $capability ) { + $this->capability = sanitize_key( $capability ); + } + + /** + * Displays the endpoint based on the view (type). + * + * @since 3.0 + * + * @return void + */ + public function display() { + $callback = $this->get_display_callback(); + + if ( is_callable( $callback ) ) { + call_user_func( $callback, $this ); + } + } + + /** + * Retrieves the current report's display callback. + * + * @since 3.0 + * + * @return callable Display callback. + */ + public function get_display_callback() { + return $this->display_callback; + } + + /** + * Sets the display callback used to render the report. + * + * @since 3.0 + * + * @param callable $callback Display callback. + */ + private function set_display_callback( $callback ) { + if ( is_callable( $callback ) ) { + $this->display_callback = $callback; + + } else { + $this->flag_invalid_report_arg_type( 'display_callback', 'callable' ); + } + } + + /** + * Retrieves the list of filters registered for use with this report. + * + * @since 3.0 + * + * @return array List of support filters. + */ + public function get_filters() { + return $this->filters; + } + + /** + * Sets the endpoint filters supported by the current report's endpoints. + * + * @since 3.0 + * + * @param array $filters Filters to set for this report. + */ + private function set_filters( $filters ) { + + foreach ( $filters as $filter ) { + if ( Reports\validate_filter( $filter ) ) { + $this->filters[] = $filter; + + } else { + $message = sprintf( 'The \'%1$s\' filter for the \'%2$s\' report is invalid.', + $filter, + $this->get_id() + ); + + $this->errors->add( 'invalid_report_filter', $message, $this ); + } + } + + $this->filters = array_unique( $this->filters ); + } + + /** + * Retrieves the display group for the current report. + * + * @since 3.0 + * + * @return string Display group. Default 'reports'. + */ + public function get_group() { + return $this->group; + } + + /** + * Sets the display group for the current report. + * + * @since 3.0 + * + * @param string $group Report display group. + */ + private function set_group( $group ) { + $this->group = sanitize_key( $group ); + } + + /** + * Displays an entire group of an endpoints view. + * + * @since 3.0 + * + * @param string $view_group Endpoints view group. + * @return void + */ + public function display_endpoint_group( $view_group ) { + $groups = $this->parse_view_groups(); + + if ( array_key_exists( $view_group, $groups ) ) { + $callback = Reports\get_endpoint_group_callback( $groups[ $view_group ] ); + + if ( is_callable( $callback ) ) { + call_user_func( $callback, $this ); + } + } + } + + /** + * Flags an error for an invalid report argument type. + * + * @since 3.0 + * + * @param string $argument Argument name. + */ + protected function flag_invalid_report_arg_type( $argument, $expected_type ) { + $message = sprintf( 'The \'%1$s\' argument must be of type %2$s for the \'%3$s\' report.', + $argument, + $expected_type, + $this->get_id() + ); + + $this->errors->add( 'invalid_report_arg_type', $message, array( + 'report_id' => $this->get_id(), + ) ); + } +} diff --git a/includes/reports/data/class-table-endpoint.php b/includes/reports/data/class-table-endpoint.php new file mode 100644 index 00000000000..680365a7574 --- /dev/null +++ b/includes/reports/data/class-table-endpoint.php @@ -0,0 +1,223 @@ +errors = new \WP_Error(); + + // ID and Label. + $this->set_props( $args ); + + // List table set up and dumping display args. + $this->setup_list_table( $args ); + + // Parse display attributes from defaults. + $args = $this->parse_display_props( $args ); + + parent::__construct( $args ); + } + + /** + * Sets display-related properties for the Endpoint. + * + * @since 3.0 + * + * @param array $endpoint Endpoint record from the registry. + */ + private function parse_display_props( $endpoint ) { + + $view_type = $this->get_view(); + + if ( ! empty( $endpoint['views'][ $view_type ] ) ) { + + $view_atts = $endpoint['views'][ $view_type ]; + + $list_table = $this->get_list_table(); + + if ( null === $list_table ) { + return $endpoint; + } + + $endpoint['views'][ $view_type ] = $this->maybe_convert_callbacks_to_methods( $view_atts, $list_table ); + } + + return $endpoint; + } + + /** + * Sets attributes related to the list table. + * + * @since 3.0 + * + * @param array $endpoint Table endpoint arguments. + */ + private function setup_list_table( $endpoint ) { + + if ( ! empty( $endpoint['views'][ $this->view ]['display_args'] ) ) { + + $display_args = $endpoint['views'][ $this->view ]['display_args']; + + if ( ! empty( $display_args['class_name'] ) ) { + + if ( ! empty( $display_args['class_file'] ) ) { + + $this->set_class_file( $display_args['class_file'] ); + + $this->set_list_table( $display_args['class_name'] ); + + } else { + + $this->errors->add( + 'missing_table_class_file', + sprintf( 'The list table class file for the \'%1$s\' endpoint is missing.', $this->get_id() ) + ); + + } + + } else { + + $this->errors->add( + 'missing_table_class_name', + sprintf( 'The list table class name for the \'%1$s\' endpoint is missing.', + $this->get_id() + ) + ); + + } + + // Dump the display args as they're no longer needed. + $endpoint['views'][ $this->view ]['display_args'] = array(); + + } + + } + + /** + * Retrieves the list table class file. + * + * @since 3.0 + * + * @return string|null Class file if set, otherwise null. + */ + public function get_class_file() { + return $this->class_file; + } + + /** + * Sets the list table class file. + * + * @since 3.0 + * + * @param string $file Class file. + */ + private function set_class_file( $file ) { + if ( false === strpos( $file, '..' ) && false === strpos( $file, './' ) ) { + $this->class_file = $file; + } + } + + /** + * Retrieves the list table instance. + * + * @since 3.0 + * + * @return WP_List_Table|null List table instance if set, otherwise null. + */ + public function get_list_table() { + return $this->list_table; + } + + /** + * Sets the list table instance. + * + * @since 3.0 + * + * @see get_class_file() + * + * @param string $class List table class name. + */ + private function set_list_table( $class ) { + if ( ! class_exists( $class ) ) { + $path_to_file = $this->get_class_file(); + + if ( file_exists( $path_to_file ) ) { + require_once $path_to_file; + } + } + $this->list_table = new $class; + } + + /** + * Display logic for the current table endpoint. + * + * @since 3.0 + */ + public function display() { + $callback = $this->get_display_callback(); + + if ( is_callable( $callback ) ) { + $table = $this->get_list_table(); + + if ( null !== $table ) { + // Prep the table data for display (prepare_items). + $this->get_data(); + + call_user_func_array( $callback, array( + $this, // Endpoint + $table, // Table + $this->get_display_args(), // Args + ) ); + } + } + } + +} diff --git a/includes/reports/data/class-tile-endpoint.php b/includes/reports/data/class-tile-endpoint.php new file mode 100644 index 00000000000..3a09ab9ac3d --- /dev/null +++ b/includes/reports/data/class-tile-endpoint.php @@ -0,0 +1,45 @@ +get_id() ) . '" class="' . esc_attr( implode( ' ', $classnames ) ) . '">'; + parent::display(); + echo ''; + } + +} diff --git a/includes/reports/data/customers/class-most-valuable-customers-list-table.php b/includes/reports/data/customers/class-most-valuable-customers-list-table.php new file mode 100644 index 00000000000..739ffd5522b --- /dev/null +++ b/includes/reports/data/customers/class-most-valuable-customers-list-table.php @@ -0,0 +1,154 @@ +prepare( " AND currency = %s ", $currency ); + } + + $start_date = sanitize_text_field( date( 'Y-m-d 00:00:00', strtotime( $date_range['start'] ) ) ); + $end_date = sanitize_text_field( date( 'Y-m-d 23:59:59', strtotime( $date_range['end'] ) ) ); + + $sql = "SELECT customer_id, COUNT(id) AS order_count, SUM({$column}) AS total_spent + FROM {$wpdb->edd_orders} + WHERE status IN (%s, %s) AND date_created >= %s AND date_created <= %s AND type = 'sale' + {$currency_clause} + GROUP BY customer_id + ORDER BY total_spent DESC + LIMIT 5"; + + $results = $wpdb->get_results( $wpdb->prepare( $sql, sanitize_text_field( 'complete' ), sanitize_text_field( 'revoked' ), $start_date, $end_date ) ); + + foreach ( $results as $result ) { + $customer = edd_get_customer( (int) $result->customer_id ); + + // Skip if customer record not found. + if ( ! $customer ) { + continue; + } + + $user_id = ! empty( $customer->user_id ) + ? intval( $customer->user_id ) + : 0; + + $data[] = array( + 'id' => $customer->id, + 'user_id' => $user_id, + 'name' => $customer->name, + 'email' => $customer->email, + 'order_count' => absint( $result->order_count ), + 'spent' => $result->total_spent, + 'date_created' => $customer->date_created, + ); + } + + return $data; + } + + /** + * Retrieve the table columns. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns. + */ + public function get_columns() { + $columns = parent::get_columns(); + + // Remove the checkbox if it exists. + if ( isset( $columns['cb'] ) ) { + unset( $columns['cb'] ); + } + + return $columns; + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} diff --git a/includes/reports/data/customers/class-top-five-customers-list-table.php b/includes/reports/data/customers/class-top-five-customers-list-table.php new file mode 100644 index 00000000000..d50c9475a40 --- /dev/null +++ b/includes/reports/data/customers/class-top-five-customers-list-table.php @@ -0,0 +1,206 @@ + 5, + 'order' => 'DESC', + 'orderby' => 'purchase_value', + ); + + $customers = edd_get_customers( $args ); + + foreach ( $customers as $customer ) { + /** @var \EDD_Customer $customer */ + + $user_id = ! empty( $customer->user_id ) + ? intval( $customer->user_id ) + : 0; + + $data[] = array( + 'id' => $customer->id, + 'user_id' => $user_id, + 'name' => $customer->name, + 'email' => $customer->email, + 'order_count' => $customer->purchase_count, + 'spent' => $customer->purchase_value, + 'date_created' => $customer->date_created, + ); + } + } else { + global $wpdb; + + // @todo DRY with Most_Valuable_Customers_List_Table + + $column = Reports\get_taxes_excluded_filter() ? 'total - tax' : 'total'; + $currency = Reports\get_filter_value( 'currencies' ); + $currency_clause = ''; + + if ( empty( $currency ) || 'convert' === $currency ) { + $column = sprintf( '%s / rate', $column ); + } else { + $currency_clause = $wpdb->prepare( 'AND status = %s', strtoupper( $currency ) ); + } + + $sql = "SELECT customer_id, COUNT(id) AS order_count, SUM({$column}) AS total_spent + FROM {$wpdb->edd_orders} + WHERE status IN (%s, %s) AND type = 'sale' + {$currency_clause} + GROUP BY customer_id + ORDER BY total_spent DESC + LIMIT 5"; + + $results = $wpdb->get_results( $wpdb->prepare( $sql, sanitize_text_field( 'complete' ), sanitize_text_field( 'revoked' ) ) ); + + foreach ( $results as $result ) { + $customer = edd_get_customer( (int) $result->customer_id ); + + // Skip if customer record not found. + if ( ! $customer ) { + continue; + } + + $user_id = ! empty( $customer->user_id ) + ? intval( $customer->user_id ) + : 0; + + $data[] = array( + 'id' => $customer->id, + 'user_id' => $user_id, + 'name' => $customer->name, + 'email' => $customer->email, + 'order_count' => absint( $result->order_count ), + 'spent' => $result->total_spent, + 'date_created' => $customer->date_created, + ); + } + } + + return $data; + } + + /** + * Retrieve the table columns. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns. + */ + public function get_columns() { + $columns = parent::get_columns(); + + // Remove the checkbox if it exists. + if ( isset( $columns['cb'] ) ) { + unset( $columns['cb'] ); + } + + return $columns; + } + + /** + * Overrides the `spent` column value to possibly display in the filtered currency. + * + * @since 3.0 + * + * @param array $item + * @param string $column_name + * + * @return string + */ + public function column_default( $item, $column_name ) { + if ( 'spent' !== $column_name ) { + return parent::column_default( $item, $column_name ); + } + + $currency = ''; + $selected_currency = Reports\get_filter_value( 'currencies' ); + if ( ! empty( $selected_currency ) && 'convert' !== $selected_currency ) { + $currency = $selected_currency; + } + + $value = edd_currency_filter( edd_format_amount( $item[ $column_name ] ), $currency ); + + return apply_filters( 'edd_customers_column_' . $column_name, $value, $item['id'] ); + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} diff --git a/includes/reports/data/discounts/class-top-five-discounts-list-table.php b/includes/reports/data/discounts/class-top-five-discounts-list-table.php new file mode 100644 index 00000000000..af460d487d5 --- /dev/null +++ b/includes/reports/data/discounts/class-top-five-discounts-list-table.php @@ -0,0 +1,237 @@ +get_most_popular_discounts( array( + 'number' => 5, + 'range' => $filter['range'], + ) ); + + $data = array(); + + foreach ( $d as $result ) { + if ( empty( $result->object ) ) { + continue; + } + + $c = new \stdClass(); + $c->id = $result->object->id; + $c->name = $result->object->name; + $c->status = $result->object->status; + $c->use_count = $result->count; + $c->code = $result->object->code; + $c->type = $result->object->type; + $c->amount = $result->object->amount; + + $data[] = $c; + } + + return $data; + } + + /** + * Retrieve the table columns. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'code' => __( 'Code', 'easy-digital-downloads' ), + 'use_count' => __( 'Uses', 'easy-digital-downloads' ), + 'amount' => __( 'Amount', 'easy-digital-downloads' ) + ); + } + + /** + * This function renders most of the columns in the list table. + * + * @since 3.0 + * + * @param \stdClass $discount Discount object. + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $discount, $column_name ) { + return property_exists( $discount, $column_name ) ? $discount->$column_name : ''; + } + + /** + * This function renders the amount column. + * + * @access public + * @since 3.0 + * + * @param \stdClass $discount Data for the discount code. + * @return string Formatted amount. + */ + public function column_amount( $discount ) { + return edd_format_discount_rate( $discount->type, $discount->amount ); + } + + /** + * Render the Name Column + * + * @since 3.0 + * + * @param \stdClass $discount Discount object. + * @return string Data shown in the Name column + */ + public function column_name( $discount ) { + $base = $this->get_base_url(); + $state = ''; + + // Bail if current user cannot manage discounts + if ( ! current_user_can( 'manage_shop_discounts' ) ) { + return; + } + + // State + if ( ( ! empty( $status ) && ( $status !== $discount->status ) ) || ( $discount->status !== 'active' ) ) { + $state = ' — ' . edd_get_discount_status_label( $discount->id ); + } + + // Wrap discount title in strong anchor + $discount_title = '' . stripslashes( $discount->name ) . '' . esc_html( $state ) . ''; + + // Return discount title & row actions + return $discount_title; + } + + /** + * Setup the final data for the table. + * + * @since 3.0 + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); + $sortable = $this->get_sortable_columns(); + + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } + + /** + * Get the base URL for the discount list table + * + * @since 3.0 + * + * @return string + */ + public function get_base_url() { + + // Remove some query arguments + $base = remove_query_arg( edd_admin_removable_query_args(), edd_get_admin_base_url() ); + + // Add base query args + return add_query_arg( array( + 'page' => 'edd-discounts', + ), $base ); + } + + /** + * Message to be displayed when there are no items + * + * @since 3.0 + */ + public function no_items() { + esc_html_e( 'No discounts found.', 'easy-digital-downloads' ); + } + + /** + * Gets the name of the primary column. + * + * @since 3.0 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} diff --git a/includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php b/includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php new file mode 100644 index 00000000000..02ab7d25998 --- /dev/null +++ b/includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php @@ -0,0 +1,307 @@ +prepare( "tt.taxonomy IN ({$placeholders})", $taxonomies ); + + $sql = "SELECT t.*, tt.*, tr.object_id + FROM {$wpdb->terms} AS t + INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id + INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id + WHERE {$taxonomy__in}"; + + $results = $wpdb->get_results( $sql ); + + // Build intermediate array to allow for better data processing. + $taxonomies = array(); + foreach ( $results as $r ) { + $taxonomies[ absint( $r->term_id ) ]['name'] = esc_html( $r->name ); + $taxonomies[ absint( $r->term_id ) ]['object_ids'][] = absint( $r->object_id ); + $taxonomies[ absint( $r->term_id ) ]['parent'] = absint( $r->parent ); + } + + // Setup an empty array for the final returned data. + $data = array(); + + // Store each download's stats during the loop to avoid double queries. + $download_stats = array(); + + foreach ( $taxonomies as $k => $t ) { + $c = new \stdClass(); + $c->id = $k; + $c->name = $taxonomies[ $k ]['name']; + + $earnings = 0.00; + $sales = 0; + + $average_earnings = 0.00; + $average_sales = 0; + + foreach ( $taxonomies[ $k ]['object_ids'] as $download_id ) { + if ( ! isset( $download_stats[ $download_id ] ) ) { + $stats = new Stats( + array( + 'product_id' => absint( $download_id ), + 'currency' => $currency, + 'range' => $dates['range'], + 'output' => 'typed', + ) + ); + + $download_stats[ $download_id ]['earnings'] = $stats->get_order_item_earnings( + array( + 'function' => 'SUM', + ) + ); + + $download_stats[ $download_id ]['sales'] = $stats->get_order_item_count( + array( + 'function' => 'COUNT', + ) + ); + + $download_stats[ $download_id ]['average_earnings'] = edd_get_average_monthly_download_earnings( $download_id ); + $download_stats[ $download_id ]['average_sales'] = edd_get_average_monthly_download_sales( $download_id ); + } + + $earnings += $download_stats[ $download_id ]['earnings']; + $sales += $download_stats[ $download_id ]['sales']; + + $average_earnings += $download_stats[ $download_id ]['average_earnings']; + $average_sales += $download_stats[ $download_id ]['average_sales']; + } + + $c->sales = $sales; + $c->earnings = $earnings; + $c->parent = 0 === $t['parent'] + ? null + : $t['parent']; + + $c->average_sales = $average_sales; + $c->average_earnings = $average_earnings; + + $data[] = $c; + } + + $sorted_data = array(); + + foreach ( $data as $d ) { + + // Get parent level elements. + if ( null === $d->parent ) { + $sorted_data[] = $d; + + $objects = array_values( wp_filter_object_list( $data, array( 'parent' => $d->id ) ) ); + + foreach ( $objects as $o ) { + $sorted_data[] = $o; + } + } + } + + // Sort by total earnings. + usort( $sorted_data, function( $a, $b ) { + return ( $a->earnings < $b->earnings ) ? -1 : 1; + } ); + + return $sorted_data; + } + + /** + * Retrieve the table columns. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'sales' => __( 'Total Sales', 'easy-digital-downloads' ), + 'earnings' => __( 'Total Earnings', 'easy-digital-downloads' ), + 'average_sales' => __( 'Monthly Sales Average', 'easy-digital-downloads' ), + 'average_earnings' => __( 'Monthly Earnings Average', 'easy-digital-downloads' ) + ); + } + + /** + * Render the Name Column. + * + * @since 3.0 + * + * @param \stdClass $taxonomy Taxonomy object. + * @return string Data shown in the Name column. + */ + public function column_name( $taxonomy ) { + return 0 < $taxonomy->parent + ? '— ' . $taxonomy->name + : $taxonomy->name; + } + + /** + * Render the Sales Column. + * + * @since 3.0 + * + * @param \stdClass $taxonomy Taxonomy object. + * @return string Data shown in the Sales column. + */ + public function column_sales( $taxonomy ) { + return $taxonomy->sales; + } + + /** + * Render the Earnings Column. + * + * @since 3.0 + * + * @param \stdClass $taxonomy Taxonomy object. + * @return string Data shown in the Earnings column. + */ + public function column_earnings( $taxonomy ) { + return edd_currency_filter( edd_format_amount( $taxonomy->earnings ) ); + } + + /** + * Render the Average Sales Column. + * + * @since 3.0 + * + * @param \stdClass $taxonomy Taxonomy object. + * @return int Data shown in the Average Sales column. + */ + public function column_average_sales( $taxonomy ) { + return (int) round( $taxonomy->average_sales ); + } + + /** + * Render the Average Earnings Column. + * + * @since 3.0 + * + * @param \stdClass $taxonomy Taxonomy object. + * @return string Data shown in the Average Earnings column. + */ + public function column_average_earnings( $taxonomy ) { + return edd_currency_filter( edd_format_amount( $taxonomy->average_earnings ) ); + } + + /** + * Setup the final data for the table. + * + * @since 3.0 + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); + $sortable = $this->get_sortable_columns(); + + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } + + /** + * Message to be displayed when there are no items + * + * @since 3.0 + */ + public function no_items() { + esc_html_e( 'No taxonomies found.', 'easy-digital-downloads' ); + } + + /** + * Gets the name of the primary column. + * + * @since 3.0 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} diff --git a/includes/reports/data/downloads/class-top-selling-downloads-list-table.php b/includes/reports/data/downloads/class-top-selling-downloads-list-table.php new file mode 100644 index 00000000000..1367a4c5a6b --- /dev/null +++ b/includes/reports/data/downloads/class-top-selling-downloads-list-table.php @@ -0,0 +1,213 @@ +get_most_valuable_order_items( array( + 'number' => 10, + 'range' => $filter['range'], + 'currency' => '', + ) ); + } + + /** + * Retrieve the table columns. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'price' => __( 'Price', 'easy-digital-downloads' ), + 'sales' => __( 'Sales', 'easy-digital-downloads' ), + 'earnings' => __( 'Net Earnings', 'easy-digital-downloads' ), + ); + } + + /** + * Render the Name Column. + * + * @since 3.0 + * + * @param \stdClass $download Download object. + * @return string Data shown in the Name column. + */ + public function column_name( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + // Check for variable pricing + $retval = ! is_null( $download->price_id ) && is_numeric( $download->price_id ) + ? edd_get_download_name( $download->object->ID, $download->price_id ) + : edd_get_download_name( $download->object->ID ); + + return $retval; + } + + /** + * Render the Price Column. + * + * @since 3.0 + * + * @param \stdClass $download Download object. + * @return string Data shown in the Price column. + */ + public function column_price( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + // Check for variable pricing + $retval = ! is_null( $download->price_id ) && is_numeric( $download->price_id ) + ? edd_price( $download->object->ID, false, $download->price_id ) + : edd_price( $download->object->ID, false ); + + return $retval; + } + + public function column_sales( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + return current_user_can( 'view_product_stats', $download->object->ID ) + ? $download->sales + : '—'; + } + + public function column_earnings( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + return current_user_can( 'view_product_stats', $download->object->ID ) + ? edd_currency_filter( edd_format_amount( $download->total ) ) + : '—'; + } + + /** + * Setup the final data for the table. + * + * @since 3.0 + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); + $sortable = $this->get_sortable_columns(); + + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } + + /** + * Get the base URL for the discount list table + * + * @since 3.0 + * + * @return string + */ + public function get_base_url() { + return remove_query_arg( edd_admin_removable_query_args(), edd_get_admin_base_url() ); + } + + /** + * Message to be displayed when there are no items + * + * @since 3.0 + */ + public function no_items() { + esc_html_e( 'No downloads found.', 'easy-digital-downloads' ); + } + + /** + * Gets the name of the primary column. + * + * @since 3.0 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} diff --git a/includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php b/includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php new file mode 100644 index 00000000000..0685b2e599c --- /dev/null +++ b/includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php @@ -0,0 +1,227 @@ +get_most_downloaded_products( array( + 'number' => 5, + 'range' => $filter['range'] + ) ); + } + + /** + * Retrieve the table columns. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'name' => __( 'Name', 'easy-digital-downloads' ), + 'download_count' => __( 'File Downloads', 'easy-digital-downloads' ), + 'price' => __( 'Price', 'easy-digital-downloads' ), + 'sales' => __( 'Sales', 'easy-digital-downloads' ), + 'earnings' => __( 'Earnings', 'easy-digital-downloads' ) + ); + } + + /** + * Render the Name Column. + * + * @since 3.0 + * + * @param \stdClass $download Download object. + * @return string Data shown in the Name column. + */ + public function column_name( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + // Download title + return $download->object->get_name(); + } + + /** + * Render the Download Count Column. + * + * @since 3.0 + * + * @param \stdClass $download Download object. + * @return string Data shown in the Download Count column. + */ + public function column_download_count( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + return $download->total; + } + + /** + * Render the Price Column. + * + * @since 3.0 + * + * @param \stdClass $download Download object. + * @return string Data shown in the Price column. + */ + public function column_price( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + if ( $download->object->has_variable_prices() ) { + return edd_price_range( $download->object->ID ); + } else { + return edd_price( $download->object->ID, false ); + } + } + + public function column_sales( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + return current_user_can( 'view_product_stats', $download->object->ID ) + ? edd_get_download_sales_stats( $download->object->ID ) + : '—'; + } + + public function column_earnings( $download ) { + if ( ! $download->object instanceof \EDD_Download ) { + return '—'; + } + + return current_user_can( 'view_product_stats', $download->object->ID ) + ? edd_currency_filter( edd_format_amount( edd_get_download_earnings_stats( $download->object->ID ) ) ) + : '—'; + } + + /** + * Setup the final data for the table. + * + * @since 3.0 + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); + $sortable = $this->get_sortable_columns(); + + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } + + /** + * Get the base URL for the discount list table + * + * @since 3.0 + * + * @return string + */ + public function get_base_url() { + return remove_query_arg( edd_admin_removable_query_args(), edd_get_admin_base_url() ); + } + + /** + * Message to be displayed when there are no items + * + * @since 3.0 + */ + public function no_items() { + esc_html_e( 'No downloads found.', 'easy-digital-downloads' ); + } + + /** + * Gets the name of the primary column. + * + * @since 3.0 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} \ No newline at end of file diff --git a/includes/reports/data/payment-gateways/class-gateway-stats-list-table.php b/includes/reports/data/payment-gateways/class-gateway-stats-list-table.php new file mode 100644 index 00000000000..b6432913e49 --- /dev/null +++ b/includes/reports/data/payment-gateways/class-gateway-stats-list-table.php @@ -0,0 +1,205 @@ + 'report-gateway', + 'plural' => 'report-gateways', + 'ajax' => false, + ) ); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'label'; + } + + /** + * Render each column. + * + * @since 1.5 + * + * @param array $item Contains all the data of the downloads + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + return $item[ $column_name ]; + } + + /** + * Column names. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'label' => __( 'Gateway', 'easy-digital-downloads' ), + 'complete_sales' => __( 'Complete Sales', 'easy-digital-downloads' ), + 'pending_sales' => __( 'Pending / Failed Sales', 'easy-digital-downloads' ), + 'refunded_sales' => __( 'Refunds Issued', 'easy-digital-downloads' ), + 'total_sales' => __( 'Total Sales', 'easy-digital-downloads' ), + ); + } + + /** + * Build all the reports data + * + * @since 1.5 + * @return array All the data for customer reports + */ + public function get_data() { + $filter = Reports\get_filter_value( 'dates' ); + $currency = Reports\get_filter_value( 'currencies' ); + + $reports_data = array(); + $gateways = edd_get_payment_gateways(); + + foreach ( $gateways as $gateway_id => $gateway ) { + $stats = new Stats(); + + $complete_count = $stats->get_gateway_sales( array( + 'range' => $filter['range'], + 'gateway' => $gateway_id, + 'status' => edd_get_gross_order_statuses(), + 'type' => array( 'sale' ), + 'currency' => $currency, + ) ); + + $pending_count = $stats->get_gateway_sales( array( + 'range' => $filter['range'], + 'gateway' => $gateway_id, + 'status' => edd_get_incomplete_order_statuses(), + 'type' => array( 'sale' ), + 'currency' => $currency, + ) ); + + $refunded_count = $stats->get_gateway_sales( array( + 'range' => $filter['range'], + 'gateway' => $gateway_id, + 'status' => array( 'complete' ), + 'type' => array( 'refund' ), + 'currency' => $currency, + ) ); + + $total_count = $stats->get_gateway_sales( array( + 'range' => $filter['range'], + 'gateway' => $gateway_id, + 'status' => 'any', + 'type' => array( 'sale' ), + 'currency' => $currency, + ) ); + + $reports_data[] = array( + 'ID' => $gateway_id, + 'label' => '' . esc_html( $gateway['admin_label'] ) . '', + 'complete_sales' => edd_format_amount( $complete_count, false ), + 'pending_sales' => edd_format_amount( $pending_count, false ), + 'refunded_sales' => edd_format_amount( $refunded_count, false ), + 'total_sales' => edd_format_amount( $total_count, false ), + ); + } + + return $reports_data; + } + + /** + * Setup the final data for the table + * + * @since 1.5 + * @uses EDD_Gateway_Reports_Table::get_columns() + * @uses EDD_Gateway_Reports_Table::get_sortable_columns() + * @uses EDD_Gateway_Reports_Table::reports_data() + * @return void + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); // No hidden columns + $sortable = $this->get_sortable_columns(); + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} diff --git a/includes/reports/data/taxes/class-tax-collected-by-location-list-table.php b/includes/reports/data/taxes/class-tax-collected-by-location-list-table.php new file mode 100644 index 00000000000..00251c9bdec --- /dev/null +++ b/includes/reports/data/taxes/class-tax-collected-by-location-list-table.php @@ -0,0 +1,254 @@ + 'report-tax-collected-by-location', + 'plural' => 'report-tax-collected-by-locations', + 'ajax' => false, + ) ); + } + + /** + * Gets the name of the primary column. + * + * @since 2.5 + * @access protected + * + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'label'; + } + + /** + * Render each column. + * + * @since 1.5 + * + * @param array $item Contains all the data of the downloads + * @param string $column_name The name of the column + * + * @return string Column Name + */ + public function column_default( $item, $column_name ) { + return $item[ $column_name ]; + } + + /** + * Column names. + * + * @since 3.0 + * + * @return array $columns Array of all the list table columns + */ + public function get_columns() { + return array( + 'country' => __( 'Country/Region', 'easy-digital-downloads' ), + 'from' => __( 'From', 'easy-digital-downloads' ), + 'to' => __( 'To', 'easy-digital-downloads' ), + 'gross' => __( 'Gross', 'easy-digital-downloads' ), + 'tax' => __( 'Tax', 'easy-digital-downloads' ), + 'net' => __( 'Net', 'easy-digital-downloads' ), + ); + } + + /** + * Query data for the list table. + * + * @since 3.0 + * + * @return array $data All the data for the list table. + */ + public function get_data() { + global $wpdb; + + $data = array(); + $tax_rates = edd_get_tax_rates( array(), OBJECT ); + $date_filter = Reports\get_filter_value( 'dates' ); + $date_range = Reports\parse_dates_for_range( $date_filter['range'] ); + $currency = Reports\get_filter_value( 'currencies' ); + $convert_currency = empty( $currency ) || 'convert' === $currency; + $format_currency = $convert_currency ? edd_get_currency() : strtoupper( $currency ); + + // Date query. + $date_query = ''; + + if ( ! empty( $date_range['start'] ) && '0000-00-00 00:00:00' !== $date_range['start'] ) { + $date_query .= $wpdb->prepare( " AND {$wpdb->edd_orders}.date_created >= %s", esc_sql( EDD()->utils->date( $date_range['start'], null, false )->startOfDay()->format( 'mysql' ) ) ); + } + + if ( ! empty( $date_range['end'] ) && '0000-00-00 00:00:00' !== $date_range['end'] ) { + $date_query .= $wpdb->prepare( " AND {$wpdb->edd_orders}.date_created <= %s", esc_sql( EDD()->utils->date( $date_range['end'], null, false )->endOfDay()->format( 'mysql' ) ) ); + } + + $from = empty( $date_range['start'] ) || '0000-00-00 00:00:00' === $date_range['start'] + ? '—' + : edd_date_i18n( EDD()->utils->date( $date_range['start'], null, false )->startOfDay()->timestamp ); + + $to = empty( $date_range['end'] ) || '0000-00-00 00:00:00' === $date_range['end'] + ? '—' + : edd_date_i18n( EDD()->utils->date( $date_range['end'], null, false )->endOfDay()->timestamp ); + + $tax_column = $convert_currency ? 'tax / rate' : 'tax'; + $total_column = $convert_currency ? 'total / rate' : 'total'; + + $currency_sql = ''; + if ( ! $convert_currency && array_key_exists( strtoupper( $currency ), edd_get_currencies() ) ) { + $currency_sql = $wpdb->prepare( " AND currency = %s ", strtoupper( $currency ) ); + } + + /* + * We need to first calculate the total tax collected for all orders so we can determine the amount of tax collected for the global rate + * + * The total determined here will be reduced by the amount collected for each specified tax rate/region. + */ + $all_orders = $wpdb->get_results( " + SELECT SUM({$tax_column}) as tax, SUM({$total_column}) as total + FROM {$wpdb->edd_orders} + WHERE 1=1 {$currency_sql} {$date_query} + ", ARRAY_A ); + + foreach ( $tax_rates as $tax_rate ) { + + $country_region = $tax_rate->name . '-' . $tax_rate->description; + + if ( array_key_exists( $country_region, $data ) ) { + continue; // We've already pulled numbers for this country / region + } + + $location = edd_get_country_name( $tax_rate->name ); + + if ( ! empty( $tax_rate->description ) ) { + $location .= ' — ' . edd_get_state_name( $tax_rate->name, $tax_rate->description ); + } + + $region = ! empty( $tax_rate->description ) + ? $wpdb->prepare( ' AND region = %s', esc_sql( $tax_rate->description ) ) + : ''; + + $results = $wpdb->get_results( $wpdb->prepare( " + SELECT SUM($tax_column) as tax, SUM($total_column) as total, country, region + FROM {$wpdb->edd_orders} + INNER JOIN {$wpdb->edd_order_addresses} ON {$wpdb->edd_order_addresses}.order_id = {$wpdb->edd_orders}.id + WHERE {$wpdb->edd_order_addresses}.country = %s {$region} {$date_query} {$currency_sql} + ", esc_sql( $tax_rate->name ) ), ARRAY_A ); + + $all_orders[0]['tax'] -= $results[0]['tax']; + $all_orders[0]['total'] -= $results[0]['total']; + + $data[ $country_region ] = array( + 'country' => $location, + 'from' => $from, + 'to' => $to, + 'gross' => edd_currency_filter( edd_format_amount( floatval( $results[0]['total'] ) ), $format_currency ), + 'tax' => edd_currency_filter( edd_format_amount( floatval( $results[0]['tax'] ) ), $format_currency ), + 'net' => edd_currency_filter( edd_format_amount( floatval( $results[0]['total'] - $results[0]['tax'] ) ), $format_currency ), + ); + } + + if( $all_orders[0]['total'] > 0 && $all_orders[0]['tax'] > 0 ) { + + $data[ 'global' ] = array( + 'country' => __( 'Global Rate', 'easy-digital-downloads' ), + 'from' => $from, + 'to' => $to, + 'gross' => edd_currency_filter( edd_format_amount( floatval( max( 0, $all_orders[0]['total'] ) ) ), $format_currency ), + 'tax' => edd_currency_filter( edd_format_amount( floatval( max( 0, $all_orders[0]['tax'] ) ) ), $format_currency ), + 'net' => edd_currency_filter( edd_format_amount( floatval( max( 0, $all_orders[0]['total'] - $all_orders[0]['tax'] ) ) ), $format_currency ), + ); + + } + + return $data; + } + + /** + * Setup the final data for the table + * + * @since 1.5 + * @uses EDD_Gateway_Reports_Table::get_columns() + * @uses EDD_Gateway_Reports_Table::get_sortable_columns() + * @uses EDD_Gateway_Reports_Table::reports_data() + * @return void + */ + public function prepare_items() { + $columns = $this->get_columns(); + $hidden = array(); // No hidden columns + $sortable = $this->get_sortable_columns(); + $this->_column_headers = array( $columns, $hidden, $sortable ); + $this->items = $this->get_data(); + } + + /** + * Return empty array to disable sorting. + * + * @since 3.0 + * + * @return array + */ + public function get_sortable_columns() { + return array(); + } + + /** + * Return empty array to remove bulk actions. + * + * @since 3.0 + * + * @return array + */ + public function get_bulk_actions() { + return array(); + } + + /** + * Hide pagination. + * + * @since 3.0 + * + * @param string $which + */ + protected function pagination( $which ) { + + } + + /** + * Hide table navigation. + * + * @since 3.0 + * + * @param string $which + */ + protected function display_tablenav( $which ) { + + } +} diff --git a/includes/reports/exceptions/class-invalid-parameter.php b/includes/reports/exceptions/class-invalid-parameter.php new file mode 100644 index 00000000000..8f9b66ce973 --- /dev/null +++ b/includes/reports/exceptions/class-invalid-parameter.php @@ -0,0 +1,43 @@ +utils->get_registry( 'reports:endpoints' ); + + if ( empty( $registry ) || is_wp_error( $registry ) ) { + return false; + } + + try { + $added = $registry->register_endpoint( $endpoint_id, $attributes ); + + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + + $added = false; + } + + return $added; +} + +/** + * Retrieves and builds an endpoint object. + * + * @since 3.0 + * + * @see \EDD\Reports\Data\Endpoint_Registry::build_endpoint() + * + * @param string $endpoint_id Endpoint ID. + * @param string $view_type View type to use when building the object. + * @return Data\Endpoint|\WP_Error Endpoint object on success, otherwise a WP_Error object. + */ +function get_endpoint( $endpoint_id, $view_type ) { + + /** @var Data\Endpoint_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports:endpoints' ); + + if ( empty( $registry ) || is_wp_error( $registry ) ) { + return $registry; + } + + return $registry->build_endpoint( $endpoint_id, $view_type ); +} + +/** + * Registers a new report. + * + * @since 3.0 + * + * @see \EDD\Reports\Data\Report_Registry::add_report() + * + * @param string $report_id Report ID. + * @param array $attributes { + * Reports attributes. All arguments are required unless otherwise noted. + * + * @type string $label Report label. + * @type int $priority Optional. Priority by which to register the report. Default 10. + * @type array $filters Filters available to the report. + * @type array $endpoints Endpoints to associate with the report. + * } + * @return bool True if the report was successfully registered, otherwise false. + */ +function add_report( $report_id, $attributes ) { + + /** @var Data\Report_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports' ); + + if ( empty( $registry ) || is_wp_error( $registry ) ) { + return false; + } + + try { + $added = $registry->add_report( $report_id, $attributes ); + + } catch ( \EDD_Exception $exception ) { + edd_debug_log_exception( $exception ); + + $added = false; + } + + return $added; +} + +/** + * Retrieves and builds a report object. + * + * @since 3.0 + * + * @see \EDD\Reports\Data\Report_Registry::build_report() + * + * @param string $report_id Report ID. + * @param bool $build_endpoints Optional. Whether to build the endpoints (includes registering + * any endpoint dependencies, such as registering meta boxes). + * Default true. + * @return Data\Report|\WP_Error Report object on success, otherwise a WP_Error object. + */ +function get_report( $report_id = false, $build_endpoints = true ) { + + /** @var Data\Report_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports' ); + + if ( empty( $registry ) || is_wp_error( $registry ) ) { + return $registry; + } + + return $registry->build_report( $report_id, $build_endpoints ); +} + +/** Sections ******************************************************************/ + +/** + * Retrieves the list of slug/label report pairs. + * + * @since 3.0 + * + * @return array List of reports, otherwise an empty array. + */ +function get_reports() { + + /** @var Data\Report_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports' ); + + if ( empty( $registry ) || is_wp_error( $registry ) ) { + return array(); + } else { + $reports = $registry->get_reports( 'priority', 'core' ); + } + + // Re-sort by priority. + uasort( $reports, array( $registry, 'priority_sort' ) ); + + /** + * Filters the list of report slug/label pairs. + * + * @since 3.0 + * + * @param array $reports List of slug/label pairs as representative of reports. + */ + return apply_filters( 'edd_get_reports', $reports ); +} + +/** + * Retrieves the slug for the active report. + * + * @since 3.0 + * + * @return string The active report, or the 'overview' report if no view defined + */ +function get_current_report() { + return isset( $_REQUEST['view'] ) + ? sanitize_key( $_REQUEST['view'] ) + : 'overview'; // Hardcoded default +} + +/** Endpoints *****************************************************************/ + +/** + * Retrieves the list of supported endpoint view types and their attributes. + * + * @since 3.0 + * + * @return array List of supported endpoint types. + */ +function get_endpoint_views() { + if ( ! did_action( 'edd_reports_init' ) ) { + _doing_it_wrong( __FUNCTION__, 'Endpoint views cannot be retrieved prior to the firing of the edd_reports_init hook.', 'EDD 3.0' ); + + return array(); + } + + /** @var Data\Endpoint_View_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports:endpoints:views' ); + + if ( empty( $registry ) || is_wp_error( $registry ) ) { + return array(); + } else { + $views = $registry->get_endpoint_views(); + } + + return $views; +} + +/** + * Retrieves the name of the handler class for a given endpoint view. + * + * @since 3.0 + * + * @param string $view Endpoint view. + * @return string Handler class name if set and the view exists, otherwise an empty string. + */ +function get_endpoint_handler( $view ) { + $views = get_endpoint_views(); + + return isset( $views[ $view ]['handler'] ) + ? $views[ $view ]['handler'] + : ''; +} + +/** + * Retrieves the group display callback for a given endpoint view. + * + * @since 3.0 + * + * @param string $view Endpoint view. + * @return string Group callback if set, otherwise an empty string. + */ +function get_endpoint_group_callback( $view ) { + $views = get_endpoint_views(); + + return isset( $views[ $view ]['group_callback'] ) + ? $views[ $view ]['group_callback'] + : ''; +} + +/** + * Determines whether an endpoint view is valid. + * + * @since 3.0 + * + * @param string $view Endpoint view slug. + * @return bool True if the view is valid, otherwise false. + */ +function validate_endpoint_view( $view ) { + return array_key_exists( $view, get_endpoint_views() ); +} + +/** + * Parses views for an incoming endpoint. + * + * @since 3.0 + * + * @see get_endpoint_views() + * + * @param array $views View slugs and attributes as dictated by get_endpoint_views(). + * + * @return array (Maybe) adjusted views slugs and attributes array. + */ +function parse_endpoint_views( $views ) { + $valid_views = get_endpoint_views(); + + foreach ( $views as $view => $attributes ) { + if ( ! empty( $valid_views[ $view ]['fields'] ) ) { + $fields = $valid_views[ $view ]['fields']; + + // Merge the incoming args with the field defaults. + $view_args = wp_parse_args( $attributes, $fields ); + + // Overwrite the view attributes, keeping only the valid fields. + $views[ $view ] = array_intersect_key( $view_args, $fields ); + + if ( $views[ $view ]['display_callback'] === $fields['display_callback'] ) { + $views[ $view ]['display_args'] = wp_parse_args( $views[ $view ]['display_args'], $fields['display_args'] ); + } + } + } + + return $views; +} + +/** Filters *******************************************************************/ + +/** + * Retrieves the list of registered reports filters and their attributes. + * + * @since 3.0 + * + * @return array List of supported endpoint filters. + */ +function get_filters() { + $filters = array( + 'dates' => array( + 'label' => __( 'Date', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_dates_filter' + ), + 'products' => array( + 'label' => __( 'Products', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_products_filter' + ), + 'product_categories' => array( + 'label' => __( 'Product Categories', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_product_categories_filter' + ), + 'taxes' => array( + 'label' => __( 'Exclude Taxes', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_taxes_filter' + ), + 'gateways' => array( + 'label' => __( 'Gateways', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_gateways_filter' + ), + 'discounts' => array( + 'label' => __( 'Discounts', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_discounts_filter' + ), + 'regions' => array( + 'label' => __( 'Regions', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_region_filter' + ), + 'countries' => array( + 'label' => __( 'Countries', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_country_filter' + ), + 'currencies' => array( + 'label' => __( 'Currencies', 'easy-digital-downloads' ), + 'display_callback' => __NAMESPACE__ . '\\display_currency_filter' + ) + ); + + /** + * Filters the list of available report filters. + * + * @since 3.0 + * + * @param array[] $filters + */ + return apply_filters( 'edd_report_filters', $filters ); +} + +/** + * Determines whether the given filter is valid. + * + * @since 3.0 + * + * @param string $filter Filter key. + * @return bool True if the filter is valid, otherwise false. + */ +function validate_filter( $filter ) { + return array_key_exists( $filter, get_filters() ); +} + +/** + * Retrieves the value of an endpoint filter for the current session and report. + * + * @since 3.0 + * + * @param string $filter Filter key to retrieve the value for. + * @return mixed|string Value of the filter if it exists, otherwise an empty string. + */ +function get_filter_value( $filter ) { + $value = ''; + + // Bail if filter does not validate + if ( ! validate_filter( $filter ) ) { + return $value; + } + + switch ( $filter ) { + // Handle dates. + case 'dates': + $default_range = 'this_month'; + $default_relative_range = 'previous_period'; + + if ( ! isset( $_GET['range'] ) ) { + $dates = parse_dates_for_range( $default_range ); + $value = array( + 'range' => $default_range, + 'relative_range' => $default_relative_range, + 'from' => $dates['start']->format( 'Y-m-d' ), + 'to' => $dates['end']->format( 'Y-m-d' ), + ); + } else { + $value = array( + 'range' => isset( $_GET['range'] ) + ? sanitize_text_field( $_GET['range'] ) + : $default_range, + 'relative_range' => isset( $_GET['relative_range'] ) + ? sanitize_text_field( $_GET['relative_range'] ) + : $default_relative_range, + 'from' => isset( $_GET['filter_from'] ) + ? sanitize_text_field( $_GET['filter_from'] ) + : '', + 'to' => isset( $_GET['filter_to'] ) + ? sanitize_text_field( $_GET['filter_to'] ) + : '' + ); + } + + break; + + // Handle taxes. + case 'taxes': + $value = array(); + + if ( isset( $_GET['exclude_taxes'] ) ) { + $value['exclude_taxes'] = true; + } + + break; + + // Handle default (direct from URL). + default: + $value = isset( $_GET[ $filter ] ) + ? sanitize_text_field( $_GET[ $filter ] ) + : ''; + + /** + * Filters the value of a report filter. + * + * @since 3.0 + * + * @param string $value Report filter value. + * @param string $filter Report filter. + */ + $value = apply_filters( 'edd_reports_get_filter_value', $value, $filter ); + } + + return $value; +} + +/** + * Returns a list of registered report filters that should be persisted across views. + * + * @since 3.0 + * + * @return array + */ +function get_persisted_filters() { + $filters = array( + 'range', + 'relative_range', + 'filter_from', + 'filter_to', + 'exclude_taxes', + ); + + /** + * Filters registered report filters that should be persisted across views. + * + * @since 3.0 + * + * @param array $filters List of registered filters to persist. + */ + $filters = apply_filters( 'edd_reports_get_persisted_filters', $filters ); + + return $filters; +} + +/** + * Retrieves key/label pairs of date filter options for use in a drop-down. + * + * @since 3.0 + * + * @return array Key/label pairs of date filter options. + */ +function get_dates_filter_options() { + static $options = null; + + if ( is_null( $options ) ) { + $options = array( + 'other' => __( 'Custom', 'easy-digital-downloads' ), + 'today' => __( 'Today', 'easy-digital-downloads' ), + 'yesterday' => __( 'Yesterday', 'easy-digital-downloads' ), + 'this_week' => __( 'This Week', 'easy-digital-downloads' ), + 'last_week' => __( 'Last Week', 'easy-digital-downloads' ), + 'last_30_days' => __( 'Last 30 Days', 'easy-digital-downloads' ), + 'this_month' => __( 'Month to Date', 'easy-digital-downloads' ), + 'last_month' => __( 'Last Month', 'easy-digital-downloads' ), + 'this_quarter' => __( 'Quarter to Date', 'easy-digital-downloads' ), + 'last_quarter' => __( 'Last Quarter', 'easy-digital-downloads' ), + 'this_year' => __( 'Year to Date', 'easy-digital-downloads' ), + 'last_year' => __( 'Last Year', 'easy-digital-downloads' ), + ); + } + + /** + * Filters the list of key/label pairs of date filter options. + * + * @since 1.3 + * + * @param array $date_options Date filter options. + */ + return apply_filters( 'edd_report_date_options', $options ); +} + + +/** + * Retrieves the default relative range key for a specific range. + * + * @since 3.1 + * + * @return string Relative date range key. + */ +function get_default_relative_range( $range ) { + + switch ( $range ) { + case 'this_month': + case 'last_month': + $relative_range = 'previous_month'; + break; + + case 'this_quarter': + case 'last_quarter': + $relative_range = 'previous_quarter'; + break; + + case 'this_year': + case 'last_year': + $relative_range = 'previous_year'; + break; + + default: + $relative_range = 'previous_period'; + break; + } + + return $relative_range; +} + + +/** + * Retrieves key/label pairs of relative date filter options for use in a drop-down. + * + * @since 3.1 + * + * @return array Key/label pairs of relative date filter options. + */ +function get_relative_dates_filter_options() { + static $options = null; + + if ( is_null( $options ) ) { + $options = array( + 'previous_period' => __( 'Previous period', 'easy-digital-downloads' ), + 'previous_month' => __( 'Previous month', 'easy-digital-downloads' ), + 'previous_quarter' => __( 'Previous quarter', 'easy-digital-downloads' ), + 'previous_year' => __( 'Previous year', 'easy-digital-downloads' ), + ); + } + + return $options; +} + +/** + * Retrieves the start and end date filters for use with the Reports API. + * + * @since 3.0 + * + * @param string $values Optional. What format to retrieve dates in the resulting array in. + * Accepts 'strings' or 'objects'. Default 'strings'. + * @param string $timezone Optional. Timezone to force for filter dates. Primarily used for + * legacy testing purposes. Default empty. + * @return array|\EDD\Utils\Date[] { + * Query date range for the current graph filter request. + * + * @type string|\EDD\Utils\Date $start Start day and time (based on the beginning of the given day). + * If `$values` is 'objects', a Carbon object, otherwise a date + * time string. + * @type string|\EDD\Utils\Date $end End day and time (based on the end of the given day). If `$values` + * is 'objects', a Carbon object, otherwise a date time string. + * } + */ +function get_dates_filter( $values = 'strings', $timezone = null ) { + $dates = parse_dates_for_range(); + + if ( 'strings' === $values ) { + if ( ! empty( $dates['start'] ) ) { + $dates['start'] = $dates['start']->toDateTimeString(); + } + if ( ! empty( $dates['end'] ) ) { + $dates['end'] = $dates['end']->toDateTimeString(); + } + } + + /** + * Filters the start and end date filters for use with the Graphs API. + * + * @since 3.0 + * + * @param array|\EDD\Utils\Date[] $dates { + * Query date range for the current graph filter request. + * + * @type string|\EDD\Utils\Date $start Start day and time (based on the beginning of the given day). + * If `$values` is 'objects', a Date object, otherwise a date + * time string. + * @type string|\EDD\Utils\Date $end End day and time (based on the end of the given day). If `$values` + * is 'objects', a Date object, otherwise a date time string. + * } + */ + return apply_filters( 'edd_get_dates_filter', $dates ); +} + +/** + * Parses start and end dates for the given range. + * + * @since 3.0 + * + * @param string $range Optional. Range value to generate start and end dates for against `$date`. + * Default is the current range as derived from the session. + * @param string $date Date string converted to `\EDD\Utils\Date` to anchor calculations to. + * @param bool $convert_to_utc Optional. If we should convert the results to UTC for Database Queries + * @return \EDD\Utils\Date[] Array of start and end date objects. + */ +function parse_dates_for_range( $range = null, $date = 'now', $convert_to_utc = true ) { + + // Set the time ranges in the user's timezone, so they ultimately see them in their own timezone. + $date = EDD()->utils->date( $date, edd_get_timezone_id(), false ); + + if ( null === $range || ! array_key_exists( $range, get_dates_filter_options() ) ) { + $range = get_dates_filter_range(); + } + + switch ( $range ) { + + case 'this_month': + $dates = array( + 'start' => $date->copy()->startOfMonth(), + 'end' => $date->copy()->endOfDay(), + ); + break; + + case 'last_month': + $dates = array( + 'start' => $date->copy()->subMonthNoOverflow( 1 )->startOfMonth(), + 'end' => $date->copy()->subMonthNoOverflow( 1 )->endOfMonth(), + ); + break; + + case 'today': + $dates = array( + 'start' => $date->copy()->startOfDay(), + 'end' => $date->copy()->endOfDay(), + ); + break; + + case 'yesterday': + $dates = array( + 'start' => $date->copy()->subDay( 1 )->startOfDay(), + 'end' => $date->copy()->subDay( 1 )->endOfDay(), + ); + break; + + case 'this_week': + $dates = array( + 'start' => $date->copy()->startOfWeek(), + 'end' => $date->copy()->endOfDay(), + ); + break; + + case 'last_week': + $dates = array( + 'start' => $date->copy()->subWeek( 1 )->startOfWeek(), + 'end' => $date->copy()->subWeek( 1 )->endOfWeek(), + ); + break; + + case 'last_30_days': + $dates = array( + 'start' => $date->copy()->subDay( 30 )->startOfDay(), + 'end' => $date->copy()->endOfDay(), + ); + break; + + case 'this_quarter': + $dates = array( + 'start' => $date->copy()->startOfQuarter(), + 'end' => $date->copy()->endOfDay(), + ); + break; + + case 'last_quarter': + $dates = array( + 'start' => $date->copy()->subQuarter( 1 )->startOfQuarter(), + 'end' => $date->copy()->subQuarter( 1 )->endOfQuarter(), + ); + break; + + case 'this_year': + $dates = array( + 'start' => $date->copy()->startOfYear(), + 'end' => $date->copy()->endOfDay(), + ); + break; + + case 'last_year': + $dates = array( + 'start' => $date->copy()->subYear( 1 )->startOfYear(), + 'end' => $date->copy()->subYear( 1 )->endOfYear(), + ); + break; + + case 'other': + default: + $dates_from_report = get_filter_value( 'dates' ); + + if ( ! empty( $dates_from_report ) ) { + $start = $dates_from_report['from']; + $end = $dates_from_report['to']; + } else { + $start = $end = 'now'; + } + + $dates = array( + 'start' => EDD()->utils->date( $start, edd_get_timezone_id(), false )->startOfDay(), + 'end' => EDD()->utils->date( $end, edd_get_timezone_id(), false )->endOfDay(), + ); + break; + } + + if ( $convert_to_utc ) { + // Convert the values to the UTC equivalent so that we can query the database using UTC. + $dates['start'] = edd_get_utc_equivalent_date( $dates['start'] ); + $dates['end'] = edd_get_utc_equivalent_date( $dates['end'] ); + } + + $dates['range'] = $range; + + return $dates; +} + +/** + * Parses relative start and end dates for the given range. + * + * @since 3.1 + * + * @param string $range Optional. Range value to generate start and end dates for against `$date`. + * @param string $relative_range Optional. Range value to generate relative start and end dates for against `$date`. + * Default is the current range as derived from the session. + * @param string $date Date string converted to `\EDD\Utils\Date` to anchor calculations to. + * @param bool $convert_to_utc Optional. If we should convert the results to UTC for Database Queries + * @return \EDD\Utils\Date[] Array of start and end date objects. + */ +function parse_relative_dates_for_range( $range = null, $relative_range = null, $date = 'now', $convert_to_utc = true ) { + + // Set the time ranges in the user's timezone, so they ultimately see them in their own timezone. + $date = EDD()->utils->date( $date, edd_get_timezone_id(), false ); + + if ( null === $range || ! array_key_exists( $range, get_dates_filter_options() ) ) { + $range = get_dates_filter_range(); + } + + if ( null === $relative_range || ! array_key_exists( $relative_range, get_relative_dates_filter_options() ) ) { + $relative_range = get_relative_dates_filter_range(); + } + + $dates = parse_dates_for_range( $range, $date, false ); + + switch ( $relative_range ) { + case 'previous_period': + $days_diff = $dates['start']->copy()->diffInDays( $dates['end'], true ) + 1; + $dates = array( + 'start' => $dates['start']->copy()->subDays( $days_diff ), + 'end' => $dates['end']->copy()->subDays( $days_diff ), + ); + break; + case 'previous_month': + $dates = array( + 'start' => $dates['start']->copy()->subMonth( 1 ), + 'end' => $dates['end']->copy()->subMonthNoOverflow( 1 ), + ); + break; + case 'previous_quarter': + $dates = array( + 'start' => $dates['start']->copy()->subQuarter( 1 ), + 'end' => $dates['end']->copy()->subQuarter( 1 ), + ); + break; + case 'previous_year': + $dates = array( + 'start' => $dates['start']->copy()->subYear( 1 ), + 'end' => $dates['end']->copy()->subYear( 1 ), + ); + break; + } + + + if ( $convert_to_utc ) { + // Convert the values to the UTC equivalent so that we can query the database using UTC. + $dates['start'] = edd_get_utc_equivalent_date( $dates['start'] ); + $dates['end'] = edd_get_utc_equivalent_date( $dates['end'] ); + } + + $dates['range'] = $range; + + return $dates; +} + +/** + * Retrieves the date filter range. + * + * @since 3.0 + * + * @return string Date filter range. + */ +function get_dates_filter_range() { + + $dates = get_filter_value( 'dates' ); + + if ( isset( $dates['range'] ) ) { + $range = sanitize_key( $dates['range'] ); + + } else { + + /** + * Filters the report dates default range. + * + * @since 1.3 + * + * @param string $range Date range as derived from the session. Default 'last_30_days' + * @param array $dates Dates filter data array. + */ + $range = apply_filters( 'edd_get_report_dates_default_range', 'this_month', $dates ); + } + + /** + * Filters the dates filter range. + * + * @since 3.0 + * + * @param string $range Dates filter range. + * @param array $dates Dates filter data array. + */ + return apply_filters( 'edd_get_dates_filter_range', $range, $dates ); +} + + +/** + * Retrieves the date filter for relative range. + * + * @since 3.1 + * + * @return string Date filter range. + */ +function get_relative_dates_filter_range() { + + $dates = get_filter_value( 'dates' ); + + if ( isset( $dates['relative_range'] ) ) { + $relative_range = sanitize_key( $dates['relative_range'] ); + } else { + + /** + * Filters the report dates default range. + * + * @since 3.1 + * + * @param string $range Relative daate range as derived from the session. Default 'previous_period' + * @param array $dates Dates filter data array. + */ + $relative_range = apply_filters( 'edd_get_report_dates_default_relative_range', 'previous_period', $dates ); + } + + /** + * Filters the dates filter range. + * + * @since 3.1 + * + * @param string $range Dates filter relative range. + * @param array $dates Dates filter data array. + */ + return apply_filters( 'edd_get_dates_filter_relative_range', $relative_range, $dates ); +} + +/** + * Determines whether results should be displayed hour by hour, or not. + * + * @since 3.0 + * + * @return bool True if results should use hour by hour, otherwise false. + */ +function get_dates_filter_hour_by_hour() { + $hour_by_hour = false; + + // Retrieve the queried dates. + $dates = get_dates_filter( 'objects' ); + + // Determine graph options. + switch ( $dates['range'] ) { + case 'today': + case 'yesterday': + $hour_by_hour = true; + break; + case 'this_week': + case 'this_month': + case 'this_quarter': + case 'this_year': + case 'other': + $difference = ( $dates['end']->getTimestamp() - $dates['start']->getTimestamp() ); + if ( $difference <= ( DAY_IN_SECONDS * 2 ) ) { + $hour_by_hour = true; + } + break; + default: + $hour_by_hour = false; + break; + } + + return $hour_by_hour; +} + +/** + * Determines whether results should be displayed day by day or not. + * + * @since 3.0 + * + * @return bool True if results should use day by day, otherwise false. + */ +function get_dates_filter_day_by_day() { + // Retrieve the queried dates + $dates = get_dates_filter( 'objects' ); + + // Determine graph options + switch ( $dates['range'] ) { + case 'today': + case 'yesterday': + case 'this_year': + case 'last_year': + $day_by_day = false; + break; + case 'other': + $difference = ( $dates['end']->getTimestamp() - $dates['start']->getTimestamp() ); + + if ( $difference >= ( YEAR_IN_SECONDS / 4 ) ) { + $day_by_day = false; + } else { + $day_by_day = true; + } + break; + default: + $day_by_day = true; + break; + } + + return $day_by_day; +} + +/** + * Gets the period for a graph. + * + * @since 3.1.1.4 + * @return string + */ +function get_graph_period() { + if ( get_dates_filter_hour_by_hour() ) { + return 'hour'; + } + if ( get_dates_filter_day_by_day() ) { + return 'day'; + } + + return 'month'; +} + +/** + * Gets the SQL clauses. + * The result of this function should be run through $wpdb->prepare(). + * + * @since 3.1.1.4 + * @param string $period The period for the query. + * @param string $column The column to query. + * @return array + */ +function get_sql_clauses( $period, $column = 'date_created' ) { + + // Get the date for the query. + $converted_date = get_column_conversion( $column ); + + switch ( $period ) { + case 'hour': + $date_format = '%%Y-%%m-%%d %%H:00:00'; + break; + case 'day': + $date_format = '%%Y-%%m-%%d'; + break; + default: + $date_format = '%%Y-%%m'; + break; + } + + return array( + 'select' => "DATE_FORMAT({$converted_date}, \"{$date_format}\") AS date", + 'where' => '', + 'groupby' => 'date', + 'orderby' => 'date', + ); +} + +/** + * Given a function and column, make a timezone converted groupby query. + * + * @since 3.0 + * @since 3.0.4 If MONTH is passed as the function, always add YEAR and MONTH + * to avoid issues with spanning multiple years. + * @since 3.1.1.4 This function isn't needed anymore due to using DATE_FORMAT in the select clause. + * + * @param string $function The function to run the value through, like DATE, HOUR, MONTH. + * @param string $column The column to group by. + * + * @return string + */ +function get_groupby_date_string( $function = 'DATE', $column = 'date_created' ) { + /** + * If there is no offset, the default column will be returned. + * Otherwise, the column will be converted to the timezone offset. + */ + $column_conversion = get_column_conversion( $column ); + + $function = strtoupper( $function ); + switch ( $function ) { + case 'HOUR': + $group_by_string = "DAY({$column_conversion}), HOUR({$column_conversion})"; + break; + case 'MONTH': + $group_by_string = "YEAR({$column_conversion}), MONTH({$column_conversion})"; + break; + default: + $group_by_string = "{$function}({$column_conversion})"; + break; + } + + return $group_by_string; +} + +/** + * Get the time zone converted dates for the query. + * + * @since 3.1.1.4 + * @param string $column + * @return string + */ +function get_column_conversion( $column = 'date_created' ) { + $date = EDD()->utils->date( 'now', edd_get_timezone_id(), false ); + $gmt_offset = $date->getOffset(); + if ( empty( $gmt_offset ) ) { + return $column; + } + + // Output the offset in the proper format. + $hours = abs( floor( $gmt_offset / HOUR_IN_SECONDS ) ); + $minutes = abs( floor( ( $gmt_offset / MINUTE_IN_SECONDS ) % MINUTE_IN_SECONDS ) ); + $math = ( $gmt_offset >= 0 ) ? '+' : '-'; + + $formatted_offset = ! empty( $minutes ) ? "{$hours}:{$minutes}" : $hours . ':00'; + + /** + * There is a limitation here that we cannot get past due to MySQL not having timezone information. + * + * When a requested date group spans the DST change. For instance, a 6 month graph will have slightly + * different results for each month than if you pulled each of those 6 months individually. This is because + * our 'grouping' can only convert the timezone based on the current offset and that can change if the + * range spans the DST break, which would have some dates be in a +/- 1 hour state. + * + * @see https://github.com/awesomemotive/easy-digital-downloads/pull/9449 + */ + return "CONVERT_TZ({$column}, '+00:00', '{$math}{$formatted_offset}')"; +} + +/** + * Retrieves the tax exclusion filter. + * + * @since 3.0 + * + * @return bool True if taxes should be excluded from calculations. + */ +function get_taxes_excluded_filter() { + $taxes = get_filter_value( 'taxes' ); + + if ( ! isset( $taxes['exclude_taxes'] ) ) { + return false; + } + + return (bool) $taxes['exclude_taxes']; +} + +/** Display *******************************************************************/ + +/** + * Handles display of a report. + * + * @since 3.0 + * + * @param Data\Report $report Report object. + */ +function default_display_report( $report ) { + + // Bail if erroneous report + if ( empty( $report ) || is_wp_error( $report ) ) { + return; + } + + // Try to output: tiles, tables, and charts + $report->display_endpoint_group( 'tiles' ); + $report->display_endpoint_group( 'tables' ); + $report->display_endpoint_group( 'charts' ); +} + +/** + * Displays the default content for a tile endpoint. + * + * @since 3.0 + * + * @param Data\Report $report Report object the tile endpoint is being rendered in. + * Not always set. + * @param array $tile { + * Tile display arguments. + * + * @type Data\Tile_Endpoint $endpoint Endpoint object. + * @type mixed|array $data Date for display. By default, will be an array, + * but can be of other types. + * @type array $display_args Array of any display arguments. + * } + * @return void Meta box display callbacks only echo output. + */ +function default_display_tile( $endpoint, $data, $args ) { + echo '
    ' . esc_html( $endpoint->get_label() ) . '
    '; + + if ( empty( $data ) ) { + echo '
    —
    '; + } else { + switch ( $args['type'] ) { + case 'number': + echo '
    ' . edd_format_amount( $data ) . '
    '; + break; + + case 'split-number': + printf( '
    %1$d / %2$d
    ', + edd_format_amount( $data['first_value'] ), + edd_format_amount( $data['second_value'] ) + ); + break; + + case 'split-amount': + printf( '
    %1$d / %2$d
    ', + edd_currency_filter( edd_format_amount( $data['first_value'] ) ), + edd_currency_filter( edd_format_amount( $data['second_value'] ) ) + ); + break; + + case 'relative': + $direction = ( ! empty( $data['direction'] ) && in_array( $data['direction'], array( 'up', 'down' ), true ) ) + ? '-' . sanitize_key( $data['direction'] ) + : ''; + echo '
    ' . edd_format_amount( $data['value'] ) . '
    '; + break; + + case 'amount': + echo '
    ' . edd_currency_filter( edd_format_amount( $data ) ) . '
    '; + break; + + case 'url': + echo '
    ' . esc_url( $data ) . '
    '; + break; + + default: + $tags = wp_kses_allowed_html( 'post' ); + echo '
    ' . wp_kses( $data, $tags ) . '
    '; + break; + } + } + + if ( ! empty( $args['comparison_label'] ) ) { + echo '
    ' . esc_attr( $args['comparison_label'] ) . '
    '; + } +} + +/** + * Handles default display of all tile endpoints registered against a report. + * + * @since 3.0 + * + * @param Data\Report $report Report object. + */ +function default_display_tiles_group( $report ) { + if ( ! $report->has_endpoints( 'tiles' ) ) { + return; + } + + $tiles = $report->get_endpoints( 'tiles' ); +?> + +
    + $tile ) : + $tile->display(); + endforeach; + ?> +
    + + has_endpoints( 'tables' ) ) { + return; + } + + $tables = $report->get_endpoints( 'tables' ); ?> + +
    $table ) : + + ?>
    +

    get_label() ); ?>

    display(); + + ?>
    has_endpoints( 'charts' ) ) { + return; + } + + ?> +
    + get_endpoints( 'charts' ); + + foreach ( $charts as $endpoint_id => $chart ) { + ?> +
    +

    get_label() ); ?>

    + + display(); ?> +
    + +
    + +
    +
    + html->select( + array( + 'name' => 'range', + 'class' => 'edd-graphs-date-options', + 'options' => $range_options, + 'variations' => false, + 'show_option_all' => false, + 'show_option_none' => false, + 'selected' => $selected_range, + ) + ); + + $relative_range_select = EDD()->html->select( + array( + 'name' => 'relative_range', + 'class' => 'edd-graphs-relative-date-options', + 'options' => $relative_range_options, + 'variations' => false, + 'show_option_all' => false, + 'show_option_none' => false, + 'selected' => $selected_relative_range, + ) + ); + + // From. + $from = EDD()->html->date_field( + array( + 'id' => 'filter_from', + 'name' => 'filter_from', + 'value' => ( empty( $dates['from'] ) || ( 'other' !== $dates['range'] ) ) ? '' : $dates['from'], + 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ), + ) + ); + + // To. + $to = EDD()->html->date_field( + array( + 'id' => 'filter_to', + 'name' => 'filter_to', + 'value' => ( empty( $dates['to'] ) || ( 'other' !== $dates['range'] ) ) ? '' : $dates['to'], + 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ), + ) + ); + + // Output fields + ?> +
    + + +
    + +
    + $range_name ) : + $range_dates = \EDD\Reports\parse_dates_for_range( $range_key ); + $selected_range_class = ( $selected_range !== $range_key ) ? 'hidden' : ''; + $start_date = edd_get_edd_timezone_equivalent_date_from_utc( $range_dates['start'] )->format( $date_format ); + $end_date = edd_get_edd_timezone_equivalent_date_from_utc( $range_dates['end'] )->format( $date_format ); + $label = $start_date; + if ( $start_date !== $end_date ) { + $label = $start_date . ' - ' . $end_date; + } + ?> + + +
    +
    + +
    + + + + +
    + + + + +
    + +
    +
    +
    +
    + + + + +
      + $relative_range_name ) : + $relative_range_dates = \EDD\Reports\parse_relative_dates_for_range( $range, $relative_range_key ); + $selected_range_class = ( $selected_relative_range === $relative_range_key ) ? 'active' : ''; + ?> +
    • + + format( $date_format ) ); ?> - format( $date_format ) ); ?> +
    • + +
    + html->product_dropdown( array( + 'chosen' => true, + 'variations' => true, + 'selected' => empty( $products ) ? 0 : $products, + 'show_option_none' => false, + 'show_option_all' => sprintf( __( 'All %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + ) ); ?> + + + + html->category_dropdown( 'product_categories', get_filter_value( 'product_categories' ) ); ?> + + + + + + array( 'code', 'name' ), + 'number' => 100, + ) ); + + $discounts = array(); + + foreach ( $d as $discount_data ) { + $discounts[ $discount_data->code ] = esc_html( $discount_data->name ); + } + + // Get the select + $select = EDD()->html->discount_dropdown( array( + 'name' => 'discounts', + 'chosen' => true, + 'selected' => empty( $discount ) ? 0 : $discount, + ) ); ?> + + $data ) { + $gateways[ $id ] = esc_html( $data['admin_label'] ); + } + + // Get the select + $select = EDD()->html->select( array( + 'name' => 'gateways', + 'options' => $gateways, + 'selected' => empty( $gateway ) ? 0 : $gateway, + 'show_option_none' => false, + ) ); ?> + + html->region_select( + array( + 'name' => 'regions', + 'id' => 'edd_reports_filter_regions', + 'options' => $regions, + ), + $country, + $region + ); + ?> + + html->country_select( + array( + 'name' => 'countries', + 'id' => 'edd_reports_filter_countries', + 'options' => $countries, + ), + $country + ); + ?> + + get_col( + "SELECT distinct currency FROM {$wpdb->edd_orders}" + ); + + if ( is_array( $order_currencies ) ) { + $order_currencies = array_filter( $order_currencies ); + } + + set_transient( 'edd_distinct_order_currencies', $order_currencies, 3 * HOUR_IN_SECONDS ); + } + + if ( ! is_array( $order_currencies ) || count( $order_currencies ) <= 1 ) { + return; + } + + $all_currencies = array_intersect_key( edd_get_currencies(), array_flip( $order_currencies ) ); + if ( array_key_exists( edd_get_currency(), $all_currencies ) ) { + $all_currencies = array_merge( array( + 'convert' => sprintf( __( '%s - Converted', 'easy-digital-downloads' ), $all_currencies[ edd_get_currency() ] ) + ), $all_currencies ); + } + ?> + + html->select( array( + 'name' => 'currencies', + 'id' => 'edd_reports_filter_currencies', + 'options' => $all_currencies, + 'selected' => $currency, + 'show_option_all' => false, + 'show_option_none' => false + ) ); + ?> + + 'edd-reports', + ) ); + ?> + +
    + +
    + + get_id(); + + // Bail if no report + if ( empty( $report_id ) ) { + return; + } + + $redirect_url = edd_get_admin_url( array( + 'page' => 'edd-reports', + 'view' => sanitize_key( $report_id ), + ) ); + + // Bail if no filters + $filters = $report->get_filters(); + if ( empty( $filters ) ) { + return; + } + + // Bail if no manifest + $manifest = get_filters(); + if ( empty( $manifest ) ) { + return; + } + + // Setup callables + $callables = array(); + + // Loop through filters and find the callables + foreach ( $filters as $filter ) { + + // Skip if empty + if ( empty( $manifest[ $filter ]['display_callback'] ) ) { + continue; + } + + // Skip if not callable + $callback = $manifest[ $filter ]['display_callback']; + if ( ! is_callable( $callback ) ) { + continue; + } + + // Add callable to callables + $callables[] = $callback; + } + + // Bail if no callables + if ( empty( $callables ) ) { + return; + } + + // Start an output buffer + ob_start(); + + // Call the callables in the buffer + foreach ( $callables as $to_call ) { + call_user_func( $to_call, $report ); + } ?> + + + + + + + + 'reports', + 'utm_content' => 'ios-app', + ) + ); + ?> + + + + + + ID)) { - $position = edd_get_item_position_in_cart($post->ID); + // Enqueue global checkout + wp_enqueue_script( 'edd-checkout-global' ); } - + + // AJAX scripts, if enabled + if ( ! edd_is_ajax_disabled() ) { + wp_enqueue_script( 'edd-ajax' ); + } +} + +/** + * Enqueue styles + * + * Checks the styles option and hooks the required filter. + * + * @since 3.0 + */ +function edd_enqueue_styles() { + wp_enqueue_style( 'edd-styles' ); +} +add_action( 'wp_enqueue_scripts', 'edd_enqueue_styles' ); + +/** + * Localize scripts + * + * @since 3.0 + * + * @global $post $post + */ +function edd_localize_scripts() { + global $post; + + $version = edd_admin_get_script_version(); + $currency = new \EDD\Currency\Currency( edd_get_currency() ); + + if ( edd_is_checkout() ) { + wp_localize_script( 'edd-checkout-global', 'edd_global_vars', apply_filters( 'edd_global_checkout_script_vars', array( + 'ajaxurl' => esc_url_raw( edd_get_ajax_url() ), + 'checkout_nonce' => wp_create_nonce( 'edd_checkout_nonce' ), + 'checkout_error_anchor' => '#edd_purchase_submit', + 'currency_sign' => $currency->symbol, + 'currency_pos' => $currency->position, + 'decimal_separator' => $currency->decimal_separator, + 'thousands_separator' => $currency->thousands_separator, + 'no_gateway' => __( 'Please select a payment method', 'easy-digital-downloads' ), + 'no_discount' => __( 'Please enter a discount code', 'easy-digital-downloads' ), // Blank discount code message + 'enter_discount' => __( 'Enter discount', 'easy-digital-downloads' ), + 'discount_applied' => __( 'Discount Applied', 'easy-digital-downloads' ), // Discount verified message + 'no_email' => __( 'Please enter an email address before applying a discount code', 'easy-digital-downloads' ), + 'no_username' => __( 'Please enter a username before applying a discount code', 'easy-digital-downloads' ), + 'purchase_loading' => __( 'Please Wait...', 'easy-digital-downloads' ), + 'complete_purchase' => edd_get_checkout_button_purchase_label(), + 'taxes_enabled' => edd_use_taxes() ? '1' : '0', + 'edd_version' => $version, + 'current_page' => get_the_ID(), + ) ) ); + } + // Load AJAX scripts, if enabled - if( edd_is_ajax_enabled()) { - wp_enqueue_script('edd-ajax', EDD_PLUGIN_URL . 'includes/js/edd-ajax.js'); - wp_localize_script('edd-ajax', 'edd_scripts', array( - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - 'ajax_nonce' => wp_create_nonce( 'edd_ajax_nonce' ), - 'no_discount' => __('Please enter a discount code', 'edd'), // blank discount code message - 'discount_applied' => __('Discount Applied', 'edd'), // discount verified message - 'no_email' => __('Please enter an email address before applying a discount code', 'edd'), - 'position_in_cart' => isset($position) ? $position : -1, - 'already_in_cart_message' => __('You have already added this item to your cart', 'edd'), // item already in the cart message - 'empty_cart_message' => __('Your cart is empty', 'edd'), // item already in the cart message - 'loading' => __('Loading', 'edd') , // general loading message - 'ajax_loader' => EDD_PLUGIN_URL . 'includes/images/loading.gif', // ajax loading image - 'checkout_page' => isset($edd_options['purchase_page']) ? get_permalink($edd_options['purchase_page']) : '', - 'permalinks' => get_option( 'permalink_structure' ) ? '1' : '0' - ) - ); + if ( ! edd_is_ajax_disabled() ) { + + // Get position in cart of current download + $position = isset( $post->ID ) + ? edd_get_item_position_in_cart( $post->ID ) + : -1; + + if ( ( ! empty( $post->post_content ) && ( has_shortcode( $post->post_content, 'purchase_link' ) || has_shortcode( $post->post_content, 'downloads' ) ) ) || is_post_type_archive( 'download' ) ) { + $has_purchase_links = true; + } else { + $has_purchase_links = false; + } + + wp_localize_script( 'edd-ajax', 'edd_scripts', apply_filters( 'edd_ajax_script_vars', array( + 'ajaxurl' => esc_url_raw( edd_get_ajax_url() ), + 'position_in_cart' => $position, + 'has_purchase_links' => $has_purchase_links, + 'already_in_cart_message' => __('You have already added this item to your cart','easy-digital-downloads' ), // Item already in the cart message + 'empty_cart_message' => __('Your cart is empty','easy-digital-downloads' ), // Item already in the cart message + 'loading' => __('Loading','easy-digital-downloads' ) , // General loading message + 'select_option' => __('Please select an option','easy-digital-downloads' ) , // Variable pricing error with multi-purchase option enabled + 'is_checkout' => edd_is_checkout() ? '1' : '0', + 'default_gateway' => edd_get_default_gateway(), + 'redirect_to_checkout' => ( edd_straight_to_checkout() || edd_is_checkout() ) ? '1' : '0', + 'checkout_page' => esc_url_raw( edd_get_checkout_uri() ), + 'permalinks' => get_option( 'permalink_structure' ) ? '1' : '0', + 'quantities_enabled' => edd_item_quantities_enabled(), + 'taxes_enabled' => edd_use_taxes() ? '1' : '0', // Adding here for widget, but leaving in checkout vars for backcompat + 'current_page' => get_the_ID(), + ) ) ); } - - // Load jQuery validation - if(isset($edd_options['jquery_validation']) && is_page($edd_options['purchase_page'])) { - wp_enqueue_script('jquery-validation', EDD_PLUGIN_URL . 'includes/js/jquery.validate.min.js'); - wp_enqueue_script('edd-validation', EDD_PLUGIN_URL . 'includes/js/form-validation.js'); - $required = array( 'firstname' => true, 'lastname' => true ); - wp_localize_script('edd-validation', 'edd_scripts_validation', apply_filters('edd_scripts_validation',$required)); +} + +/** + * Load head styles + * + * Ensures download styling is still shown correctly if a theme is using the CSS template file + * + * @since 2.5 + * @global $post + */ +function edd_load_head_styles() { + global $post; + + // Bail if styles are disabled + if ( edd_get_option( 'disable_styles', false ) || ! is_object( $post ) ) { + return; + } + + // Use minified libraries not debugging scripts + $suffix = is_rtl() ? '-rtl' : ''; + $suffix .= edd_doing_script_debug() ? '' : '.min'; + + $file = 'edd' . $suffix . '.css'; + $templates_dir = edd_get_theme_template_dir_name(); + + $child_theme_style_sheet = trailingslashit( get_stylesheet_directory() ) . $templates_dir . $file; + $child_theme_style_sheet_2 = trailingslashit( get_stylesheet_directory() ) . $templates_dir . 'edd.css'; + $parent_theme_style_sheet = trailingslashit( get_template_directory() ) . $templates_dir . $file; + $parent_theme_style_sheet_2 = trailingslashit( get_template_directory() ) . $templates_dir . 'edd.css'; + + if ( has_shortcode( $post->post_content, 'downloads' ) && + file_exists( $child_theme_style_sheet ) || + file_exists( $child_theme_style_sheet_2 ) || + file_exists( $parent_theme_style_sheet ) || + file_exists( $parent_theme_style_sheet_2 ) + ) { + $has_css_template = apply_filters( 'edd_load_head_styles', true ); + } else { + $has_css_template = false; } - wp_enqueue_script('edd-checkout-global', EDD_PLUGIN_URL . 'includes/js/edd-checkout-global.js'); + + // Bail if no template + if ( empty( $has_css_template ) ) { + return; + } + + ?> + + array( + 'edd-admin-tools-export' + ), + 'dashboard' => array(), + 'discounts' => array(), + 'downloads' => array(), + 'tools-export' => array(), + 'tools-import' => array(), + 'notes' => array(), + 'onboarding' => array(), + 'orders' => array( + 'edd-admin-notes', + 'wp-util', + 'wp-backbone', + ), + // Backwards compatibility. + 'payments' => array(), + 'reports' => array( + 'edd-chart-js', + ), + 'settings' => array(), + 'tools' => array( + 'edd-admin-tools-export' + ), + 'upgrades' => array(), + ); + + foreach ( $admin_pages as $page => $deps ) { + wp_register_script( + 'edd-admin-' . $page, + $js_dir . 'edd-admin-' . $page . '.js', + array_merge( $admin_deps, $deps ), + $version + ); } } -add_action('wp_enqueue_scripts', 'edd_register_styles'); - +add_action( 'admin_init', 'edd_register_admin_scripts' ); /** - * Load Admin Scripts + * Register all admin area styles * - * Enqueues the required admin scripts. + * @since 3.0 + */ +function edd_register_admin_styles() { + $css_dir = EDD_PLUGIN_URL . 'assets/css/'; + $css_suffix = is_rtl() ? '-rtl.min.css' : '.min.css'; + $version = edd_admin_get_script_version(); + $deps = array( 'edd-admin' ); + + // Register styles + wp_register_style( 'jquery-chosen', $css_dir . 'chosen' . $css_suffix, array(), $version ); + wp_register_style( 'jquery-ui-css', $css_dir . 'jquery-ui-fresh' . $css_suffix, array(), $version ); + wp_register_style( 'edd-admin', $css_dir . 'edd-admin' . $css_suffix, array( 'forms' ), $version ); + wp_register_style( 'edd-admin-menu', $css_dir . 'edd-admin-menu' . $css_suffix, array(), $version ); + wp_register_style( 'edd-admin-chosen', $css_dir . 'edd-admin-chosen' . $css_suffix, $deps, $version ); + wp_register_style( 'edd-admin-email-tags', $css_dir . 'edd-admin-email-tags' . $css_suffix, $deps, $version ); + wp_register_style( 'edd-admin-datepicker', $css_dir . 'edd-admin-datepicker' . $css_suffix, $deps, $version ); + wp_register_style( 'edd-admin-tax-rates', $css_dir . 'edd-admin-tax-rates' . $css_suffix, $deps, $version ); + wp_register_style( 'edd-admin-onboarding', $css_dir . 'edd-admin-onboarding' . $css_suffix, $deps, $version ); +} +add_action( 'admin_init', 'edd_register_admin_styles' ); + +/** + * Print admin area scripts * - * @access private - * @since 1.0 - * @return void -*/ + * @since 3.0 + */ +function edd_enqueue_admin_scripts( $hook = '' ) { -function edd_load_admin_scripts($hook) { + // Bail if not an EDD admin page + if ( ! edd_should_load_admin_scripts( $hook ) ) { + return; + } - global $post, $pagenow, $edd_discounts_page, $edd_payments_page, $edd_settings_page, $edd_reports_page, $edd_add_ons_page; + /** + * Prevent the CM Admin Tools JS from loading on our settings pages, as they + * are including options and actions that can permemtnly harm a store's data. + */ + wp_deregister_script( 'cmadm-utils' ); + wp_deregister_script( 'cmadm-backend' ); - $edd_pages = array($edd_discounts_page, $edd_payments_page, $edd_settings_page, $edd_reports_page, $edd_add_ons_page, 'index.php'); - $edd_cpt = apply_filters( 'edd_load_scripts_for_these_types', array( 'download', 'edd_payment' ) ); + // Enqueue media on EDD admin pages + wp_enqueue_media(); - if ( ! in_array( $hook, $edd_pages ) && ! is_object( $post ) ) - return; + // Scripts to enqueue + $scripts = array( + 'edd-admin-scripts', + 'jquery-chosen', + 'jquery-form', + 'jquery-ui-datepicker', + 'jquery-ui-dialog', + 'jquery-ui-tooltip', + 'media-upload', + 'thickbox', + 'wp-ajax-response', + 'wp-color-picker', + ); - if ( is_object( $post ) && ! in_array( $post->post_type, $edd_cpt ) ) - return; - - if($hook == 'download_page_edd-reports') { - wp_enqueue_script('google-charts', 'https://www.google.com/jsapi'); + // Loop through and enqueue the scripts + foreach ( $scripts as $script ) { + wp_enqueue_script( $script ); } - if($hook == 'download_page_edd-discounts') { - wp_enqueue_script('jquery-ui-datepicker'); + + // Downloads page. + if ( edd_is_admin_page( 'download' ) ) { + wp_enqueue_script( 'edd-admin-downloads' ); } - if($hook == $edd_settings_page) { - wp_enqueue_style('colorbox', EDD_PLUGIN_URL . 'includes/css/colorbox.css'); - wp_enqueue_script('colorbox', EDD_PLUGIN_URL . 'includes/js/jquery.colorbox-min.js', array('jquery'), '1.3.19.3'); + + // Upgrades Page + if ( in_array( $hook, array( 'edd-admin-upgrades', 'download_page_edd-tools' ) ) ) { + wp_enqueue_script( 'edd-admin-tools-export' ); + wp_enqueue_script( 'edd-admin-upgrades' ); } - wp_enqueue_script('media-upload'); - wp_enqueue_script('thickbox'); - wp_enqueue_script('edd-admin-scripts', EDD_PLUGIN_URL . 'includes/js/admin-scripts.js'); - wp_localize_script('edd-admin-scripts', 'edd_vars', array( - 'post_id' => isset($post->ID) ? $post->ID : null, - 'add_new_download' => __('Add New Download', 'edd'), // thickbox title - 'use_this_file' => __('Use This File','edd'), // "use this file" button - 'quick_edit_warning'=> __('Sorry, not available for variable priced products.', 'edd'), - 'delete_payment' => __('Are you sure you wish to delete this payment?', 'edd'), - 'one_price_min' => __('You must have at least one price', 'edd'), - 'one_file_min' => __('You must have at least one file', 'edd'), - 'one_field_min' => __('You must have at least one field', 'edd') - )); - wp_enqueue_style('thickbox'); - wp_enqueue_style('jquery-ui-css', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css', false, '1.8', 'all'); - wp_enqueue_style('edd-admin', EDD_PLUGIN_URL . 'includes/css/edd-admin.css'); + +} +add_action( 'admin_enqueue_scripts', 'edd_enqueue_admin_scripts' ); + +/** + * Enqueue admin area styling. + * + * Always enqueue the menu styling. Only enqueue others on EDD pages. + * + * @since 3.0 + */ +function edd_enqueue_admin_styles( $hook = '' ) { + + // Always enqueue the admin menu CSS + wp_enqueue_style( 'edd-admin-menu' ); + + // Bail if not an EDD admin page + if ( ! edd_should_load_admin_scripts( $hook ) ) { + return; + } + + // Styles to enqueue (in priority order) + $styles = array( + 'jquery-chosen', + 'thickbox', + 'wp-jquery-ui-dialog', + 'wp-color-picker', + 'edd-admin', + 'edd-admin-chosen', + 'edd-admin-datepicker' + ); + + // Loop through and enqueue the scripts + foreach ( $styles as $style ) { + wp_enqueue_style( $style ); + } +} +add_action( 'admin_enqueue_scripts', 'edd_enqueue_admin_styles' ); + +/** + * Localize all admin scripts + * + * @since 3.0 + */ +function edd_localize_admin_scripts() { + $currency = edd_get_currency(); + + // Customize the currency on a few individual pages. + if ( function_exists( 'edd_is_admin_page' ) ) { + if ( edd_is_admin_page( 'reports' ) ) { + /* + * For reports, use the currency currently being filtered. + */ + $currency_filter = \EDD\Reports\get_filter_value( 'currencies' ); + if ( ! empty( $currency_filter ) && array_key_exists( strtoupper( $currency_filter ), edd_get_currencies() ) ) { + $currency = strtoupper( $currency_filter ); + } + } elseif ( edd_is_admin_page( 'payments' ) && ! empty( $_GET['id'] ) ) { + /* + * For orders & refunds, use the currency of the current order. + */ + $order = edd_get_order( absint( $_GET['id'] ) ); + if ( $order instanceof \EDD\Orders\Order ) { + $currency = $order->currency; + } + } + } + + $edd_currency = new EDD\Currency\Currency( $currency ); + + // Admin scripts + wp_localize_script( 'edd-admin-scripts', 'edd_vars', array( + 'post_id' => get_the_ID(), + 'edd_version' => edd_admin_get_script_version(), + 'currency' => $edd_currency->code, + 'currency_sign' => $edd_currency->symbol, + 'currency_pos' => $edd_currency->position, + 'currency_decimals' => $edd_currency->number_decimals, + 'decimal_separator' => $edd_currency->decimal_separator, + 'thousands_separator' => $edd_currency->thousands_separator, + 'date_picker_format' => edd_get_date_picker_format( 'js' ), + 'add_new_download' => __( 'Add New Download', 'easy-digital-downloads' ), + 'use_this_file' => __( 'Use This File', 'easy-digital-downloads' ), + 'quick_edit_warning' => __( 'Sorry, not available for variable priced products.', 'easy-digital-downloads' ), + 'delete_order_item' => __( 'Are you sure you want to delete this item?', 'easy-digital-downloads' ), + 'delete_order_adjustment' => __( 'Are you sure you want to delete this adjustment?', 'easy-digital-downloads' ), + 'delete_note' => __( 'Are you sure you want to delete this note?', 'easy-digital-downloads' ), + 'delete_tax_rate' => __( 'Are you sure you want to delete this tax rate?', 'easy-digital-downloads' ), + 'revoke_api_key' => __( 'Are you sure you want to revoke this API key?', 'easy-digital-downloads' ), + 'regenerate_api_key' => __( 'Are you sure you want to regenerate this API key?', 'easy-digital-downloads' ), + 'resend_receipt' => __( 'Are you sure you want to resend the purchase receipt?', 'easy-digital-downloads' ), + 'disconnect_customer' => __( 'Are you sure you want to disconnect the WordPress user from this customer record?', 'easy-digital-downloads' ), + 'copy_download_link_text' => __( 'Copy these links to your clipboard and give them to your customer', 'easy-digital-downloads' ), + /* translators: singular download label */ + 'delete_payment_download' => sprintf( __( 'Are you sure you want to delete this %s?', 'easy-digital-downloads' ), edd_get_label_singular() ), + /* translators: plural download label */ + 'type_to_search' => sprintf( __( 'Type to search %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + /* translators: singular download label */ + 'one_option' => sprintf( __( 'Choose a %s', 'easy-digital-downloads' ), edd_get_label_singular() ), + /* translators: plural download label */ + 'one_or_more_option' => sprintf( __( 'Choose one or more %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + 'one_price_min' => __( 'You must have at least one price', 'easy-digital-downloads' ), + 'one_field_min' => __( 'You must have at least one field', 'easy-digital-downloads' ), + 'one_download_min' => __( 'Payments must contain at least one item', 'easy-digital-downloads' ), + 'no_results_text' => __( 'No match for:', 'easy-digital-downloads'), + 'numeric_item_price' => __( 'Item price must be numeric', 'easy-digital-downloads' ), + 'numeric_item_tax' => __( 'Item tax must be numeric', 'easy-digital-downloads' ), + 'numeric_quantity' => __( 'Quantity must be numeric', 'easy-digital-downloads' ), + 'remove_text' => __( 'Remove', 'easy-digital-downloads' ), + 'batch_export_no_class' => __( 'You must choose a method.', 'easy-digital-downloads' ), + 'batch_export_no_reqs' => __( 'Required fields not completed.', 'easy-digital-downloads' ), + 'reset_stats_warn' => __( 'Are you sure you want to reset your store? This process is not reversible. Please be sure you have a recent backup.', 'easy-digital-downloads' ), + 'unsupported_browser' => __( 'We are sorry but your browser is not compatible with this kind of file upload. Please upgrade your browser.', 'easy-digital-downloads' ), + 'show_advanced_settings' => __( 'Show advanced settings', 'easy-digital-downloads' ), + 'hide_advanced_settings' => __( 'Hide advanced settings', 'easy-digital-downloads' ), + 'no_downloads_error' => __( 'There are no downloads attached to this payment', 'easy-digital-downloads' ), + 'wait' => __( 'Please wait …', 'easy-digital-downloads' ), + 'test_email_save_changes' => __( 'You must save your changes to send the test email.', 'easy-digital-downloads' ), + + // Diaglog buttons. + 'confirm_dialog_text' => __( 'Confirm', 'easy-digital-downloads' ), + 'cancel_dialog_text' => __( 'Cancel', 'easy-digital-downloads' ), + + // Features. + 'quantities_enabled' => edd_item_quantities_enabled(), + 'taxes_enabled' => edd_use_taxes(), + 'taxes_included' => edd_use_taxes() && edd_prices_include_tax(), + 'new_media_ui' => edd_apply_filters_deprecated( 'edd_use_35_media_ui', array( 1 ), '3.1.1', false, __( 'The edd_use_35_media_ui filter is no longer supported.', 'easy-digital-downloads' ) ), + + // REST based items. + 'restBase' => rest_url( \EDD\API\v3\Endpoint::$namespace ), + 'restNonce' => wp_create_nonce( 'wp_rest' ), + ) ); + + wp_localize_script( 'edd-admin-upgrades', 'edd_admin_upgrade_vars', array( + 'migration_complete' => esc_html__( 'Migration complete', 'easy-digital-downloads' ) + ) ); } -add_action('admin_enqueue_scripts', 'edd_load_admin_scripts', 100); +add_action( 'admin_enqueue_scripts', 'edd_localize_admin_scripts' ); +/** + * Add `defer` to the AlpineJS script tag. + */ +add_filter( 'script_loader_tag', function( $url ) { + if ( false !== strpos( $url, EDD_PLUGIN_URL . 'assets/js/alpine.min.js' ) ) { + $url = str_replace( ' src', ' defer src', $url ); + } + + return $url; +} ); /** * Admin Downloads Icon * * Echoes the CSS for the downloads post type icon. * - * @access private - * @since 1.0 - * @return void + * @since 1.0 + * @since 2.6.11 Removed globals and CSS for custom icon */ - function edd_admin_downloads_icon() { - global $post_type; - $icon_url = EDD_PLUGIN_URL . 'includes/images/edd-icon.png'; + + $images_url = EDD_PLUGIN_URL . 'assets/images/'; + $menu_icon = '\f316'; + $icon_cpt_url = $images_url . 'edd-cpt.png'; + $icon_cpt_2x_url = $images_url . 'edd-cpt-2x.png'; ?> - + + + '', - 'text' => __('Purchase', 'edd'), - 'style' => 'button', - 'color' => 'blue', - 'class' => '' - ), $atts ) - ); - - $download = edd_get_download($id); - - if($download) { - return edd_get_purchase_link($download->ID, $text, $style, $color, $class); - } -} -add_shortcode('purchase_link', 'edd_download_shortcode'); - - -/** - * Download History Shortcode - * - * Displays a user's download history. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_download_history() { - - if(is_user_logged_in()) { - ob_start(); - edd_get_template_part( 'history', 'downloads' ); - return ob_get_clean(); - } -} -add_shortcode('download_history', 'edd_download_history'); - -/** - * Purchase History Shortcode - * - * Displays a user's purchsae history. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_purchase_history() { - if(is_user_logged_in()) { - ob_start(); - edd_get_template_part( 'history', 'purchases' ); - return ob_get_clean(); - } -} -add_shortcode('purchase_history', 'edd_purchase_history'); - - -/** - * Checkout Form Shortcode - * - * Show the checkout form. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_checkout_form_shortcode($atts, $content = null) { - return edd_checkout_form(); -} -add_shortcode('download_checkout', 'edd_checkout_form_shortcode'); - - -/** - * Download Cart Shortcode - * - * Show the shopping cart. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_cart_shortcode($atts, $content = null) { - return edd_shopping_cart(); -} -add_shortcode('download_cart', 'edd_cart_shortcode'); - - -/** - * Login Shortcode - * - * Shows the login form. - * - * @access public - * @since 1.0 - * @return string -*/ - -function edd_login_form_shortcode($atts, $content = null) { - - extract( shortcode_atts( array( - 'redirect' => '', - ), $atts ) - ); - return edd_login_form($redirect); -} -add_shortcode('edd_login', 'edd_login_form_shortcode'); - - -/** - * Discounts short code - * - * Displays a list of all active discounts - * - * @access public - * @since 1.0.8.2 - * @return string -*/ - -function edd_discounts_shortcode( $atts, $content = null ) { - - $discounts = edd_get_discounts(); - - if( ! $discounts && edd_has_active_discounts() ) - return; - - $discounts_list = '
      '; - - foreach( $discounts as $discount ) { - - if( edd_is_discount_valid( $discount['code'] ) ) { - - $discounts_list .= '
    • '; - - $discounts_list .= '' . $discount['name'] . ''; - $discounts_list .= ' - '; - $discounts_list .= '' . edd_format_discount_rate( $discount['type'], $discount['amount'] ) . ''; - - $discounts_list .= '
    • '; - - } - - } - - $discounts_list .= '
    '; - - return $discounts_list; - -} -add_shortcode('download_discounts', 'edd_discounts_shortcode'); - - - -/** - * Purchase Collection Shortcode - * - * Displays a collection purchase link for adding all - * items in a taxonomy term to the cart. - * - * @access public - * @since 1.0.6 - * @return string -*/ - -function edd_purchase_collection_shortcode($atts, $content = null) { - extract( shortcode_atts( array( - 'taxonomy' => '', - 'terms' => '', - 'link' => __('Purchase All Items', 'edd') - ), $atts ) - ); - - $link = is_null( $content ) ? $link : $content; - - return '' . $link . ''; -} -add_shortcode('purchase_collection', 'edd_purchase_collection_shortcode'); - - -/** - * Downloads Shortcode - * - * Incomplete short code for querying downloads. - * - * Contributor: Sunny Ratilal - * - * @access public - * @since 1.0.6 - * @return string -*/ - -function edd_downloads_query($atts, $content = null) { - - extract( shortcode_atts( array( - 'category' => '', - 'tags' => '', - 'relation' => 'OR', - 'number' => 10, - 'price' => 'yes', - 'excerpt' => 'yes', - 'full_content' => 'no', - 'buy_button' => 'yes', - 'columns' => 3, - 'thumbnails' => 'true', - 'orderby' => 'post_date', - 'order' => 'DESC' - ), $atts ) - ); - - $query = array( - 'post_type' => 'download', - 'posts_per_page' => absint($number), - 'orderby' => $orderby, - 'order' => $order - ); - - switch ( $orderby ) { - case 'price': - $orderby = 'meta_value'; - $query['meta_key'] = 'edd_price'; - $query['orderby'] = 'meta_value_num'; - break; - - case 'title': - $query['orderby'] = 'title'; - break; - - case 'id': - $query['orderby'] = 'ID'; - break; - - case 'random': - $query['orderby'] = 'rand'; - break; - - default: - $query['orderby'] = 'post_date'; - break; - } - - if ( $tags ) { - $query['download_tag'] = $tags; - } - if ( $category ) { - $query['download_category'] = $category; - } - - switch( intval( $columns ) ) : - - case 1: - $column_width = '100%'; break; - case 2: - $column_width = '50%'; break; - case 3: - $column_width = '33%'; break; - case 4: - $column_width = '25%'; break; - case 5: - $column_width = '20%'; break; - case 6: - $column_width = '16.6%'; break; - - endswitch; - - // allow the query to be manipulated by other plugins - $query = apply_filters('edd_downloads_query', $query); - - $downloads = new WP_Query( $query ); - if ( $downloads->have_posts() ) : - $i = 1; - ob_start(); ?> -
    - have_posts() ) : $downloads->the_post(); ?> -
    -
    - -
    -
    -
    - - -
    -
    - NULL, - ), $atts ) - ); - - - if( is_null( $id ) ) - $id = get_the_ID(); - - return edd_price( $id, false ); - -} -add_shortcode('edd_price', 'edd_download_price_shortcode'); \ No newline at end of file +ID : 0; + + $atts = shortcode_atts( array( + 'id' => $post_id, + 'price_id' => isset( $atts['price_id'] ) ? $atts['price_id'] : false, + 'sku' => '', + 'price' => '1', + 'direct' => '0', + 'text' => '', + 'style' => edd_get_option( 'button_style', 'button' ), + 'color' => edd_get_button_color_class(), + 'class' => 'edd-submit', + 'form_id' => '', + ), + $atts, 'purchase_link' ); + + // Override text only if not provided / empty + if ( ! $atts['text'] ) { + if( $atts['direct'] == '1' || $atts['direct'] == 'true' ) { + $atts['text'] = edd_get_option( 'buy_now_text', __( 'Buy Now', 'easy-digital-downloads' ) ); + } else { + $atts['text'] = edd_get_option( 'add_to_cart_text', __( 'Purchase', 'easy-digital-downloads' ) ); + } + } + + if( ! empty( $atts['sku'] ) ) { + + $download = edd_get_download_by( 'sku', $atts['sku'] ); + + if ( $download ) { + $atts['download_id'] = $download->ID; + } + + } elseif( isset( $atts['id'] ) ) { + + // Edd_get_purchase_link() expects the ID to be download_id since v1.3 + $atts['download_id'] = $atts['id']; + + $download = edd_get_download( $atts['download_id'] ); + + } + + if ( $download ) { + return edd_get_purchase_link( $atts ); + } +} +add_shortcode( 'purchase_link', 'edd_download_shortcode' ); + +/** + * Download History Shortcode + * + * Displays a user's download history. + * + * @since 1.0 + * @return string + */ +function edd_download_history() { + if ( is_user_logged_in() ) { + ob_start(); + + if( ! edd_user_pending_verification() ) { + + edd_get_template_part( 'history', 'downloads' ); + + } else { + + edd_get_template_part( 'account', 'pending' ); + + } + + return ob_get_clean(); + } +} +add_shortcode( 'download_history', 'edd_download_history' ); + +/** + * Purchase History Shortcode + * + * Displays a user's purchase history. + * + * @since 1.0 + * @return string + */ +function edd_purchase_history() { + ob_start(); + + if( ! edd_user_pending_verification() ) { + + edd_get_template_part( 'history', 'purchases' ); + + } else { + + edd_get_template_part( 'account', 'pending' ); + + } + + return ob_get_clean(); +} +add_shortcode( 'purchase_history', 'edd_purchase_history' ); + +/** + * Checkout Form Shortcode + * + * Show the checkout form. + * + * @since 1.0 + * @param array $atts Shortcode attributes + * @param string $content + * @return string + */ +function edd_checkout_form_shortcode( $atts, $content = null ) { + return edd_checkout_form(); +} +add_shortcode( 'download_checkout', 'edd_checkout_form_shortcode' ); + +/** + * Download Cart Shortcode + * + * Show the shopping cart. + * + * @since 1.0 + * @param array $atts Shortcode attributes + * @param string $content + * @return string + */ +function edd_cart_shortcode( $atts, $content = null ) { + return edd_shopping_cart(); +} +add_shortcode( 'download_cart', 'edd_cart_shortcode' ); + +/** + * Login Shortcode + * + * Shows a login form allowing users to users to log in. This function simply + * calls the edd_login_form function to display the login form. + * + * @since 1.0 + * @param array $atts Shortcode attributes + * @param string $content + * @uses edd_login_form() + * @return string + */ +function edd_login_form_shortcode( $atts, $content = null ) { + $redirect = ''; + + extract( shortcode_atts( array( + 'redirect' => $redirect + ), $atts, 'edd_login' ) ); + + if ( empty( $redirect ) ) { + $login_redirect_page = edd_get_option( 'login_redirect_page', '' ); + + if ( ! empty( $login_redirect_page ) ) { + $redirect = get_permalink( $login_redirect_page ); + } + } + + if ( empty( $redirect ) ) { + $purchase_history = edd_get_option( 'purchase_history_page', 0 ); + + if ( ! empty( $purchase_history ) ) { + $redirect = get_permalink( $purchase_history ); + } + } + + if ( empty( $redirect ) ) { + $redirect = home_url(); + } + + return edd_login_form( $redirect ); +} +add_shortcode( 'edd_login', 'edd_login_form_shortcode' ); + +/** + * Register Shortcode + * + * Shows a registration form allowing users to users to register for the site + * + * @since 2.0 + * @param array $atts Shortcode attributes + * @param string $content + * @uses edd_register_form() + * @return string + */ +function edd_register_form_shortcode( $atts, $content = null ) { + $redirect = home_url(); + $purchase_history = edd_get_option( 'purchase_history_page', 0 ); + + if ( ! empty( $purchase_history ) ) { + $redirect = get_permalink( $purchase_history ); + } + + extract( shortcode_atts( array( + 'redirect' => $redirect + ), $atts, 'edd_register' ) ); + + return edd_register_form( $redirect ); +} +add_shortcode( 'edd_register', 'edd_register_form_shortcode' ); + +/** + * Discounts shortcode + * + * Displays a list of all the active discounts. The active discounts can be configured + * from the Discount Codes admin screen. + * + * @since 1.0.8.2 + * @param array $atts Shortcode attributes + * @param string $content + * @uses edd_get_discounts() + * @return string $discounts_lists List of all the active discount codes + */ +function edd_discounts_shortcode( $atts, $content = null ) { + $discounts = edd_get_discounts(); + + $discounts_list = '
      '; + + if ( ! empty( $discounts ) && edd_has_active_discounts() ) { + + foreach ( $discounts as $discount ) { + + if ( edd_is_discount_active( $discount->id ) ) { + $discounts_list .= '
    • '; + $discounts_list .= '' . edd_get_discount_code( $discount->id ) . ''; + $discounts_list .= ' - '; + $discounts_list .= '' . edd_format_discount_rate( edd_get_discount_type( $discount->id ), edd_get_discount_amount( $discount->id ) ) . ''; + $discounts_list .= '
    • '; + } + } + + } else { + $discounts_list .= '
    • ' . __( 'No discounts found', 'easy-digital-downloads' ) . '
    • '; + } + + $discounts_list .= '
    '; + + return $discounts_list; +} +add_shortcode( 'download_discounts', 'edd_discounts_shortcode' ); + +/** + * Purchase Collection Shortcode + * + * Displays a collection purchase link for adding all items in a taxonomy term + * to the cart. + * + * @since 1.0.6 + * @param array $atts Shortcode attributes + * @param string $content + * @return string + */ +function edd_purchase_collection_shortcode( $atts, $content = null ) { + extract( shortcode_atts( array( + 'taxonomy' => '', + 'terms' => '', + 'text' => __( 'Purchase All Items', 'easy-digital-downloads' ), + 'style' => edd_get_option( 'button_style', 'button' ), + 'color' => edd_get_button_color_class(), + 'class' => 'edd-submit', + ), $atts, 'purchase_collection' ) ); + + $button_display = implode( ' ', array_filter( array( $style, $color, $class ) ) ); + + return '' . esc_html( $text ) . ''; +} +add_shortcode( 'purchase_collection', 'edd_purchase_collection_shortcode' ); + +/** + * Downloads Shortcode + * + * This shortcodes uses the WordPress Query API to get downloads with the + * arguments specified when using the shortcode. A list of the arguments + * can be found from the EDD Documentation. The shortcode will take all the + * parameters and display the downloads queried in a valid HTML
    tags. + * + * @since 1.0.6 + * @internal Incomplete shortcode + * @param array $atts Shortcode attributes + * @param string $content + * @return string $display Output generated from the downloads queried + */ +function edd_downloads_query( $atts, $content = null ) { + $atts = shortcode_atts( array( + 'category' => '', + 'exclude_category' => '', + 'tags' => '', + 'exclude_tags' => '', + 'author' => false, + 'relation' => 'OR', + 'number' => 9, + 'price' => 'no', + 'excerpt' => 'yes', + 'full_content' => 'no', + 'buy_button' => 'yes', + 'columns' => 3, + 'thumbnails' => 'true', + 'orderby' => 'post_date', + 'order' => 'DESC', + 'ids' => '', + 'class' => '', + 'pagination' => 'true', + ), $atts, 'downloads' ); + + $query = array( + 'post_type' => 'download', + 'orderby' => $atts['orderby'], + 'order' => $atts['order'] + ); + + if ( filter_var( $atts['pagination'], FILTER_VALIDATE_BOOLEAN ) || ( ! filter_var( $atts['pagination'], FILTER_VALIDATE_BOOLEAN ) && $atts[ 'number' ] ) ) { + + $query['posts_per_page'] = (int) $atts['number']; + + if ( $query['posts_per_page'] < 0 ) { + $query['posts_per_page'] = abs( $query['posts_per_page'] ); + } + } else { + $query['nopaging'] = true; + } + + if( 'random' == $atts['orderby'] ) { + $atts['pagination'] = false; + } + + switch ( $atts['orderby'] ) { + case 'price': + $atts['orderby'] = 'meta_value'; + $query['meta_key'] = 'edd_price'; + $query['orderby'] = 'meta_value_num'; + break; + + case 'sales': + $atts['orderby'] = 'meta_value'; + $query['meta_key'] = '_edd_download_sales'; + $query['orderby'] = 'meta_value_num'; + break; + + case 'earnings': + $atts['orderby'] = 'meta_value'; + $query['meta_key'] = '_edd_download_earnings'; + $query['orderby'] = 'meta_value_num'; + break; + + case 'title': + $query['orderby'] = 'title'; + break; + + case 'id': + $query['orderby'] = 'ID'; + break; + + case 'random': + $query['orderby'] = 'rand'; + break; + + case 'post__in': + $query['orderby'] = 'post__in'; + break; + + default: + $query['orderby'] = 'post_date'; + break; + } + + if ( $atts['tags'] || $atts['category'] || $atts['exclude_category'] || $atts['exclude_tags'] ) { + + $query['tax_query'] = array( + 'relation' => $atts['relation'] + ); + + if ( $atts['tags'] ) { + + $tag_list = explode( ',', $atts['tags'] ); + + foreach( $tag_list as $tag ) { + + $t_id = (int) $tag; + $is_id = is_int( $t_id ) && ! empty( $t_id ); + + if( $is_id ) { + + $term_id = $tag; + + } else { + + $term = get_term_by( 'slug', $tag, 'download_tag' ); + + if( ! $term ) { + continue; + } + + $term_id = $term->term_id; + } + + $query['tax_query'][] = array( + 'taxonomy' => 'download_tag', + 'field' => 'term_id', + 'terms' => $term_id + ); + } + + } + + if ( $atts['category'] ) { + + $categories = explode( ',', $atts['category'] ); + + foreach( $categories as $category ) { + + $t_id = (int) $category; + $is_id = is_int( $t_id ) && ! empty( $t_id ); + + if( $is_id ) { + + $term_id = $category; + + } else { + + $term = get_term_by( 'slug', $category, 'download_category' ); + + if( ! $term ) { + continue; + } + + $term_id = $term->term_id; + + } + + $query['tax_query'][] = array( + 'taxonomy' => 'download_category', + 'field' => 'term_id', + 'terms' => $term_id, + ); + + } + + } + + if ( $atts['exclude_category'] ) { + + $categories = explode( ',', $atts['exclude_category'] ); + + foreach( $categories as $category ) { + + $t_id = (int) $category; + $is_id = is_int( $t_id ) && ! empty( $t_id ); + + if( $is_id ) { + + $term_id = $category; + + } else { + + $term = get_term_by( 'slug', $category, 'download_category' ); + + if( ! $term ) { + continue; + } + + $term_id = $term->term_id; + } + + $query['tax_query'][] = array( + 'taxonomy' => 'download_category', + 'field' => 'term_id', + 'terms' => $term_id, + 'operator' => 'NOT IN' + ); + } + + } + + if ( $atts['exclude_tags'] ) { + + $tag_list = explode( ',', $atts['exclude_tags'] ); + + foreach( $tag_list as $tag ) { + + $t_id = (int) $tag; + $is_id = is_int( $t_id ) && ! empty( $t_id ); + + if( $is_id ) { + + $term_id = $tag; + + } else { + + $term = get_term_by( 'slug', $tag, 'download_tag' ); + + if( ! $term ) { + continue; + } + + $term_id = $term->term_id; + } + + $query['tax_query'][] = array( + 'taxonomy' => 'download_tag', + 'field' => 'term_id', + 'terms' => $term_id, + 'operator' => 'NOT IN' + ); + + } + + } + } + + if ( $atts['exclude_tags'] || $atts['exclude_category'] ) { + $query['tax_query']['relation'] = 'AND'; + } + + if ( $atts['author'] ) { + $authors = explode( ',', $atts['author'] ); + if ( ! empty( $authors ) ) { + $author_ids = array(); + $author_names = array(); + + foreach ( $authors as $author ) { + if ( is_numeric( $author ) ) { + $author_ids[] = $author; + } else { + $user = get_user_by( 'login', $author ); + if ( $user ) { + $author_ids[] = $user->ID; + } + } + } + + if ( ! empty( $author_ids ) ) { + $author_ids = array_unique( array_map( 'absint', $author_ids ) ); + $query['author'] = implode( ',', $author_ids ); + } + } + } + + if( ! empty( $atts['ids'] ) ) { + $query['post__in'] = explode( ',', $atts['ids'] ); + } + + if ( get_query_var( 'paged' ) ) { + $query['paged'] = get_query_var('paged'); + } else if ( get_query_var( 'page' ) ) { + $query['paged'] = get_query_var( 'page' ); + } else { + $query['paged'] = 1; + } + + // Allow the query to be manipulated by other plugins + $query = apply_filters( 'edd_downloads_query', $query, $atts ); + + $downloads = new WP_Query( $query ); + + do_action( 'edd_downloads_list_before', $atts ); + + // Ensure buttons are not appended to content. + remove_filter( 'the_content', 'edd_after_download_content' ); + + ob_start(); + + + if ( $downloads->have_posts() ) : + $i = 1; + $columns_class = array( 'edd_download_columns_' . $atts['columns'] ); + $custom_classes = array_filter( explode( ',', $atts['class'] ) ); + $wrapper_classes = array_unique( array_merge( $columns_class, $custom_classes ) ); + $wrapper_classes = implode( ' ', $wrapper_classes ); + ?> + +
    + + + + have_posts() ) : $downloads->the_post(); ?> + + + + + + + +
    + + null, + 'price_id' => false, + ), $atts, 'edd_price' ) ); + + if ( is_null( $id ) ) { + $id = get_the_ID(); + } + + return edd_price( $id, false, $price_id ); +} +add_shortcode( 'edd_price', 'edd_download_price_shortcode' ); + +/** + * Receipt Shortcode + * + * Shows an order receipt. + * + * @since 1.4 + * @param array $atts Shortcode attributes + * @param string $content + * @return string + */ +function edd_receipt_shortcode( $atts, $content = null ) { + global $edd_receipt_args; + + $edd_receipt_args = shortcode_atts( array( + 'error' => __( 'Sorry, trouble retrieving order receipt.', 'easy-digital-downloads' ), + 'price' => true, + 'discount' => true, + 'products' => true, + 'date' => true, + 'notes' => true, + 'payment_key' => false, + 'payment_method' => true, + 'payment_id' => true, + ), $atts, 'edd_receipt' ); + + $session = edd_get_purchase_session(); + + if ( isset( $_GET['payment_key'] ) ) { + $payment_key = urldecode( $_GET['payment_key'] ); + } elseif ( ! empty( $_GET['order'] ) && ! empty( $_GET['id'] ) ) { + $payment_key = edd_get_payment_key( absint( $_GET['id'] ) ); + } elseif ( $session ) { + $payment_key = $session['purchase_key']; + } elseif ( $edd_receipt_args['payment_key'] ) { + $payment_key = $edd_receipt_args['payment_key']; + } + + // No key found + if ( ! isset( $payment_key ) ) { + return '

    ' . $edd_receipt_args['error'] . '

    '; + } + + $order = edd_get_order_by( 'payment_key', $payment_key ); + $user_can_view = edd_can_view_receipt( $order ); + + // Key was provided, but user is logged out. Offer them the ability to login and view the receipt + if ( ! $user_can_view && ! empty( $payment_key ) && ! is_user_logged_in() && ! edd_is_guest_payment( $order ) ) { + global $edd_login_redirect; + $edd_login_redirect = edd_get_receipt_page_uri( $order->id ); + + ob_start(); + + echo '

    ' . __( 'You must be logged in to view this payment receipt.', 'easy-digital-downloads' ) . '

    '; + edd_get_template_part( 'shortcode', 'login' ); + + $login_form = ob_get_clean(); + + return $login_form; + } + + $user_can_view = apply_filters( 'edd_user_can_view_receipt', $user_can_view, $edd_receipt_args ); + + // If this was a guest checkout and the purchase session is empty, output a relevant error message + if ( empty( $session ) && ! is_user_logged_in() && ! $user_can_view ) { + return '

    ' . apply_filters( 'edd_receipt_guest_error_message', __( 'Receipt could not be retrieved, your purchase session has expired.', 'easy-digital-downloads' ) ) . '

    '; + } + + /* + * Check if the user has permission to view the receipt + * + * If user is logged in, user ID is compared to user ID of ID stored in payment meta + * + * Or if user is logged out and purchase was made as a guest, the purchase session is checked for + * + * Or if user is logged in and the user can view sensitive shop data + */ + + if ( ! $user_can_view ) { + return '

    ' . $edd_receipt_args['error'] . '

    '; + } + + ob_start(); + + edd_get_template_part( 'shortcode', 'receipt' ); + + $display = ob_get_clean(); + + return $display; +} +add_shortcode( 'edd_receipt', 'edd_receipt_shortcode' ); + +/** + * Render the profile editor shortcode. + * + * @since 1.4 + * + * @param null $atts Unused parameter. + * @param null $content Unused parameter. + * + * @return string Shortcode template. + */ +function edd_profile_editor_shortcode( $atts = null, $content = null ) { + ob_start(); + + if ( ! edd_user_pending_verification() ) { + edd_get_template_part( 'shortcode', 'profile-editor' ); + } else { + edd_get_template_part( 'account', 'pending' ); + } + + $display = ob_get_clean(); + + return $display; +} +add_shortcode( 'edd_profile_editor', 'edd_profile_editor_shortcode' ); + +/** + * Process profile updates. + * + * @since 1.4 + * @since 3.0 Updated to use new custom tables. + * + * @param array $data Data sent from the profile editor. + * @return bool False on error. + */ +function edd_process_profile_editor_updates( $data ) { + + // Profile field change request. + if ( empty( $data['edd_profile_editor_submit'] ) && ! is_user_logged_in() ) { + return false; + } + + // Pending users can't edit their profile. + if ( edd_user_pending_verification() ) { + return false; + } + + // Verify nonce. + if ( empty( $data['edd_profile_editor_nonce'] ) || ! wp_verify_nonce( $data['edd_profile_editor_nonce'], 'edd-profile-editor-nonce' ) ) { + return false; + } + + $user_id = get_current_user_id(); + $old_user_data = get_userdata( $user_id ); + + // Fetch customer record. + $customer = edd_get_customer_by( 'user_id', $user_id ); + if ( ! empty( $customer->user_id ) && $customer->user_id != $user_id ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + edd_set_error( 'customer_mismatch', __( 'Your profile could not be updated. Please contact a site administrator.', 'easy-digital-downloads' ) ); + } + + $display_name = isset( $data['edd_display_name'] ) ? sanitize_text_field( $data['edd_display_name'] ) : $old_user_data->display_name; + $first_name = isset( $data['edd_first_name'] ) ? sanitize_text_field( $data['edd_first_name'] ) : $old_user_data->first_name; + $last_name = isset( $data['edd_last_name'] ) ? sanitize_text_field( $data['edd_last_name'] ) : $old_user_data->last_name; + $email = isset( $data['edd_email'] ) ? sanitize_email( $data['edd_email'] ) : $old_user_data->user_email; + $line1 = isset( $data['edd_address_line1'] ) ? sanitize_text_field( $data['edd_address_line1'] ) : ''; + $line2 = isset( $data['edd_address_line2'] ) ? sanitize_text_field( $data['edd_address_line2'] ) : ''; + $city = isset( $data['edd_address_city'] ) ? sanitize_text_field( $data['edd_address_city'] ) : ''; + $state = isset( $data['edd_address_state'] ) ? sanitize_text_field( $data['edd_address_state'] ) : ''; + $zip = isset( $data['edd_address_zip'] ) ? sanitize_text_field( $data['edd_address_zip'] ) : ''; + $country = isset( $data['edd_address_country'] ) ? sanitize_text_field( $data['edd_address_country'] ) : ''; + + $userdata = array( + 'ID' => $user_id, + 'first_name' => $first_name, + 'last_name' => $last_name, + 'display_name' => $display_name, + 'user_email' => $email, + ); + + $address = array( + 'line1' => $line1, + 'line2' => $line2, + 'city' => $city, + 'state' => $state, + 'zip' => $zip, + 'country' => $country, + ); + + do_action( 'edd_pre_update_user_profile', $user_id, $userdata ); + + // New password + if ( ! empty( $data['edd_new_user_pass1'] ) ) { + if ( $data['edd_new_user_pass1'] !== $data['edd_new_user_pass2'] ) { + edd_set_error( 'password_mismatch', __( 'The passwords you entered do not match. Please try again.', 'easy-digital-downloads' ) ); + } else { + $userdata['user_pass'] = $data['edd_new_user_pass1']; + } + } + + // Make sure the new email doesn't belong to another user. + if ( $email !== $old_user_data->user_email ) { + + // Make sure the new email is valid. + if ( ! is_email( $email ) ) { + edd_set_error( 'email_invalid', __( 'The email you entered is invalid. Please enter a valid email.', 'easy-digital-downloads' ) ); + } + + $customers = edd_get_customers( + array( + 'email' => $email, + 'user_id__not_in' => array( $user_id ), + ) + ); + // Make sure the new email doesn't belong to another user. + if ( email_exists( $email ) || ! empty( $customers ) ) { + edd_set_error( 'email_exists', __( 'This email address is not available.', 'easy-digital-downloads' ) ); + } + } + + // Check for errors. + $errors = edd_get_errors(); + + // Send back to the profile editor if there are errors. + if ( ! empty( $errors ) ) { + if ( ! empty( $data['edd_redirect'] ) ) { + edd_redirect( $data['edd_redirect'] ); + } + return false; + } + + // Update user. + $updated = wp_update_user( $userdata ); + + // If the current user does not have an associated customer record, create one so that all of the customer's data is stored. + if ( ! $customer && $updated ) { + $customer_id = edd_add_customer( + array( + 'user_id' => $updated, + 'email' => $email, + ) + ); + + $customer = edd_get_customer_by( 'id', $customer_id ); + } + + // Try to update customer data. + if ( $customer ) { + + // Update the primary address. + $customer_address_id = edd_get_customer_addresses( array( + 'customer_id' => $customer->id, + 'type' => 'billing', + 'is_primary' => 1, + 'number' => 1, + 'fields' => 'ids', + ) ); + + // Try updating the address if it exists. + if ( ! empty( $customer_address_id ) ) { + $customer_address_id = $customer_address_id[0]; + + edd_update_customer_address( $customer_address_id, array( + 'name' => stripslashes( $first_name . ' ' . $last_name ), + 'address' => $address['line1'], + 'address2' => $address['line2'], + 'city' => $address['city'], + 'country' => $address['country'], + 'region' => $address['state'], + 'postal_code' => $address['zip'], + 'country' => $address['country'] + ) ); + + // Add a customer address. + } else { + edd_maybe_add_customer_address( + $customer->id, + array( + 'name' => stripslashes( $first_name . ' ' . $last_name ), + 'type' => 'billing', + 'address' => $address['line1'], + 'address2' => $address['line2'], + 'city' => $address['city'], + 'country' => $address['country'], + 'region' => $address['state'], + 'postal_code' => $address['zip'], + 'country' => $address['country'], + 'is_primary' => true, + ) + ); + } + + if ( $customer->email === $email || ( is_array( $customer->emails ) && in_array( $email, $customer->emails ) ) ) { + $customer->set_primary_email( $email ); + } + + $update_args = array( + 'name' => stripslashes( $first_name . ' ' . $last_name ), + ); + + $customer->update( $update_args ); + } + + if ( $updated ) { + do_action( 'edd_user_profile_updated', $user_id, $userdata ); + + edd_redirect( add_query_arg( 'updated', 'true', $data['edd_redirect'] ) ); + } +} +add_action( 'edd_edit_user_profile', 'edd_process_profile_editor_updates' ); + +/** + * Process the 'remove' URL on the profile editor when customers wish to remove an email address. + * + * @since 2.6 + * @param array $data The array of data passed from the profile editor. + * @return void + */ +function edd_process_profile_editor_remove_email( $data ) { + if ( ! is_user_logged_in() ) { + return; + } + + // Pending users can't edit their profile + if ( edd_user_pending_verification() ) { + return; + } + + // Nonce security + if ( ! wp_verify_nonce( $data['_wpnonce'], 'edd-remove-customer-email' ) ) { + return; + } + + if ( empty( $data['email'] ) || ! is_email( $data['email'] ) ) { + return; + } + + $user_id = get_current_user_id(); + $customer = new EDD_Customer( $user_id, true ); + + if ( $customer->user_id == $user_id && $customer->remove_email( $data['email'] ) ) { + + $url = add_query_arg( 'updated', true, $data['redirect'] ); + + $user = wp_get_current_user(); + $user_login = ! empty( $user->user_login ) ? $user->user_login : edd_get_bot_name(); + /* translators: 1. email address; 2. user who removed email. */ + $customer_note = sprintf( __( 'Email address %1$s removed by %2$s', 'easy-digital-downloads' ), sanitize_email( $data['email'] ), $user_login ); + $customer->add_note( $customer_note ); + + } else { + edd_set_error( 'profile-remove-email-failure', __( 'Error removing email address from profile. Please try again later.', 'easy-digital-downloads' ) ); + $url = $data['redirect']; + } + + edd_redirect( $url ); +} +add_action( 'edd_profile-remove-email', 'edd_process_profile_editor_remove_email' ); diff --git a/includes/tax-functions.php b/includes/tax-functions.php old mode 100644 new mode 100755 index a4abe2dafcb..a5bfaf3e09b --- a/includes/tax-functions.php +++ b/includes/tax-functions.php @@ -1,2 +1,551 @@ 9999, + 'type' => 'tax_rate', + 'orderby' => 'date_created', + 'order' => 'ASC', + ) ); + + if ( isset( $args['status'] ) && 'active' === $args['status'] ) { + remove_filter( 'edd_adjustments_query_clauses', 'edd_active_tax_rates_query_clauses' ); + } + + $adjustments->query( $r ); + + if ( OBJECT === $output ) { + return $adjustments->items; + } + + $rates = array(); + + if ( $adjustments->items ) { + foreach ( $adjustments->items as $tax_rate ) { + $rate = array( + 'id' => absint( $tax_rate->id ), + 'country' => esc_attr( $tax_rate->name ), + 'rate' => floatval( $tax_rate->amount ), + 'state' => '', + 'global' => '1', + 'status' => esc_attr( $tax_rate->status ), + 'scope' => esc_attr( $tax_rate->scope ), + ); + + if ( ! empty( $tax_rate->description ) ) { + $rate['state'] = esc_attr( $tax_rate->description ); + } + + if ( 'region' === $tax_rate->scope ) { + $rate['global'] = '0'; + } + + $rates[] = $rate; + } + } + + return (array) apply_filters( 'edd_get_tax_rates', $rates ); +} + +/** + * Query for and return array of tax rates counts, keyed by status. + * + * @since 3.0 + * + * @return array + */ +function edd_get_tax_rate_counts( $args = array() ) { + + // Parse arguments + $r = wp_parse_args( $args, array( + 'count' => true, + 'groupby' => 'status', + 'type' => 'tax_rate' + ) ); + + // Query for count. + $counts = new EDD\Database\Queries\Adjustment( $r ); + + // Format & return + return edd_format_counts( $counts, $r['groupby'] ); +} + +/** + * Add a WHERE clause to ensure only active tax rates are returned. + * + * @since 3.0 + * + * @param array $clauses Query clauses. + * @return array $clauses Updated query clauses. + */ +function edd_active_tax_rates_query_clauses( $clauses ) { + $date = \Carbon\Carbon::now( edd_get_timezone_id() )->toDateTimeString(); + + $clauses['where'] .= " + AND ( start_date < '{$date}' OR start_date IS NULL ) + AND ( end_date > '{$date}' OR end_date IS NULL ) + "; + + return $clauses; +} + +/** + * Get taxation rate. + * + * @since 1.3.3 + * @since 3.0 Refactored to work with custom tables and start and end dates. + * Renamed $state parameter to $region. + * Added $fallback parameter to only get rate for passed Country and Region. + * + * @param string $country Country. + * @param string $region Region. + * @param boolean $fallback Fall back to (in order): server $_POST data, the current Customer's + * address information, then your store's Business Country setting. + * Default true. + * + * @return float + */ +function edd_get_tax_rate( $country = '', $region = '', $fallback = true ) { + + // Get the address, to try to get the tax rate + $user_address = edd_get_customer_address(); + + $address_line_1 = ! empty( $_POST['card_address'] ) + ? sanitize_text_field( $_POST['card_address'] ) + : ''; + + $address_line_2 = ! empty( $_POST['card_address_2'] ) + ? sanitize_text_field( $_POST['card_address_2'] ) + : ''; + + $city = ! empty( $_POST['card_city'] ) + ? sanitize_text_field( $_POST['card_city'] ) + : ''; + + $zip = ! empty( $_POST['card_zip'] ) + ? sanitize_text_field( $_POST['card_zip'] ) + : ''; + + // Country + if ( empty( $country ) && true === $fallback ) { + if ( ! empty( $_POST['billing_country'] ) ) { + $country = $_POST['billing_country']; + } elseif ( is_user_logged_in() && ! empty( $user_address['country'] ) ) { + $country = $user_address['country']; + } + + $country = empty( $country ) + ? edd_get_shop_country() + : $country; + } + + // Region + if ( empty( $region ) && true === $fallback ) { + if ( ! empty( $_POST['state'] ) ) { + $region = $_POST['state']; + } elseif ( ! empty( $_POST['card_state'] ) ) { + $region = $_POST['card_state']; + } elseif ( is_user_logged_in() && ! empty( $user_address['state'] ) ) { + $region = $user_address['state']; + } + + $region = empty( $region ) + ? edd_get_shop_state() + : $region; + } + + $tax_rate = edd_get_tax_rate_by_location( + array( + 'country' => $country, + 'region' => $region, + ) + ); + + $rate = $tax_rate ? $tax_rate->amount : 0.00; + + // Convert to a number we can use + $rate = $rate / 100; + + /** + * Allow the tax rate to be filtered. + * + * @since 1.3.3 + * @since 3.0 Added entire customer address. + * + * @param float $rate Calculated tax rate. + * @param string $country Country. + * @param string $region Region. + * @param string $address_line_1 First line of address. + * @param string $address_line_2 Second line of address. + * @param string $city City. + * @param string $zip ZIP code. + */ + return apply_filters( 'edd_tax_rate', $rate, $country, $region, $address_line_1, $address_line_2, $city, $zip ); +} + +/** + * Retrieve a fully formatted tax rate + * + * @since 1.9 + * @param string $country The country to retrieve a rate for + * @param string $state The state to retrieve a rate for + * @return string Formatted rate + */ +function edd_get_formatted_tax_rate( $country = false, $state = false ) { + $rate = edd_get_tax_rate( $country, $state ); + $rate = round( $rate * 100, 4 ); + $formatted = $rate .= '%'; + + return apply_filters( 'edd_formatted_tax_rate', $formatted, $rate, $country, $state ); +} + +/** + * Calculate the taxed amount. + * + * @since 1.3.3 + * @since 3.0 Renamed $state parameter to $region. + * Added $fallback parameter. + * Added `$tax_rate` parameter. + * + * @param float $amount Amount. + * @param string $country Country. Default base country. + * @param string $region Region. Default base region. + * @param boolean $fallback Fall back to (in order): server $_POST data, the current Customer's + * address information, then your store's Business Country setting. + * Default true. + * @param null|float $tax_rate Tax rate to use for the calculataion. If `null`, the rate is retrieved using + * `edd_get_tax_rate()`. + * + * @return float $tax Taxed amount. + */ +function edd_calculate_tax( $amount = 0.00, $country = '', $region = '', $fallback = true, $tax_rate = null ) { + $rate = ( null === $tax_rate ) ? edd_get_tax_rate( $country, $region, $fallback ) : $tax_rate; + $tax = 0.00; + + if ( edd_use_taxes() && $amount > 0 ) { + + if ( edd_prices_include_tax() ) { + $pre_tax = ( $amount / ( 1 + $rate ) ); + $tax = $amount - $pre_tax; + } else { + $tax = $amount * $rate; + } + } + + /** + * Filter the taxed amount. + * + * @since 1.5.3 + * + * @param float $tax Taxed amount. + * @param float $rate Tax rate applied. + * @param string $country Country. + * @param string $region Region. + */ + return apply_filters( 'edd_taxed_amount', $tax, $rate, $country, $region ); +} + +/** + * Returns the formatted tax amount for the given year + * + * @since 1.3.3 + * @param $year int The year to retrieve taxes for, i.e. 2012 + * @uses edd_get_sales_tax_for_year() + * @return void + */ +function edd_sales_tax_for_year( $year = null ) { + echo edd_currency_filter( edd_format_amount( edd_get_sales_tax_for_year( $year ) ) ); +} + +/** + * Gets the sales tax for the given year + * + * @since 1.3.3 + * @param $year int The year to retrieve taxes for, i.e. 2012 + * @uses edd_get_payment_tax() + * @return float $tax Sales tax + */ +function edd_get_sales_tax_for_year( $year = null ) { + global $wpdb; + + // Start at zero + $tax = 0; + + if ( ! empty( $year ) ) { + $year = absint( $year ); + + $tax = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(tax) FROM {$wpdb->edd_orders} WHERE status IN('complete', 'revoked') AND YEAR(date_created) = %d", $year ) ); + } + + if ( ! $tax || is_null( $tax ) ) { + $tax = 0.00; + } + + // Filter & return + return (float) apply_filters( 'edd_get_sales_tax_for_year', $tax, $year ); +} + +/** + * Is the cart taxed? + * + * This used to include a check for local tax opt-in, but that was ripped out in v1.6, so this is just a wrapper now + * + * @since 1.5 + * @return bool + */ +function edd_is_cart_taxed() { + return edd_use_taxes(); +} + +/** + * Check if the individual product prices include tax + * + * @since 1.5 + * @return bool $include_tax + */ +function edd_prices_include_tax() { + $ret = ( edd_get_option( 'prices_include_tax', false ) === 'yes' && edd_use_taxes() ); + + return apply_filters( 'edd_prices_include_tax', $ret ); +} + +/** + * Checks whether the user has enabled display of taxes on the checkout + * + * @since 1.5 + * @return bool $include_tax + */ +function edd_prices_show_tax_on_checkout() { + $ret = ( edd_get_option( 'checkout_include_tax', false ) === 'yes' && edd_use_taxes() ); + + return apply_filters( 'edd_taxes_on_prices_on_checkout', $ret ); +} + +/** + * Check to see if we should show included taxes + * + * Some countries (notably in the EU) require included taxes to be displayed. + * + * @since 1.7 + * @author Daniel J Griffiths + * @return bool + */ +function edd_display_tax_rate() { + $ret = edd_use_taxes() && edd_get_option( 'display_tax_rate', false ); + + return apply_filters( 'edd_display_tax_rate', $ret ); +} + +/** + * Should we show address fields for taxation purposes? + * + * @since 1.y + * @return bool + */ +function edd_cart_needs_tax_address_fields() { + + if ( ! edd_is_cart_taxed() ) { + return false; + } + + return ! did_action( 'edd_after_cc_fields', 'edd_default_cc_address_fields' ); +} + +/** + * Is this Download excluded from tax? + * + * @since 1.9 + * @return bool + */ +function edd_download_is_tax_exclusive( $download_id = 0 ) { + $ret = (bool) get_post_meta( $download_id, '_edd_download_tax_exclusive', true ); + + return (Bool) apply_filters( 'edd_download_is_tax_exclusive', $ret, $download_id ); +} + +/** + * Gets the tax rate object from the database for a given country / region. + * Used in `edd_get_tax_rate`, `edd_build_order`, `edd_add_manual_order`. + * If a regional tax rate is found, it will be returned immediately, + * so rates with a scope of `country` may be overridden by a more specific rate. + * + * @param array $args { + * Country and, optionally, region to get the tax rate for. + * + * @type string $country Required - country to check. + * @type string $region Optional - check a specific region within the country. + * } + * @return \EDD\Database\Rows\Adjustment|false + * + * @since 3.0 + */ +function edd_get_tax_rate_by_location( $args ) { + + $rate = false; + $tax_rates = array(); + + // Fetch all the active country tax rates from the database. + // The region is not passed in deliberately in order to check for country-wide tax rates. + if ( ! empty( $args['country'] ) ) { + $tax_rates = edd_get_tax_rates( + array( + 'name' => $args['country'], + 'status' => 'active', + ), + OBJECT + ); + } + + if ( ! empty( $tax_rates ) ) { + foreach ( $tax_rates as $tax_rate ) { + + // Regional tax rate. + if ( ! empty( $args['region'] ) && ! empty( $tax_rate->description ) && 'region' === $tax_rate->scope ) { + if ( strtolower( $args['region'] ) !== strtolower( $tax_rate->description ) ) { + continue; + } + + // A tax rate matching the region/description was found, so return it. + return $tax_rate; + } elseif ( 'country' === $tax_rate->scope ) { + // Countrywide tax rate. + $rate = $tax_rate; + } + } + + if ( $rate ) { + return $rate; + } + } + + // No regional or country rate was found, so look for a global rate. + $global_rates = edd_get_tax_rates( + array( + 'name' => '', + 'scope' => 'global', + 'status' => 'active', + ), + OBJECT + ); + + return ! empty( $global_rates ) ? reset( $global_rates ) : $rate; +} + +/** + * Clears the tax rate cache prior to displaying the cart. + * This fixes potential issues with custom tax rates / rate filtering from after we added + * tax rate caching logic. + * + * @link https://github.com/easydigitaldownloads/easy-digital-downloads/pull/8509#issuecomment-926576698 + * + * @since 3.0 + */ +add_action( 'edd_before_checkout_cart', function () { + EDD()->cart->set_tax_rate( null ); +} ); + +/** + * Adds a tax rate to the database. + * If an active tax rate is found, it's demoted to inactive and the new one is added. + * + * @since 3.0.3 + * @param array $data The array of data to create the tax rate. + * @return int|false Returns the tax rate ID if one is added; otherwise false. + */ +function edd_add_tax_rate( $data = array() ) { + $data = wp_parse_args( + $data, + array( + 'name' => '', + 'description' => '', + 'status' => 'active', + ) + ); + + // The type and amount type for tax rates cannot be overridden. + $data['type'] = 'tax_rate'; + $data['amount_type'] = 'percent'; + + if ( empty( $data['scope'] ) ) { + $scope = 'country'; + if ( empty( $data['name'] ) && empty( $data['description'] ) ) { + $scope = 'global'; + } elseif ( ! empty( $data['name'] ) && ! empty( $data['description'] ) ) { + $scope = 'region'; + } + $data['scope'] = $scope; + } + + // Check if the tax rate exists. + $data_to_check = array( + 'type' => 'tax_rate', + 'fields' => 'ids', + 'status' => 'active', + 'name' => $data['name'], + 'description' => $data['description'], + 'scope' => $data['scope'], + ); + + // Query for potentially matching active rates. + $tax_rate_exists = edd_get_adjustments( $data_to_check ); + $tax_rate_id = edd_add_adjustment( $data ); + + // If the new tax rate was successfully added, set the old one to inactive. + if ( $tax_rate_id && ! empty( $tax_rate_exists ) ) { + edd_update_adjustment( $tax_rate_exists[0], array( 'status' => 'inactive' ) ); + } + + return $tax_rate_id; +} diff --git a/includes/template-actions.php b/includes/template-actions.php new file mode 100644 index 00000000000..68ac4c80774 --- /dev/null +++ b/includes/template-actions.php @@ -0,0 +1,34 @@ +' . esc_html__( 'You need to log in to edit your profile.', 'easy-digital-downloads' ) . '

    '; + echo edd_login_form(); // WPCS: XSS ok. +} +add_action( 'edd_profile_editor_logged_out', 'edd_profile_editor_logged_out' ); + +/** + * Output a message on the login form when a user is already logged in. + * + * This remains mainly for backwards compatibility. + * + * @since 2.8 + */ +function edd_login_form_logged_in() { + echo '

    ' . esc_html__( 'You are already logged in', 'easy-digital-downloads' ) . '

    '; +} +add_action( 'edd_login_form_logged_in', 'edd_login_form_logged_in' ); \ No newline at end of file diff --git a/includes/template-functions.php b/includes/template-functions.php index e3b58d7248b..b74e68f8d6a 100755 --- a/includes/template-functions.php +++ b/includes/template-functions.php @@ -2,30 +2,30 @@ /** * Template Functions * - * @package Easy Digital Downloads - * @subpackage Template Functions - * @copyright Copyright ( c ) 2012, Pippin Williamson + * @package EDD + * @subpackage Functions/Templates + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License - * @since 1.0 -*/ + * @since 1.0 + */ +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** * Append Purchase Link * * Automatically appends the purchase link to download content, if enabled. * - * @access private - * @since 1.0 - * @return string -*/ + * @since 1.0 + * @param int $download_id Download ID + * @return void + */ function edd_append_purchase_link( $download_id ) { - - if ( ! get_post_meta( $download_id, '_edd_hide_purchase_link', true ) ) { - echo edd_get_purchase_link( $download_id ); + if ( ! get_post_meta( $download_id, '_edd_hide_purchase_link', true ) ) { + echo edd_get_purchase_link( array( 'download_id' => $download_id ) ); } - } add_action( 'edd_after_download_content', 'edd_append_purchase_link' ); @@ -33,377 +33,582 @@ function edd_append_purchase_link( $download_id ) { /** * Get Purchase Link * - * Returns the purchase link. + * Builds a Purchase link for a specified download based on arguments passed. + * This function is used all over EDD to generate the Purchase or Add to Cart + * buttons. If no arguments are passed, the function uses the defaults that have + * been set by the plugin. The Purchase link is built for simple and variable + * pricing and filters are available throughout the function to override + * certain elements of the function. * - * @access public - * @since 1.0 - * @return string -*/ + * $download_id = null, $link_text = null, $style = null, $color = null, $class = null + * + * @since 1.0 + * @param array $args Arguments for display + * @return string $purchase_form + */ +function edd_get_purchase_link( $args = array() ) { + global $post, $edd_displayed_form_ids; -function edd_get_purchase_link( $download_id = null, $link_text = null, $style = null, $color = null, $class = '' ) { + $purchase_page = edd_get_option( 'purchase_page', false ); + if ( ! $purchase_page || $purchase_page == 0 ) { - global $edd_options, $post, $user_ID; + global $no_checkout_error_displayed; + if ( ! is_null( $no_checkout_error_displayed ) ) { + return false; + } - if ( ! isset( $edd_options['purchase_page'] ) || $edd_options['purchase_page'] == 0 ) { - edd_set_error( 'set_checkout', sprintf( __( 'No checkout page has been configured. Visit Settings to set one.', 'edd' ), admin_url( 'edit.php?post_type=download&page=edd-settings' ) ) ); + edd_set_error( + 'set_checkout', + sprintf( + /* translators: the settings screen URL */ + __( 'No checkout page has been configured. Visit Settings to set one.', 'easy-digital-downloads' ), + esc_url( edd_get_admin_url( array( + 'page' => 'edd-settings', + 'tab' => 'general', + 'section' => 'pages', + ) ) ) + ) + ); edd_print_errors(); + + $no_checkout_error_displayed = true; + return false; + } - - $page = get_permalink( $post->ID ); // current page - $link_args = array( 'download_id' => $download_id, 'edd_action' => 'add_to_cart' ); - $link = add_query_arg( $link_args, $page ); - $checkout_url = edd_get_checkout_uri(); - $variable_pricing = edd_has_variable_prices( $download_id ); - - if ( is_null( $link_text ) ) { - $link_text = isset( $edd_options['add_to_cart_text'] ) ? $edd_options['add_to_cart_text'] : __( 'Purchase', 'edd' ); - } - - if ( is_null( $style ) ) { - $style = isset( $edd_options['button_style'] ) ? $edd_options['button_style'] : 'button'; - } - - if ( is_null( $color ) ) { - $color = isset( $edd_options['checkout_color'] ) ? $edd_options['checkout_color'] : 'blue'; - } - - $purchase_form = '
    '; - - if ( $variable_pricing ) { - $prices = edd_get_variable_prices( $download_id ); - $purchase_form .= '
    '; - if ( $prices ) { - foreach( $prices as $key => $price ) { - $checked = ''; - - if ( $key == 0 ) { - $checked = 'checked="checked"'; - } - - $purchase_form .= sprintf( ' ', - $checked, - esc_attr( 'edd_price_option_' . $download_id . '_' . $key ), - esc_attr( 'edd_price_option_' . $download_id ), - esc_attr( $key ) - ); - - $purchase_form .= sprintf( '
    ', - esc_attr( 'edd_price_option_' . $download_id . '_' . $key ), - esc_html( $price['name'] . ' - ' . edd_currency_filter( $price['amount'] ) ) - ); - - } - } - $purchase_form .= '
    '; + + $post_id = is_object( $post ) ? $post->ID : 0; + $button_behavior = edd_get_download_button_behavior( $post_id ); + + $defaults = apply_filters( 'edd_purchase_link_defaults', array( + 'download_id' => $post_id, + 'price' => (bool) true, + 'price_id' => isset( $args['price_id'] ) ? $args['price_id'] : false, + 'direct' => $button_behavior == 'direct' ? true : false, + 'text' => $button_behavior == 'direct' ? edd_get_option( 'buy_now_text', __( 'Buy Now', 'easy-digital-downloads' ) ) : edd_get_option( 'add_to_cart_text', __( 'Purchase', 'easy-digital-downloads' ) ), + 'checkout' => edd_get_option( 'checkout_button_text', _x( 'Checkout', 'text shown on the Add to Cart Button when the product is already in the cart', 'easy-digital-downloads' ) ), + 'style' => edd_get_option( 'button_style', 'button' ), + 'color' => edd_get_button_color_class(), + 'class' => 'edd-submit', + ) ); + + $args = wp_parse_args( $args, $defaults ); + + // Override the straight_to_gateway if the shop doesn't support it + if ( ! edd_shop_supports_buy_now() ) { + $args['direct'] = false; + } + + $download = new EDD_Download( $args['download_id'] ); + + if( empty( $download->ID ) ) { + return false; + } + + if( 'publish' !== $download->post_status && ! current_user_can( 'edit_product', $download->ID ) ) { + return false; // Product not published or user doesn't have permission to view drafts + } + + $options = array(); + $variable_pricing = $download->has_variable_prices(); + $data_variable = $variable_pricing ? ' data-variable-price="yes"' : 'data-variable-price="no"'; + $type = $download->is_single_price_mode() ? 'data-price-mode=multi' : 'data-price-mode=single'; + + $show_price = $args['price'] && $args['price'] !== 'no'; + $data_price_value = 0; + $price = false; + + if ( $variable_pricing && false !== $args['price_id'] ) { + + $price_id = $args['price_id']; + $prices = $download->prices; + $options['price_id'] = $args['price_id']; + $found_price = isset( $prices[$price_id] ) ? $prices[$price_id]['amount'] : false; + + $data_price_value = $found_price; + + if ( $show_price ) { + $price = $found_price; } - - $purchase_form .= '
    '; - - $data_variable = $variable_pricing ? ' data-variable-price="yes"' : ''; - - if ( edd_item_in_cart( $download_id ) ) { - $button_display = 'style="display:none;"'; - $checkout_display = ''; - } else { - $button_display = ''; - $checkout_display = 'style="display:none;"'; - } - - if ( $style == 'button' ) { - - $purchase_button = sprintf( '', - esc_attr( 'edd_button edd_add_to_cart_wrap edd_' . $color ), - $button_display - ); - $purchase_button .= ''; - $purchase_button .= ''; - $purchase_button .= sprintf( '', - esc_attr( 'edd_button_text edd-submit edd-add-to-cart ' . $class ), - esc_attr( $link_text ), - esc_attr( $download_id ), - $data_variable - ); - $purchase_button .= ''; - $purchase_button .= ''; - $purchase_button .= ''; - - $checkout_link = sprintf( '', - esc_url( $checkout_url ), - esc_attr( 'edd_go_to_checkout edd_button edd_' . $color ), - $checkout_display - ); - $checkout_link .= ''; - $checkout_link .= '' . __( 'Checkout', 'edd' ) . ''; - $checkout_link .= ''; - $checkout_link .= ''; - - $purchase_form .= $purchase_button . $checkout_link; - - } else { - - $purchase_text = sprintf( '', - esc_attr( 'edd_submit_plain edd-add-to-cart ' . $class ), - esc_attr( $link_text ), - esc_attr( $download_id ), - esc_attr( $data_variable ), - $button_display - ); - - $checkout_link = sprintf( '', - esc_url( $checkout_url ), - esc_attr( 'edd_go_to_checkout edd_button edd_' . $color ), - $checkout_display - ); - $checkout_link .= __( 'Checkout', 'edd' ); - $checkout_link .= ''; - - $purchase_form .= $purchase_text . $checkout_link; - } - if ( edd_is_ajax_enabled() ) { - $purchase_form .= sprintf( '
    ', - esc_url( EDD_PLUGIN_URL . 'includes/images/loading.gif' ) - ); - $purchase_form .= ' 
    '; + + } elseif ( ! $variable_pricing ) { + + $data_price_value = $download->price; + + if ( $show_price ) { + $price = $download->price; + } + + } + + $data_price = 'data-price="' . $data_price_value . '"'; + + $button_text = ! empty( $args['text'] ) ? ' – ' . $args['text'] : ''; + + if ( false !== $price ) { + + if ( 0 == $price ) { + $args['text'] = __( 'Free', 'easy-digital-downloads' ) . $button_text; + } else { + $args['text'] = edd_currency_filter( edd_format_amount( $price ) ) . $button_text; + } + + } + + $button_display = ''; + $checkout_display = 'style="display:none;"'; + if ( edd_item_in_cart( $download->ID, $options ) && ( ! $variable_pricing || ! $download->is_single_price_mode() ) ) { + $button_display = 'style="display:none;"'; + $checkout_display = ''; + } + + // Collect any form IDs we've displayed already so we can avoid duplicate IDs + if ( isset( $edd_displayed_form_ids[ $download->ID ] ) ) { + $edd_displayed_form_ids[ $download->ID ]++; + } else { + $edd_displayed_form_ids[ $download->ID ] = 1; + } + + $form_id = ! empty( $args['form_id'] ) ? $args['form_id'] : 'edd_purchase_' . $download->ID; + + // If we've already generated a form ID for this download ID, append -# + if ( $edd_displayed_form_ids[ $download->ID ] > 1 ) { + $form_id .= '-' . $edd_displayed_form_ids[ $download->ID ]; + } + + $args = apply_filters( 'edd_purchase_link_args', $args ); + + ob_start(); +?> + + + ID, $args ); ?> + +
    + ID ) ) . '" data-timestamp="' . esc_attr( $timestamp ) . '" data-token="' . esc_attr( EDD\Utils\Tokenizer::tokenize( $timestamp ) ) . '" data-action="edd_add_to_cart" data-download-id="' . esc_attr( $download->ID ) . '" ' . $data_variable . ' ' . $type . ' ' . $data_price . ' ' . $button_display . '>' . $args['text'] . ' '; + } - - $purchase_form .= '
    '; - $purchase_form .= ''; - $purchase_form .= ''; - $purchase_form .= ''; - - return apply_filters( 'edd_purchase_download_form', $purchase_form, $download_id, $link_text, $style, $color, $class ); - -} + + echo ''; + echo '' . $args['checkout'] . ''; + ?> + + + + + + + is_free( $args['price_id'] ) && ! edd_download_is_tax_exclusive( $download->ID ) ): ?> + ' . sprintf( esc_html__( 'Includes %1$s tax', 'easy-digital-downloads' ), esc_html( edd_get_formatted_tax_rate() ) ) . ''; + } elseif ( edd_display_tax_rate() && ! edd_prices_include_tax() ) { + /* translators: the formatted tax rate */ + echo '' . sprintf( esc_html__( 'Excluding %1$s tax', 'easy-digital-downloads' ), esc_html( edd_get_formatted_tax_rate() ) ) . ''; + } ?> + +
    + + + + + + is_free( $args['price_id'] ) ) { ?> + + + + + + + ID, $args ) ) : ?> + + + + ID, $args ); ?> + + +post_type == 'download' && is_singular() && is_main_query() ) { - ob_start(); - do_action( 'edd_after_download_content', $post->ID ); - $content .= ob_get_clean(); + // If we've already generated a form ID for this download ID, append -# + $form_id = ''; + if ( $edd_displayed_form_ids[ $download_id ] > 1 ) { + $form_id .= '-' . $edd_displayed_form_ids[ $download_id ]; } - - return $content; - + + $variable_pricing = edd_has_variable_prices( $download_id ); + + if ( ! $variable_pricing ) { + return; + } + + $prices = apply_filters( 'edd_purchase_variable_prices', edd_get_variable_prices( $download_id ), $download_id ); + + // If the price_id passed is found in the variable prices, do not display all variable prices. + if ( false !== $args['price_id'] && isset( $prices[ $args['price_id'] ] ) ) { + return; + } + + $type = edd_single_price_option_mode( $download_id ) ? 'checkbox' : 'radio'; + $mode = edd_single_price_option_mode( $download_id ) ? 'multi' : 'single'; + + // Filter the class names for the edd_price_options div + $css_classes_array = apply_filters( 'edd_price_options_classes', array( + 'edd_price_options', + 'edd_' . esc_attr( $mode ) . '_mode' + ), $download_id ); + + // Sanitize those class names and form them into a string + $css_classes_string = implode( ' ', array_map( 'sanitize_html_class', $css_classes_array ) ); + + if ( edd_item_in_cart( $download_id ) && ! edd_single_price_option_mode( $download_id ) ) { + return; + } + + do_action( 'edd_before_price_options', $download_id ); ?> +
    +
      + $price ) : + echo '
    • '; + echo ''; + do_action( 'edd_after_price_option', $key, $price, $download_id ); + echo '
    • '; + endforeach; + endif; + do_action( 'edd_after_price_options_list', $download_id, $prices, $type ); + ?> +
    +
    + +
    + +
    + +
    +  x  + +
    +post_type && is_singular( 'download' ) && is_main_query() && ! post_password_required() ) { + ob_start(); + do_action( 'edd_before_download_content', $post->ID ); + $content = ob_get_clean() . $content; } return $content; - } -add_filter( 'the_content', 'edd_filter_success_page_content' ); +add_filter( 'the_content', 'edd_before_download_content' ); + +/** + * After Download Content + * + * Adds an action to the end of download post content that can be hooked to by + * other functions. + * + * @since 1.0.8 + * @global $post + * + * @param $content The the_content field of the download object + * @return string the content with any additional data attached + */ +function edd_after_download_content( $content ) { + global $post; + if ( $post && $post->post_type == 'download' && is_singular( 'download' ) && is_main_query() && !post_password_required() ) { + ob_start(); + do_action( 'edd_after_download_content', $post->ID ); + $content .= ob_get_clean(); + } + + return $content; +} +add_filter( 'the_content', 'edd_after_download_content' ); /** * Get Button Colors * * Returns an array of button colors. * - * @access public - * @since 1.0 - * @return array -*/ - + * @since 1.0 + * @return array $colors Button colors + */ function edd_get_button_colors() { - - $colors = array( - 'gray' => __( 'Gray', 'edd' ), - 'pink' => __( 'Pink', 'edd' ), - 'blue' => __( 'Blue', 'edd' ), - 'green' => __( 'Green', 'edd' ), - 'teal' => __( 'Teal', 'edd' ), - 'black' => __( 'Black', 'edd' ), - 'dark gray' => __( 'Dark Gray', 'edd' ), - 'orange' => __( 'Orange', 'edd' ), - 'purple' => __( 'Purple', 'edd' ), - 'slate' => __( 'Slate', 'edd' ) + $colors = array( + 'white' => array( + 'label' => __( 'White', 'easy-digital-downloads' ), + 'hex' => '#ffffff' + ), + 'gray' => array( + 'label' => __( 'Gray', 'easy-digital-downloads' ), + 'hex' => '#f0f0f0' + ), + 'blue' => array( + 'label' => __( 'Blue', 'easy-digital-downloads' ), + 'hex' => '#428bca' + ), + 'red' => array( + 'label' => __( 'Red', 'easy-digital-downloads' ), + 'hex' => '#d9534f' + ), + 'green' => array( + 'label' => __( 'Green', 'easy-digital-downloads' ), + 'hex' => '#5cb85c' + ), + 'yellow' => array( + 'label' => __( 'Yellow', 'easy-digital-downloads' ), + 'hex' => '#f0ad4e' + ), + 'orange' => array( + 'label' => __( 'Orange', 'easy-digital-downloads' ), + 'hex' => '#ed9c28' + ), + 'dark-gray' => array( + 'label' => __( 'Dark Gray', 'easy-digital-downloads' ), + 'hex' => '#363636' + ), + 'inherit' => array( + 'label' => __( 'Inherit', 'easy-digital-downloads' ), + 'hex' => '' + ) ); - - return apply_filters( 'edd_button_colors', $colors ); + return apply_filters( 'edd_button_colors', $colors ); } - /** * Get Button Styles * * Returns an array of button styles. * - * @access public - * @since 1.2.2 - * @return array -*/ - + * @since 1.2.2 + * @return array $styles Button styles + */ function edd_get_button_styles() { - - $styles = array( - 'button' => __( 'Button', 'edd' ), - 'plain' => __( 'Plain Text', 'edd' ) + $styles = array( + 'button' => __( 'Button', 'easy-digital-downloads' ), + 'plain' => __( 'Plain Text', 'easy-digital-downloads' ) ); - - return apply_filters( 'edd_button_styles', $styles ); - -} - - -/** - * Show Has Purchased Item Message - * - * Prints a notice when user has already purchased the item. - * - * @access private - * @since 1.0 - * @return void -*/ -function edd_show_has_purchased_item_message( $download_id ) { - - global $user_ID; - - if ( edd_has_user_purchased( $user_ID, $download_id ) ) { - echo '

    ' . __( 'You have already purchased this item, but you may purchase it again.', 'edd' ) . '

    '; - } - + return apply_filters( 'edd_button_styles', $styles ); } -add_action( 'edd_after_download_content', 'edd_show_has_purchased_item_message' ); - /** * Default formatting for download excerpts * - * This excerpt is primarily used in the [downloads] short code + * This excerpt is primarily used in the [downloads] shortcode * - * @access private - * @since 1.0.8.4 - * @return string -*/ - + * @since 1.0.8.4 + * @param string $excerpt Content before filtering + * @return string $excerpt Content after filtering + * @return string + */ function edd_downloads_default_excerpt( $excerpt ) { - return do_shortcode( wpautop( $excerpt ) ); - } add_filter( 'edd_downloads_excerpt', 'edd_downloads_default_excerpt' ); - /** * Default formatting for full download content * - * This is primarily used in the [downloads] short code + * This is primarily used in the [downloads] shortcode * - * @access private - * @since 1.0.8.4 - * @return string -*/ - + * @since 1.0.8.4 + * @param string $content Content before filtering + * @return string $content Content after filtering + */ function edd_downloads_default_content( $content ) { - return do_shortcode( wpautop( $content ) ); - } add_filter( 'edd_downloads_content', 'edd_downloads_default_content' ); - /** * Gets the download links for each item purchased * - * @access private - * @since 1.1.5 - * @return string -*/ - -function edd_get_purchase_download_links( $purchase_data ) { + * @since 1.1.5 + * @param int $payment_id The ID of the payment to retrieve download links for + * @return string + */ +function edd_get_purchase_download_links( $payment_id = 0 ) { - $links = '
    \ No newline at end of file diff --git a/includes/templates/shortcode-content-excerpt.php b/includes/templates/shortcode-content-excerpt.php deleted file mode 100644 index b9fd077342d..00000000000 --- a/includes/templates/shortcode-content-excerpt.php +++ /dev/null @@ -1,6 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/includes/templates/shortcode-content-full.php b/includes/templates/shortcode-content-full.php deleted file mode 100644 index 4880d3695a6..00000000000 --- a/includes/templates/shortcode-content-full.php +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/includes/templates/shortcode-content-image.php b/includes/templates/shortcode-content-image.php deleted file mode 100644 index 5c24e0c7ef3..00000000000 --- a/includes/templates/shortcode-content-image.php +++ /dev/null @@ -1,5 +0,0 @@ - -
    - -
    - \ No newline at end of file diff --git a/includes/templates/shortcode-content-price.php b/includes/templates/shortcode-content-price.php deleted file mode 100644 index 6bd3842b97b..00000000000 --- a/includes/templates/shortcode-content-price.php +++ /dev/null @@ -1,5 +0,0 @@ - -
    - -
    - \ No newline at end of file diff --git a/includes/templates/shortcode-content-title.php b/includes/templates/shortcode-content-title.php deleted file mode 100644 index 8b11fbf811d..00000000000 --- a/includes/templates/shortcode-content-title.php +++ /dev/null @@ -1,3 +0,0 @@ -

    - -

    \ No newline at end of file diff --git a/includes/theme-compatibility.php b/includes/theme-compatibility.php new file mode 100755 index 00000000000..93358f06eda --- /dev/null +++ b/includes/theme-compatibility.php @@ -0,0 +1,42 @@ +subtotal; + if ( ! empty( $this->discount ) ) { + $subtotal -= $this->discount; + } + + $maximums = array( + 'subtotal' => $subtotal, + 'tax' => $this->tax, + 'total' => $this->total, + 'quantity' => $this->quantity, + ); + + $refunded_items = $this->get_refunded_items(); + + if ( ! empty( $refunded_items ) ) { + foreach ( $refunded_items as $refunded_item ) { + // We're adding numbers here, because `$refund_item` has negative amounts already. + $maximums['subtotal'] += $refunded_item->subtotal; + $maximums['tax'] += $refunded_item->tax; + $maximums['total'] += $refunded_item->total; + // If a partial refund was spread across all order items, just use the original quantity. + if ( abs( $refunded_item->quantity ) < abs( $this->quantity ) ) { + $maximums['quantity'] += $refunded_item->quantity; + } + } + } + + $maximums['subtotal'] = number_format( $maximums['subtotal'], edd_currency_decimal_filter(), '.', '' ); + $maximums['tax'] = number_format( $maximums['tax'], edd_currency_decimal_filter(), '.', '' ); + $maximums['total'] = number_format( $maximums['total'], edd_currency_decimal_filter(), '.', '' ); + $maximums['quantity'] = intval( $maximums['quantity'] ); + + return $maximums; + } + +} diff --git a/includes/upgrades/functions.php b/includes/upgrades/functions.php new file mode 100644 index 00000000000..409fb9c049b --- /dev/null +++ b/includes/upgrades/functions.php @@ -0,0 +1,201 @@ + array( + 'name' => __( 'Tax Rates', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Tax_Rates', + ), + 'migrate_discounts' => array( + 'name' => __( 'Discounts', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Discounts', + ), + 'migrate_orders' => array( + 'name' => __( 'Orders', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Orders', + ), + 'migrate_customer_addresses' => array( + 'name' => __( 'Customer Addresses', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Customer_Addresses', + ), + 'migrate_customer_email_addresses' => array( + 'name' => __( 'Customer Email Addresses', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Customer_Email_Addresses', + ), + 'migrate_customer_notes' => array( + 'name' => __( 'Customer Notes', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Customer_Notes', + ), + 'migrate_logs' => array( + 'name' => __( 'Logs', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Logs', + ), + 'migrate_order_notes' => array( + 'name' => __( 'Order Notes', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Order_Notes', + ), + 'v30_legacy_data_removed' => array( + 'name' => __( 'Remove Legacy Data', 'easy-digital-downloads' ), + 'class' => 'EDD\\Admin\\Upgrades\\v3\\Remove_Legacy_Data', + ), + ); +} + +/** + * Perform automatic database upgrades when necessary + * + * @since 2.6 + * @return void + */ +function edd_do_automatic_upgrades() { + + $edd_version = edd_get_db_version(); + if ( version_compare( $edd_version, EDD_VERSION, '>=' ) ) { + return; + } + + // Existing stores should set the upgraded version and the onboarding wizard as complete. + if ( ! empty( $edd_version ) ) { + update_option( 'edd_version_upgraded_from', $edd_version, false ); + if ( ! get_option( 'edd_onboarding_completed', false ) && ! get_option( 'edd_onboarding_started', false ) ) { + update_option( 'edd_onboarding_completed', true, false ); + } + } + edd_update_db_version(); +} diff --git a/includes/user-functions.php b/includes/user-functions.php old mode 100644 new mode 100755 index c8e7e66ad55..82387a12cfe --- a/includes/user-functions.php +++ b/includes/user-functions.php @@ -4,146 +4,1192 @@ * * Functions related to users / customers * - * @package Easy Digital Downloads - * @subpackage AJAX - * @copyright Copyright (c) 2012, Pippin Williamson + * @package EDD + * @subpackage Functions + * @copyright Copyright (c) 2018, Easy Digital Downloads, LLC * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License * @since 1.0.8.6 -*/ + */ + +// Exit if accessed directly +defined( 'ABSPATH' ) || exit; /** * Get Users Purchases * * Retrieves a list of all purchases by a specific user. * - * @access public - * @since 1.0 - * @return array -*/ - -function edd_get_users_purchases( $user_id = 0 ) { - - if( empty( $user_id ) ) { - global $user_ID; - $user_id = $user_ID; + * @since 1.0 + * + * @param int|string $user User ID or email address. + * @param int $number Number of purchases to retrieve + * @param bool $pagination Page number to retrieve + * @param string|array $status Either an array of statuses, a single status as a string literal or a comma + * separated list of statues. Default 'complete'. + * + * @return WP_Post[]|false List of all user purchases. + */ +function edd_get_users_purchases( $user = 0, $number = 20, $pagination = false, $status = 'complete' ) { + if ( empty( $user ) ) { + $user = get_current_user_id(); } - $purchases = get_transient('edd_user_' . $user_id . '_purchases'); - if(false === $purchases || edd_is_test_mode()) { - $mode = edd_is_test_mode() ? 'test' : 'live'; - $purchases = get_posts( + if ( is_email( $user ) ) { + $customers = edd_get_customers( array( - 'meta_query' => array( - 'relation' => 'AND', - array( - 'key' => '_edd_payment_mode', - 'value' => $mode - ), - array( - 'key' => '_edd_payment_user_id', - 'value' => $user_id - ) - ), - 'post_type' => 'edd_payment', - 'posts_per_page' => -1 + 'email' => $user, ) ); - set_transient('edd_user_' . $user_id . '_purchases', $purchases, 7200); + if ( $customers ) { + $user = $customers[0]->user_id; + } } - if($purchases) { - // return the download list - return $purchases; + + // Bail if no user found. + if ( empty( $user ) ) { + return false; + } + + if ( is_string( $status ) ) { + if ( strpos( $status, ',' ) ) { + $status = explode( ',', $status ); + } else { + $status = 'publish' === $status + ? 'complete' + : $status; + + $status = array( $status ); + } + } + + if ( is_array( $status ) ) { + $status = array_unique( $status ); } - - // no downloads - return false; + + if ( $pagination ) { + if ( get_query_var( 'paged' ) ) { + $paged = get_query_var( 'paged' ); + } elseif ( get_query_var( 'page' ) ) { + $paged = get_query_var( 'page' ); + } else { + $paged = 1; + } + } + + $args = array( + 'user' => $user, + 'number' => $number, + 'status' => $status, + 'orderby' => 'date', + ); + + if ( $pagination ) { + $args['page'] = $paged; + } else { + $args['nopaging'] = true; + } + + if ( 'any' === $status ) { + unset( $args['status'] ); + } + + $purchases = edd_get_payments( apply_filters( 'edd_get_users_purchases_args', $args ) ); + + return $purchases + ? $purchases + : false; } +/** + * Retrieve products purchased by a specific user. + * + * @since 2.0 + * @since 3.0 Refactored to use new query methods and to be more efficient. + * + * @param int|string $user User ID or email address. + * @param string $status Order status. + * + * @return WP_Post[]|false Array of products, false otherwise. + */ +function edd_get_users_purchased_products( $user = 0, $status = 'complete' ) { + + if ( $status === 'publish' ) { + $status = 'complete'; + } + + // Fall back to user ID + if ( empty( $user ) ) { + $user = get_current_user_id(); + } + + // Bail if no user + if ( empty( $user ) ) { + return false; + } + + // Try to get customer + if ( is_numeric( $user ) ) { + $customer = edd_get_customer_by( 'user_id', $user ); + } elseif ( is_email( $user ) ) { + $customer = edd_get_customer_by( 'email', $user ); + } else { + return false; + } + + if ( empty( $customer ) ) { + return false; + } + // Fetch the order IDs + $number = apply_filters( 'edd_users_purchased_products_payments', 9999999 ); + + $order_ids = edd_get_orders( array( + 'customer_id' => $customer->id, + 'fields' => 'ids', + 'status' => $status, + 'number' => $number, + ) ); + + $product_ids = edd_get_order_items( array( + 'order_id__in' => $order_ids, + 'number' => $number, + 'fields' => 'product_id', + ) ); + + $product_ids = array_unique( $product_ids ); + + // Bail if no product IDs found. + if ( empty( $product_ids ) ) { + return false; + } + + $args = apply_filters( 'edd_get_users_purchased_products_args', array( + 'include' => $product_ids, + 'post_type' => 'download', + 'posts_per_page' => -1, + ) ); + + return apply_filters( 'edd_users_purchased_products_list', get_posts( $args ) ); +} /** - * Has User Purchased + * Checks to see if a user has purchased a product. + * + * @since 1.0 + * @since 3.0 Refactored to be more efficient. * - * Checks to see if a user has purchased a download. + * @param int $user_id User ID. + * @param array|int $downloads Download IDs to check against. + * @param int $variable_price_id - the variable price ID to check for * - * @access public - * @since 1.0 - * @param int $user_id - the ID of the user to check - * @param int $download_Id - the ID of the download to check for - * @param int $variable_price_id - the variable price ID to check for - * @return boolean - true if has purchased, false otherwise -*/ + * @return bool True if purchased, false otherwise. + */ +function edd_has_user_purchased( $user_id = 0, $downloads = array(), $variable_price_id = null ) { + global $wpdb; -function edd_has_user_purchased($user_id, $download_id, $variable_price_id = null) { - - if( !is_user_logged_in() ) - return false; // at some point this should support email checking + // Bail if no user ID passed. + if ( empty( $user_id ) ) { + return false; + } - $users_purchases = edd_get_users_purchases($user_id); + /** + * Fires before the queries execute. + * + * @since 2.7.7 + */ + do_action( 'edd_has_user_purchased_before', $user_id, $downloads, $variable_price_id ); - $return = false; + if ( ! is_array( $downloads ) ) { + $downloads = array( $downloads ); + } - if($users_purchases) { - foreach($users_purchases as $purchase) { + // Bail if no downloads passed. + if ( empty( $downloads ) ) { + return false; + } - $purchase_meta = edd_get_payment_meta( $purchase->ID ); - $purchased_files = maybe_unserialize($purchase_meta['downloads']); + $number = apply_filters( 'edd_users_purchased_products_payments', 9999999 ); - if(is_array($purchased_files)) { + $where_id = "'" . implode( "', '", $wpdb->_escape( $downloads ) ) . "'"; + $product_id = "oi.product_id IN ({$where_id})"; - foreach($purchased_files as $download) { + $price_id = isset( $variable_price_id ) + ? $wpdb->prepare( 'AND oi.price_id = %d', absint( $variable_price_id ) ) + : ''; - if($download['id'] == $download_id) { + $statuses = "'" . implode( "', '", $wpdb->_escape( edd_get_deliverable_order_item_statuses() ) ). "'"; + $status_id = " AND oi.status IN({$statuses})"; - if( !is_null( $variable_price_id ) && $variable_price_id !== false ) { + // Perform a direct database query as it is more efficient. + $sql = $wpdb->prepare(" + SELECT COUNT(o.id) AS count + FROM {$wpdb->edd_orders} o + INNER JOIN {$wpdb->edd_order_items} oi ON o.id = oi.order_id + WHERE {$product_id} {$price_id} {$status_id} + AND o.type = 'sale' + AND user_id = %d + LIMIT %d", + absint( $user_id ), + $number + ); - if( $variable_price_id == $download['options']['price_id'] ) { - - return true; - - } else { - - $return = false; - - } + $result = (int) $wpdb->get_var( $sql ); - } else { - - $return = true; - - } + $return = 0 === $result + ? false + : true; - } + /** + * @since 2.7.7 + * + * Filter has purchased result + */ + return apply_filters( 'edd_has_user_purchased', $return, $user_id, $downloads, $variable_price_id ); +} + +/** + * Check if a user has made any purchases. + * + * @since 1.0 + * + * @param int $user_id User ID. + * @return bool True if user has purchased, false otherwise. + */ +function edd_has_purchases( $user_id = null ) { + + // Maybe fallback to logged in user. + if ( empty( $user_id ) ) { + $user_id = get_current_user_id(); + } + + if ( empty( $user_id ) ) { + return false; + } + + $count = edd_count_orders( array( 'user_id' => $user_id ) ); + + return (bool) $count; +} + + +/** + * Get purchase statistics for user. + * + * @since 1.6 + * @since 3.0 Updated to use new query method. + * + * @param int|string $user User ID or email address. + * + * @return array|false $stats Number of purchases and total amount spent by customer. False otherwise. + */ +function edd_get_purchase_stats_by_user( $user = '' ) { + if ( is_email( $user ) ) { + $field = 'email'; + } elseif ( is_numeric( $user ) ) { + $field = 'user_id'; + } else { + return false; + } + + $stats = array(); + $customer = edd_get_customer_by( $field, $user ); + + if ( $customer ) { + $stats['purchases'] = edd_count_orders( array( $field => $user ) ); + $stats['total_spent'] = edd_sanitize_amount( $customer->purchase_value ); + } + + return (array) apply_filters( 'edd_purchase_stats_by_user', $stats, $user ); +} + +/** + * Count number of purchases of a customer. + * + * @since 1.3 + * + * @param string|int $user User ID or email. + * @return int Number of purchases. + */ +function edd_count_purchases_of_customer( $user = null ) { + if ( empty( $user ) ) { + $user = get_current_user_id(); + } + + $stats = ! empty( $user ) + ? edd_get_purchase_stats_by_user( $user ) + : false; + + return isset( $stats['purchases'] ) + ? $stats['purchases'] + : 0; +} + +/** + * Calculates the total amount spent by a user + * + * @since 1.3 + * @param mixed $user - ID or email + * @return float - the total amount the user has spent + */ +function edd_purchase_total_of_user( $user = null ) { + $stats = edd_get_purchase_stats_by_user( $user ); + + return isset( $stats['total_spent'] ) ? $stats['total_spent'] : 0.00; +} + +/** + * Counts the total number of files a user (or customer if an email address is + * given) has downloaded + * + * @since 1.3 + * @since 3.0 Updated to use edd_count_file_download_logs. + * @param mixed $user - ID or email + * @return int - The total number of files the user has downloaded + */ +function edd_count_file_downloads_of_user( $user ) { + + // If we got an email, look up the customer ID and call the direct query + // for customer download counts. + if ( is_email( $user ) ) { + return edd_count_file_downloads_of_customer( $user ); + } + + $customer = edd_get_customer_by( 'user_id', $user ); + + return ! empty( $customer->id ) ? edd_count_file_download_logs( + array( + 'customer_id' => $customer->id, + ) + ) : 0; +} + +/** + * Counts the total number of files a customer has downloaded. + * + * @since unknown + * @since 3.0 Updated to use edd_count_file_download_logs. + * @param string|int $customer_id_or_email The email address or id of the customer. + * + * @return int The total number of files the customer has downloaded. + */ +function edd_count_file_downloads_of_customer( $customer_id_or_email = '' ) { + $customer = new EDD_Customer( $customer_id_or_email ); + + return edd_count_file_download_logs( + array( + 'customer_id' => $customer->id, + ) + ); +} + +/** + * Validate a potential username + * + * @since 1.3.4 + * @param string $username The username to validate + * @return bool + */ +function edd_validate_username( $username ) { + $sanitized = sanitize_user( $username, false ); + $valid = ( $sanitized == $username ); + + return (bool) apply_filters( 'edd_validate_username', $valid, $username ); +} + +/** + * Attach the customer to an existing user account when completing guest purchase. + * + * This only runs when a user account already exists and a guest purchase is made + * with the account's email address. + * + * After attaching the customer to the user ID, the account is set to pending. + * + * @since 2.8 + * @param bool $success True if payment was added successfully, false otherwise. + * @param int $payment_id The ID of the EDD_Payment that was added. + * @param int $customer_id The ID of the EDD_Customer object. + * @param object $customer The EDD_Customer object. + * @return void + */ +function edd_connect_guest_customer_to_existing_user( $success, $payment_id, $customer_id, $customer ) { + + // If for some reason we don't get a customer object here, return. + if ( ! $customer instanceof EDD_Customer ) { + return; + } + + if ( ! empty( $customer->user_id ) ) { + return; + } + + $user = get_user_by( 'email', $customer->email ); + + if ( ! $user instanceof WP_User ) { + return; + } + + $customer->update( array( 'user_id' => $user->ID ) ); + + // Set a flag to force the account to be verified before purchase history can be accessed. + edd_set_user_to_pending( $user->ID ); + edd_send_user_verification_email( $user->ID ); + +} +add_action( 'edd_customer_post_attach_payment', 'edd_connect_guest_customer_to_existing_user', 10, 4 ); + +/** + * Attach the newly created user_id to a customer, if one exists + * + * @since 2.4.6 + * @param int $user_id The User ID that was created + * @return void + */ +function edd_connect_existing_customer_to_new_user( $user_id ) { + $email = get_the_author_meta( 'user_email', $user_id ); + + // Update the user ID on the customer + $customer = new EDD_Customer( $email ); + + if( $customer->id > 0 ) { + $customer->update( array( 'user_id' => $user_id ) ); + } +} +add_action( 'user_register', 'edd_connect_existing_customer_to_new_user', 10, 1 ); + +/** + * Looks up purchases by email that match the registering user + * + * This is for users that purchased as a guest and then came + * back and created an account. + * + * @since 1.6 + * @param int $user_id - the new user's ID + * @return void + */ +function edd_add_past_purchases_to_new_user( $user_id ) { + + $email = get_the_author_meta( 'user_email', $user_id ); + + if ( empty( $email ) ) { + return; + } + + $payments = edd_get_payments( array( 's' => $email, 'output' => 'payments' ) ); + + if( $payments ) { + + // Set a flag to force the account to be verified before purchase history can be accessed + edd_set_user_to_pending( $user_id ); + + edd_send_user_verification_email( $user_id ); + + foreach( $payments as $payment ) { + if ( is_object( $payment ) && $payment instanceof EDD_Payment ) { + if ( intval( $payment->user_id ) > 0 ) { + continue; // This payment already associated with an account } + $payment->user_id = $user_id; + $payment->save(); } } } - return $return; + +} +add_action( 'user_register', 'edd_add_past_purchases_to_new_user', 10, 1 ); + + +/** + * Counts the total number of customers. + * + * @since 1.7 + * @return int - The total number of customers. + */ +function edd_count_total_customers( $args = array() ) { + return edd_count_customers(); } /** - * Has Purchases + * Returns the saved address for a customer + * + * @since 1.8 + * @since 3.0 Update to use new query methods. + + * @param int $user_id User ID. + * @return array Customer address. + */ +function edd_get_customer_address( $user_id = 0 ) { + + // Maybe fall back to logged in user ID. + if ( empty( $user_id ) ) { + $user_id = get_current_user_id(); + } + + $customer = edd_get_customer_by( 'user_id', $user_id ); + + $parsed_address = array(); + + if ( $customer ) { + $address = $customer->get_address(); + + if ( $address instanceof EDD\Customers\Customer_Address ) { + $parsed_address = array( + 'line1' => $address->address, + 'line2' => $address->address2, + 'city' => $address->city, + 'zip' => $address->postal_code, + 'country' => $address->country, + 'state' => $address->region, + ); + } + } + + $address = wp_parse_args( $parsed_address, array( + 'line1' => '', + 'line2' => '', + 'city' => '', + 'zip' => '', + 'country' => '', + 'state' => '', + ) ); + + return $address; +} + +/** + * Sends the new user notification email when a user registers during checkout * - * Checks to see if a user has purchased at least one item. + * @since 1.8.8 + * @param int $user_id + * @param array $user_data * - * @access public - * @since 1.0 - * @param $user_id int - the ID of the user to check - * @return bool - true if has purchased, false other wise. -*/ + * @return void + */ +function edd_new_user_notification( $user_id = 0, $user_data = array() ) { + + if( empty( $user_id ) || empty( $user_data ) ) { + return; + } + + $emails = EDD()->emails; + $from_name = edd_get_option( 'from_name', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ); + $from_email = edd_get_option( 'from_email', get_bloginfo( 'admin_email' ) ); + + // Setup and send the new user email for Admins. + $emails->__set( 'from_name', $from_name ); + $emails->__set( 'from_email', $from_email ); + + /* translators: Site name */ + $admin_subject = apply_filters( 'edd_user_registration_admin_email_subject', sprintf( __( '[%s] New User Registration', 'easy-digital-downloads' ), $from_name ), $user_data ); + $admin_heading = apply_filters( 'edd_user_registration_admin_email_heading', __( 'New user registration', 'easy-digital-downloads' ), $user_data ); + /* translators: the username */ + $admin_message = sprintf( __( 'Username: %s', 'easy-digital-downloads' ), $user_data['user_login'] ) . "\r\n\r\n"; + /* translators: the user email */ + $admin_message .= sprintf( __( 'E-mail: %s', 'easy-digital-downloads' ), $user_data['user_email'] ) . "\r\n"; + + $admin_message = apply_filters( 'edd_user_registration_admin_email_message', $admin_message, $user_data ); + + $emails->__set( 'heading', $admin_heading ); + + $emails->send( get_option( 'admin_email' ), $admin_subject, $admin_message ); -function edd_has_purchases($user_id = null) { - - if(is_null($user_id)) { - global $user_ID; - $user_id = $user_ID; + // Setup and send the new user email for the end user. + /* translators: Site name */ + $user_subject = apply_filters( 'edd_user_registration_email_subject', sprintf( __( '[%s] Your username and password', 'easy-digital-downloads' ), $from_name ), $user_data ); + $user_heading = apply_filters( 'edd_user_registration_email_heading', __( 'Your account info', 'easy-digital-downloads' ), $user_data ); + $user_message = apply_filters( 'edd_user_registration_email_username', sprintf( __( 'Username: %s', 'easy-digital-downloads' ), $user_data['user_login'] ) . "\r\n", $user_data ); + + if ( did_action( 'edd_pre_process_purchase' ) ) { + $password_message = __( 'Password entered at checkout', 'easy-digital-downloads' ); + } else { + $password_message = __( 'Password entered at registration', 'easy-digital-downloads' ); + } + + /* translators: %s: password message */ + $user_message .= apply_filters( 'edd_user_registration_email_password', sprintf( __( 'Password: %s', 'easy-digital-downloads' ), '[' . $password_message . ']' ) . "\r\n" ); + + $login_url = apply_filters( 'edd_user_registration_email_login_url', wp_login_url() ); + if( $emails->html ) { + + $user_message .= ' ' . esc_attr__( 'Click here to log in', 'easy-digital-downloads' ) . ' →' . "\r\n"; + + } else { + /* translators: %s: login URL */ + $user_message .= sprintf( __( 'To log in, visit: %s', 'easy-digital-downloads' ), esc_url( $login_url ) ) . "\r\n"; + } + + $user_message = apply_filters( 'edd_user_registration_email_message', $user_message, $user_data ); + + $emails->__set( 'heading', $user_heading ); + + $emails->send( $user_data['user_email'], $user_subject, $user_message ); + +} +add_action( 'edd_insert_user', 'edd_new_user_notification', 10, 2 ); + +/** + * Set a user's status to pending + * + * @since 2.4.4 + * @param integer $user_id The User ID to set to pending + * @return bool If the update was successful + */ +function edd_set_user_to_pending( $user_id = 0 ) { + if ( empty( $user_id ) ) { + return false; } - - if(edd_get_users_purchases($user_id)) { - return true; // user has at least one purchase + + do_action( 'edd_pre_set_user_to_pending', $user_id ); + + $update_successful = (bool) update_user_meta( $user_id, '_edd_pending_verification', '1' ); + + do_action( 'edd_post_set_user_to_pending', $user_id, $update_successful ); + + return $update_successful; +} + +/** + * Set the user from pending to active + * + * @since 2.4.4 + * @param integer $user_id The User ID to activate + * @return bool If the user was marked as active or not + */ +function edd_set_user_to_verified( $user_id = 0 ) { + + if ( empty( $user_id ) ) { + return false; + } + + if ( ! edd_user_pending_verification( $user_id ) ) { + return false; + } + + do_action( 'edd_pre_set_user_to_active', $user_id ); + + $update_successful = delete_user_meta( $user_id, '_edd_pending_verification', '1' ); + + do_action( 'edd_post_set_user_to_active', $user_id, $update_successful ); + + return $update_successful; +} + +/** + * Determines if the user account is pending verification. Pending accounts cannot view purchase history + * + * @since 2.4.4 + * @return bool + */ +function edd_user_pending_verification( $user_id = null ) { + + if( is_null( $user_id ) ) { + $user_id = get_current_user_id(); + } + + // No need to run a DB lookup on an empty user id + if ( empty( $user_id ) ) { + return false; + } + + $pending = get_user_meta( $user_id, '_edd_pending_verification', true ); + + return (bool) apply_filters( 'edd_user_pending_verification', ! empty( $pending ), $user_id ); + +} + +/** + * Gets the activation URL for the specified user + * + * @since 2.4.4 + * @return string + */ +function edd_get_user_verification_url( $user_id = 0 ) { + + if( empty( $user_id ) ) { + return false; + } + + $base_url = add_query_arg( array( + 'edd_action' => 'verify_user', + 'user_id' => absint( $user_id ), + 'ttl' => strtotime( '+24 hours' ) + ), untrailingslashit( edd_get_user_verification_page() ) ); + + $token = edd_get_user_verification_token( $base_url ); + $url = add_query_arg( 'token', $token, $base_url ); + + return apply_filters( 'edd_get_user_verification_url', $url, $user_id ); + +} + +/** + * Gets the URL that triggers a new verification email to be sent + * + * @since 2.4.4 + * @return string + */ +function edd_get_user_verification_request_url( $user_id = 0 ) { + + if( empty( $user_id ) ) { + $user_id = get_current_user_id(); + } + + $url = esc_url( wp_nonce_url( add_query_arg( array( + 'edd_action' => 'send_verification_email' + ) ), 'edd-request-verification' ) ); + + return apply_filters( 'edd_get_user_verification_request_url', $url, $user_id ); + +} + +/** + * Sends an email to the specified user with a URL to verify their account + * + * @since 2.4.4 + * @param int $user_id + */ +function edd_send_user_verification_email( $user_id = 0 ) { + + if( empty( $user_id ) ) { + return; + } + + if( ! edd_user_pending_verification( $user_id ) ) { + return; + } + + $user_data = get_userdata( $user_id ); + + if( ! $user_data ) { + return; + } + + $name = $user_data->display_name; + $url = edd_get_user_verification_url( $user_id ); + $from_name = edd_get_option( 'from_name', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ); + $from_email = edd_get_option( 'from_email', get_bloginfo( 'admin_email' ) ); + $subject = apply_filters( 'edd_user_verification_email_subject', __( 'Verify your account', 'easy-digital-downloads' ), $user_id ); + $heading = apply_filters( 'edd_user_verification_email_heading', __( 'Verify your account', 'easy-digital-downloads' ), $user_id ); + $message = sprintf( + /* translators: 1: User display name, 2: Site name, 3: Verification URL */ + __( 'Hello %1$s, + + Your account with %2$s needs to be verified before you can access your purchase history. Click here to verify your account. + + Link missing? Visit the following URL: %3$s', 'easy-digital-downloads' ), + $name, + $from_name, + esc_url_raw( $url ), + // This parameter is not used, but is added for compatibility with past translations. + esc_url_raw( $url ) + ); + + $message = apply_filters( 'edd_user_verification_email_message', $message, $user_id ); + + $emails = new EDD_Emails; + + $emails->__set( 'from_name', $from_name ); + $emails->__set( 'from_email', $from_email ); + $emails->__set( 'heading', $heading ); + + $emails->send( $user_data->user_email, $subject, $message ); + +} + +/** + * Generates a token for a user verification URL. + * + * An 'o' query parameter on a URL can include optional variables to test + * against when verifying a token without passing those variables around in + * the URL. For example, downloads can be limited to the IP that the URL was + * generated for by adding 'o=ip' to the query string. + * + * Or suppose when WordPress requested a URL for automatic updates, the user + * agent could be tested to ensure the URL is only valid for requests from + * that user agent. + * + * @since 2.4.4 + * + * @param string $url The URL to generate a token for. + * @return string The token for the URL. + */ +function edd_get_user_verification_token( $url = '' ) { + + $args = array(); + $hash = apply_filters( 'edd_get_user_verification_token_algorithm', 'sha256' ); + $secret = apply_filters( 'edd_get_user_verification_token_secret', hash( $hash, wp_salt() ) ); + + /* + * Add additional args to the URL for generating the token. + * Allows for restricting access to IP and/or user agent. + */ + $parts = parse_url( $url ); + $options = array(); + + if ( isset( $parts['query'] ) ) { + + wp_parse_str( $parts['query'], $query_args ); + + // o = option checks (ip, user agent). + if ( ! empty( $query_args['o'] ) ) { + + // Multiple options can be checked by separating them with a colon in the query parameter. + $options = explode( ':', rawurldecode( $query_args['o'] ) ); + + if ( in_array( 'ip', $options ) ) { + + $args['ip'] = edd_get_ip(); + + } + + if ( in_array( 'ua', $options ) ) { + + $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : ''; + $args['user_agent'] = rawurlencode( $ua ); + + } + + } + + } + + /* + * Filter to modify arguments and allow custom options to be tested. + * Be sure to rawurlencode any custom options for consistent results. + */ + $args = apply_filters( 'edd_get_user_verification_token_args', $args, $url, $options ); + + $args['secret'] = $secret; + $args['token'] = false; // Removes a token if present. + + $url = add_query_arg( $args, $url ); + $parts = parse_url( $url ); + + // In the event there isn't a path, set an empty one so we can MD5 the token + if ( ! isset( $parts['path'] ) ) { + + $parts['path'] = ''; + + } + + $token = md5( $parts['path'] . '?' . $parts['query'] ); + + return $token; + +} + +/** + * Generate a token for a URL and match it against the existing token to make + * sure the URL hasn't been tampered with. + * + * @since 2.4.4 + * + * @param string $url URL to test. + * @return bool + */ +function edd_validate_user_verification_token( $url = '' ) { + + $ret = false; + $parts = parse_url( $url ); + $query_args = array(); + + if ( isset( $parts['query'] ) ) { + + wp_parse_str( $parts['query'], $query_args ); + + if ( isset( $query_args['ttl'] ) && current_time( 'timestamp' ) > $query_args['ttl'] ) { + + do_action( 'edd_user_verification_token_expired' ); + + $link_text = sprintf( + /* translators: %s: URL to request a new verification link */ + __( 'Sorry but your account verification link has expired. Click here to request a new verification URL.', 'easy-digital-downloads' ), + esc_url( edd_get_user_verification_request_url() ) + ); + + wp_die( apply_filters( 'edd_verification_link_expired_text', $link_text ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + + } + + if ( isset( $query_args['token'] ) && $query_args['token'] == edd_get_user_verification_token( $url ) ) { + + $ret = true; + + } + + } + + return apply_filters( 'edd_validate_user_verification_token', $ret, $url, $query_args ); +} + +/** + * Processes an account verification email request + * + * @since 2.4.4 + * + * @return void + */ +function edd_process_user_verification_request() { + + if( ! wp_verify_nonce( $_GET['_wpnonce'], 'edd-request-verification' ) ) { + wp_die( __( 'Nonce verification failed.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + if( ! is_user_logged_in() ) { + wp_die( __( 'You must be logged in to verify your account.', 'easy-digital-downloads' ), __( 'Notice', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + if( ! edd_user_pending_verification( get_current_user_id() ) ) { + wp_die( __( 'Your account has already been verified.', 'easy-digital-downloads' ), __( 'Notice', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + edd_send_user_verification_email( get_current_user_id() ); + + $redirect = apply_filters( + 'edd_user_account_verification_request_redirect', + add_query_arg( 'edd-verify-request', '1', edd_get_user_verification_page() ) + ); + + edd_redirect( $redirect ); + +} +add_action( 'edd_send_verification_email', 'edd_process_user_verification_request' ); + +/** + * Processes an account verification + * + * @since 2.4.4 + * + * @return void + */ +function edd_process_user_account_verification() { + + if( empty( $_GET['token'] ) ) { + return false; + } + + if( empty( $_GET['user_id'] ) ) { + return false; + } + + if( empty( $_GET['ttl'] ) ) { + return false; + } + + $parts = parse_url( add_query_arg( array() ) ); + wp_parse_str( $parts['query'], $query_args ); + $url = add_query_arg( $query_args, untrailingslashit( edd_get_user_verification_page() ) ); + + if( ! edd_validate_user_verification_token( $url ) ) { + + do_action( 'edd_invalid_user_verification_token' ); + + wp_die( __( 'Invalid verification token provided.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + edd_set_user_to_verified( absint( $_GET['user_id'] ) ); + + do_action( 'edd_user_verification_token_validated' ); + + $redirect = apply_filters( + 'edd_user_account_verified_redirect', + add_query_arg( 'edd-verify-success', '1', edd_get_user_verification_page() ) + ); + + edd_redirect( $redirect ); +} +add_action( 'edd_verify_user', 'edd_process_user_account_verification' ); + +/** + * Retrieves the purchase history page, or main URL for the account verification process + * + * @since 2.4.6 + * @return string The base URL to use for account verification + */ +function edd_get_user_verification_page() { + $url = home_url(); + $purchase_history = edd_get_option( 'purchase_history_page', 0 ); + + if ( ! empty( $purchase_history ) ) { + $url = get_permalink( $purchase_history ); + } + + return apply_filters( 'edd_user_verification_base_url', $url ); +} + +/** + * When a user is deleted, detach that user id from the customer record + * + * @since 2.5 + * @param int $user_id The User ID being deleted + * @return bool If the detachment was successful + */ +function edd_detach_deleted_user( $user_id ) { + + $customer = new EDD_Customer( $user_id, true ); + $detached = false; + + if ( $customer->id > 0 ) { + $detached = $customer->update( array( 'user_id' => 0 ) ); + } + + do_action( 'edd_detach_deleted_user', $user_id, $customer, $detached ); + + return $detached; +} +add_action( 'delete_user', 'edd_detach_deleted_user', 10, 1 ); + +/** + * Modify User Profile + * + * Modifies the output of profile.php to add key generation/revocation + * + * @since 2.6 + * @param object $user Current user info + * @return void + */ +function edd_show_user_api_key_field( $user ) { + + // Bail if no user, or user ID is not current user + if ( empty( $user ) || ( get_current_user_id() !== $user->ID ) ) { + return; + } + + /** + * Show API User Key Fields + * + * Allows showing/hiding the user API Key fields. By default will only try to show on admin pages. The filter + * allows for developers to choose to show it in other places that the WordPress profile editor hooks are used + * like bbPress + * + * @since 2.9.1 + * + * @param boolean If EDD should attempt to load the user API fields + * @param WP_User The User object currently being viewed. + */ + $show_fields = apply_filters( 'edd_show_user_api_key_fields', is_admin(), $user ); + if ( ! $show_fields ) { + return; + } + + if ( ( edd_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_shop_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) { + $user = get_userdata( $user->ID ); + $public_key = EDD()->api->get_user_public_key( $user->ID ); + $secret_key = EDD()->api->get_user_secret_key( $user->ID ); + $token = EDD()->api->get_token( $user->ID ); + ?> + + + + + + + + + + +
    + + + edd_user_public_key ) ) { ?> +

    + +

    + +
    + + +
    + + +
    + + +
    +

    + +

    +
    + +
    + + + + + + + + + +
    + iOS App', 'easy-digital-downloads' ), 'https://itunes.apple.com/us/app/easy-digital-downloads-2/id1169488828?ls=1&mt=8' ); + ?> + + + +
    + + + api->get_user_public_key( $user_id ); + + if ( empty( $public_key ) ) { + $new_public_key = EDD()->api->generate_public_key( $user->user_email ); + $new_secret_key = EDD()->api->generate_private_key( $user->ID ); + + update_user_meta( $user_id, $new_public_key, 'edd_user_public_key' ); + update_user_meta( $user_id, $new_secret_key, 'edd_user_secret_key' ); + } else { + EDD()->api->revoke_api_key( $user_id ); + } } - return false; // user has never purchased anything } +add_action( 'personal_options_update', 'edd_update_user_api_key' ); +add_action( 'edit_user_profile_update', 'edd_update_user_api_key' ); diff --git a/includes/users/login.php b/includes/users/login.php new file mode 100644 index 00000000000..67a30a7c62a --- /dev/null +++ b/includes/users/login.php @@ -0,0 +1,237 @@ +session->get( 'edd_require_login_to_download_redirect' ); + if ( ! empty( $download_require_login_redirect ) ) { + $redirect_for_download = edd_get_file_download_login_redirect( $download_require_login_redirect ); + wp_safe_redirect( esc_url( $redirect_for_download ) ); + } + + $default_redirect_url = $data['edd_redirect']; + if ( has_filter( 'edd_login_redirect' ) ) { + $user_id = $user instanceof WP_User ? $user->ID : false; + /** + * Filters the URL to which users are redirected to after logging in. + * + * @since 1.0 + * @param string $default_redirect_url The URL to which to redirect after logging in. + * @param int|false User ID. false if no ID is available. + */ + wp_redirect( esc_url_raw( apply_filters( 'edd_login_redirect', $default_redirect_url, $user_id ) ) ); + } else { + wp_safe_redirect( esc_url_raw( $default_redirect_url ) ); + } + edd_die(); + } + } +} +add_action( 'edd_user_login', 'edd_process_login_form' ); + +/** + * Log User In + * + * @since 1.0 + * @since 2.9.24 Uses the wp_signon function instead of all the additional checks which can bypass hooks in core. + * + * @param int $user_id User ID + * @param string $user_login Username + * @param string $user_pass Password + * @param boolean $remember Remember me + * @return void +*/ +function edd_log_user_in( $user_id, $user_login, $user_pass, $remember = false ) { + + $credentials = array( + 'user_login' => $user_login, + 'user_password' => $user_pass, + 'remember' => $remember, + ); + + $user = wp_signon( $credentials ); + + if ( ! $user instanceof WP_User ) { + edd_set_error( + 'edd_invalid_login', + sprintf( + /* translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. */ + __( 'Invalid username or password. %1$sReset Password%2$s', 'easy-digital-downloads' ), + '', + '' + ) + ); + } else { + // Since wp_signon doesn't set the current user, we need to do this. + wp_set_current_user( $user->ID ); + + do_action( 'edd_log_user_in', $user_id, $user_login, $user_pass ); + } + + return $user; +} + +add_filter( 'login_url', 'edd_update_login_url', 10, 3 ); +/** + * If a login page has been set in the EDD settings, + * update the WordPress login URL. + * + * @param string $url + * @return string + */ +function edd_update_login_url( $url, $redirect_to, $force_reauth ) { + + // Don't change the login URL if the request is an admin request. + if ( ! edd_doing_ajax() && is_admin() ) { + return $url; + } + + /** + * If the $wp_rewrite global hasn't been initialized, don't do anything. + * This is added defensively for situations where `wp_login_url` may be called too early. + */ + global $wp_rewrite; + if ( ! $wp_rewrite ) { + return $url; + } + + // Get the login page URL and return the default if it's not set. + $login_url = edd_get_login_page_uri(); + if ( ! $login_url ) { + return $url; + } + + if ( ! empty( $redirect ) ) { + $login_url = add_query_arg( 'redirect_to', urlencode( $redirect ), $login_url ); + } + if ( $force_reauth ) { + $login_url = add_query_arg( 'reauth', '1', $login_url ); + } + + return $login_url; +} + +/** + * Helper function to get the EDD login page URI. + * + * @return false|string + */ +function edd_get_login_page_uri() { + $login_page = edd_get_option( 'login_page', false ); + if ( ! function_exists( 'has_block' ) || ( $login_page && ! has_block( 'edd/login', absint( $login_page ) ) ) ) { + return false; + } + + return $login_page ? get_permalink( $login_page ) : false; +} + +/** + * Generate a redirect URL that is used when file downloads require the user to be logged in. + * + * By default uses the homepage, appends a nonce, and an action, and returns a Nonce'd URL + * + * @since 3.1 + * + * @param array $redirect_data The data stored for this specific redirect URL. + * + * @return string The URL to use in the redirect process of logging in to download the file. + */ +function edd_get_file_download_login_redirect( $redirect_data ) { + $login_redirect_page_id = edd_get_option( 'login_redirect_page', false ); + $redirect_base = ! empty( $login_redirect_page_id ) ? get_permalink( $login_redirect_page_id ) : home_url(); + + $token = \EDD\Utils\Tokenizer::tokenize( $redirect_data ); + + $redirect_for_download = add_query_arg( + array( + 'edd_action' => 'process_file_download_after_login', + '_token' => $token, + ), + apply_filters( 'edd_get_file_download_login_redirect_base', $redirect_base ) + ); + + return $redirect_for_download; +} diff --git a/includes/users/lost-password.php b/includes/users/lost-password.php new file mode 100644 index 00000000000..0e019d2a770 --- /dev/null +++ b/includes/users/lost-password.php @@ -0,0 +1,314 @@ +session->get( 'edd_forgot_password_redirect' ); + if ( empty( $redirect_url ) ) { + return $errors; + } + $message = sprintf( + /* translators: %s: Link to the referring page. */ + __( 'Follow the instructions in the confirmation email you just received, then return to what you were doing.', 'easy-digital-downloads' ), + esc_url( $redirect_url ) + ); + $errors->remove( 'confirm' ); + $errors->add( + 'confirm', + apply_filters( + 'edd_login_register_error_message', + $message, + $redirect_url + ), + 'message' + ); + EDD()->session->set( 'edd_forgot_password_redirect', null ); + + return $errors; +} + +/** + * Gets the lost password URL, customized for EDD. Using this allows the password + * reset form to redirect to the login screen with the EDD custom confirmation message. + * + * @since 2.10 + * @return string + */ +function edd_get_lostpassword_url() { + + $login_page_uri = edd_get_login_page_uri(); + + if ( empty( $login_page_uri ) ) { + return add_query_arg( + array( + 'edd_forgot_password' => 'confirm', + ), + wp_lostpassword_url() + ); + } + + return add_query_arg( 'action', 'lostpassword', $login_page_uri ); +} + +/** + * Gets the password reset link for a user. + * + * @param WP_User $user + * @return false|string + */ +function edd_get_password_reset_link( $user ) { + $key = get_password_reset_key( $user ); + if ( is_wp_error( $key ) ) { + return false; + } + + return add_query_arg( + array( + 'action' => 'rp', + 'key' => rawurlencode( $key ), + 'login' => rawurlencode( $user->user_login ), + ), + wp_login_url() + ); +} + +add_action( 'lostpassword_form', 'edd_set_lostpassword_session' ); +/** + * Sets a session value for the lost password redirect URI. + * + * @since 3.0.2 + * @return void + */ +function edd_set_lostpassword_session() { + if ( ! empty( $_GET['edd_forgot_password'] ) && 'confirm' === $_GET['edd_forgot_password'] ) { + $url = wp_validate_redirect( + wp_get_referer(), + edd_get_checkout_uri() + ); + EDD()->session->set( 'edd_forgot_password_redirect', $url ); + } +} + +add_action( 'edd_user_lost_password', 'edd_handle_lost_password_request' ); +/** + * Handles the lost password request from the EDD lost password block. + * + * @since 3.1 + * @param array $data + * @return void + */ +function edd_handle_lost_password_request( $data ) { + + // Verify the nonce. + if ( empty( $data['edd_lost-password_nonce'] ) || ! wp_verify_nonce( $data['edd_lost-password_nonce'], 'edd-lost-password-nonce' ) ) { + edd_set_error( 'edd_lost_password', __( 'Your request could not be completed.', 'easy-digital-downloads' ) ); + return; + } + + if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { + $errors = retrieve_password(); + if ( ! is_wp_error( $errors ) ) { + edd_set_success( 'checkemail', __( 'You did it! Check your email for instructions on resetting your password.', 'easy-digital-downloads' ) ); + } else { + $error_code = $errors->get_error_code(); + $message = $errors->get_error_message( $error_code ); + if ( $message ) { + // WP_Error messages include "Error:" so we remove that here to prevent duplication. + $message = explode( ':', $message ); + $output = trim( $message[0] ); + if ( ! empty( $message[1] ) ) { + unset( $message[0] ); + $output = trim( implode( ':', $message ) ); + } + edd_set_error( $error_code, $output ); + } + } + } + edd_redirect( remove_query_arg( 'action', wp_get_referer() ) ); +} + +add_filter( 'retrieve_password_message', 'edd_retrieve_password_message', 10, 4 ); +/** + * Filters the email message sent when a password reset has been requested. + * + * @since 3.1 + * @param string $message The email message. + * @param string $key The activation key. + * @param string $user_login The username for the user. + * @param WP_User $user_data WP_User object. + * @return string + */ +function edd_retrieve_password_message( $message, $key, $user_login, $user_data ) { + if ( empty( $_POST['edd_action'] ) || 'user_lost_password' !== $_POST['edd_action'] ) { + return $message; + } + if ( empty( $_POST['edd_lost-password_nonce'] ) || ! wp_verify_nonce( $_POST['edd_lost-password_nonce'], 'edd-lost-password-nonce' ) ) { + return $message; + } + if ( is_multisite() ) { + $site_name = get_network()->site_name; + } else { + /* + * The blogname option is escaped with esc_html on the way into the database + * in sanitize_option. We want to reverse this for the plain text arena of emails. + */ + $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); + } + $message = __( 'Someone has requested a password reset for the following account:', 'easy-digital-downloads' ) . "\r\n\r\n"; + /* translators: %s: Site name. */ + $message .= sprintf( __( 'Site Name: %s', 'easy-digital-downloads' ), $site_name ) . "\r\n\r\n"; + /* translators: %s: User login. */ + $message .= sprintf( __( 'Username: %s', 'easy-digital-downloads' ), $user_login ) . "\r\n\r\n"; + $message .= __( 'If this was a mistake, ignore this email and nothing will happen.', 'easy-digital-downloads' ) . "\r\n\r\n"; + $message .= __( 'To reset your password, visit the following address:', 'easy-digital-downloads' ) . "\r\n\r\n"; + $message .= add_query_arg( + array( + 'edd_action' => 'password_reset_requested', + 'key' => $key, + 'login' => rawurlencode( $user_login ), + ), + esc_url_raw( $_POST['edd_redirect'] ) + ); + $message .= "\r\n\r\n"; + + if ( ! is_user_logged_in() ) { + $requester_ip = $_SERVER['REMOTE_ADDR']; + if ( $requester_ip ) { + $message .= sprintf( + /* translators: %s: IP address of password reset requester. */ + __( 'This password reset request originated from the IP address %s.', 'easy-digital-downloads' ), + $requester_ip + ) . "\r\n"; + } + } + + return $message; +} + +add_action( 'edd_password_reset_requested', 'edd_validate_password_reset_link' ); +/** + * Validates the email link and sends the user to the password reset form upon success. + * + * @since 3.1 + * @return void + */ +function edd_validate_password_reset_link() { + list( $rp_path ) = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ); + $rp_cookie = 'wp-resetpass-' . COOKIEHASH; + $redirect = remove_query_arg( array( 'key', 'login', 'edd_action' ), wp_get_referer() ); + + // Everything is good; move forward with the password reset. + if ( isset( $_GET['key'] ) && isset( $_GET['login'] ) ) { + $value = sprintf( '%s:%s', wp_unslash( $_GET['login'] ), wp_unslash( $_GET['key'] ) ); + setcookie( $rp_cookie, $value, 0, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); + + edd_redirect( add_query_arg( 'action', 'rp', $redirect ) ); + } + + $user = false; + if ( isset( $_COOKIE[ $rp_cookie ] ) && 0 < strpos( $_COOKIE[ $rp_cookie ], ':' ) ) { + list( $rp_login, $rp_key ) = explode( ':', wp_unslash( $_COOKIE[ $rp_cookie ] ), 2 ); + + $user = check_password_reset_key( $rp_key, $rp_login ); + + if ( isset( $_POST['pass1'] ) && ! hash_equals( $rp_key, $_POST['rp_key'] ) ) { + $user = false; + } + } + + if ( ! $user || is_wp_error( $user ) ) { + setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); + + if ( $user && $user->get_error_code() === 'expired_key' ) { + edd_set_error( 'expiredkey', __( 'Your password reset link has expired. Please request a new link below.', 'easy-digital-downloads' ) ); + } else { + edd_set_error( 'invalidkey', __( 'Your password reset link appears to be invalid. Please request a new link below.', 'easy-digital-downloads' ) ); + } + } + + // Redirect back to the lost password form instead of the password reset. + edd_redirect( add_query_arg( 'action', 'lostpassword', $redirect ) ); +} + +add_action( 'edd_user_reset_password', 'edd_validate_password_reset' ); +/** + * Validates the password reset and redirects to the login form on success. + * + * @since 3.1 + * @param array $data + * @return void + */ +function edd_validate_password_reset( $data ) { + + // We don't need or use AJAX requests for this, so die if one is received. + if ( edd_doing_ajax() ) { + wp_die( __( 'Invalid password reset request.', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 400 ) ); + } + + // Verify the nonce. + if ( ! isset( $data['edd_resetpassword_nonce'] ) || ! wp_verify_nonce( $data['edd_resetpassword_nonce'], 'edd-reset-password-nonce' ) ) { + edd_set_error( 'password_reset_failed', __( 'Invalid password reset request.', 'easy-digital-downloads' ) ); + } + + if ( empty( $data['rp_key'] ) ) { + edd_set_error( 'password_reset_failed', __( 'Invalid password reset request.', 'easy-digital-downloads' ) ); + } + + $user = check_password_reset_key( $data['rp_key'], $data['user_login'] ); + + if ( ! $user || is_wp_error( $user ) ) { + edd_set_error( 'password_reset_failed', __( 'Invalid password reset request.', 'easy-digital-downloads' ) ); + } + + // Check if password is one or all empty spaces. + if ( ! empty( $data['pass1'] ) ) { + $_POST['pass1'] = trim( $data['pass1'] ); + } + + if ( empty( $data['pass1'] ) ) { + edd_set_error( 'empty_password', __( 'The password cannot be a space or all spaces.', 'easy-digital-downloads' ) ); + } + + // Check if password fields do not match. + if ( ! empty( $data['pass1'] ) && trim( $data['pass2'] ) !== $data['pass1'] ) { + edd_set_error( 'password_reset_mismatch', __( 'The passwords do not match.', 'easy-digital-downloads' ) ); + } + + $user = get_user_by( 'login', $data['user_login'] ); + if ( false === $user ) { + edd_set_error( 'password_reset_unsuccessful', __( 'Your password could not be reset.', 'easy-digital-downloads' ) ); + } + + $redirect = remove_query_arg( 'action', $data['edd_redirect'] ); + + // If no errors were registered then reset the password. + $errors = edd_get_errors(); + if ( empty( $errors ) ) { + reset_password( $user, $data['pass1'] ); + edd_set_success( 'password_reset_successful', __( 'Your password was successfully reset.', 'easy-digital-downloads' ) ); + // todo: check if this is correct + setcookie( 'wp-resetpass-' . COOKIEHASH, ' ', time() - YEAR_IN_SECONDS, wp_make_link_relative( wp_get_referer() ), COOKIE_DOMAIN, is_ssl(), true ); + edd_redirect( $redirect ); + } + + edd_redirect( add_query_arg( 'action', 'password_reset_unsuccessful', $redirect ) ); +} diff --git a/includes/users/register.php b/includes/users/register.php new file mode 100644 index 00000000000..0f6d07c420f --- /dev/null +++ b/includes/users/register.php @@ -0,0 +1,129 @@ + $data['edd_user_email'], + ) + ); + if ( email_exists( $data['edd_user_email'] ) || ! empty( $customers ) ) { + edd_set_error( 'email_unavailable', __( 'This email address is not available.', 'easy-digital-downloads' ) ); + } + + if ( empty( $data['edd_user_email'] ) || ! is_email( $data['edd_user_email'] ) ) { + edd_set_error( 'email_invalid', __( 'Invalid email', 'easy-digital-downloads' ) ); + } + + if ( ! empty( $data['edd_payment_email'] ) && $data['edd_payment_email'] != $data['edd_user_email'] && ! is_email( $data['edd_payment_email'] ) ) { + edd_set_error( 'payment_email_invalid', __( 'Invalid payment email', 'easy-digital-downloads' ) ); + } + + if ( isset( $data['edd_honeypot'] ) && ! empty( $data['edd_honeypot'] ) ) { + edd_set_error( 'invalid_form_data', __( 'Registration form validation failed.', 'easy-digital-downloads' ) ); + } + + // Check if password is one or all empty spaces. + if ( ! empty( $data['edd_user_pass'] ) ) { + $data['edd_user_pass'] = trim( $data['edd_user_pass'] ); + } + + if ( empty( $data['edd_user_pass'] ) ) { + edd_set_error( 'empty_password', __( 'The password cannot be a space or all spaces.', 'easy-digital-downloads' ) ); + } + + // Check if password fields do not match. + if ( ! empty( $data['edd_user_pass'] ) && ( empty( $data['edd_user_pass2'] ) || trim( $data['edd_user_pass2'] ) !== $data['edd_user_pass'] ) ) { + edd_set_error( 'password_mismatch', __( 'The passwords do not match.', 'easy-digital-downloads' ) ); + } + + do_action( 'edd_process_register_form' ); + + // Check for errors and redirect if none present. + $errors = edd_get_errors(); + + if ( empty( $errors ) ) { + + $redirect = apply_filters( 'edd_register_redirect', $data['edd_redirect'] ); + + edd_register_and_login_new_user( + array( + 'user_login' => $data['edd_user_login'], + 'user_pass' => $data['edd_user_pass'], + 'user_email' => $data['edd_user_email'], + 'user_registered' => date( 'Y-m-d H:i:s' ), + 'role' => get_option( 'default_role' ), + ) + ); + + edd_set_success( 'account_registration_successful', __( 'Your account has been successfully created.', 'easy-digital-downloads' ) ); + + edd_redirect( $redirect ); + } +} +add_action( 'edd_user_register', 'edd_process_register_form' ); diff --git a/includes/utils/class-date.php b/includes/utils/class-date.php new file mode 100644 index 00000000000..09ef4e46156 --- /dev/null +++ b/includes/utils/class-date.php @@ -0,0 +1,129 @@ +getOffset(); + $interval = \DateInterval::createFromDateString( "{$offset} seconds" ); + $this->add( $interval ); + } + + /** + * Formats a given date string according to WP date and time formats and timezone. + * + * @since 3.0 + * + * @param string|true $format Optional. How to format the date string. Accepts 'date', + * 'time', 'datetime', 'mysql', 'timestamp', 'wp_timestamp', + * 'object', or any valid date_format() string. If true, 'datetime' + * will be used. Default 'datetime'. + * @return string|int|\DateTime Formatted date string, timestamp if `$type` is timestamp, + * or a DateTime object if `$type` is 'object'. + */ + public function format( $format = 'datetime' ) { + + if ( empty( $format ) || true === $format ) { + $format = 'datetime'; + } + + switch( $format ) { + + // jQuery UI Datepicker formats + case 'date-attribute': + case 'date-js': + case 'date-mysql': + case 'time-mysql': + + // WordPress Formats + case 'date': + case 'time': + case 'datetime': + case 'mysql': + $formatted = parent::format( edd_get_date_format( $format ) ); + break; + + case 'object': + $formatted = $this; + break; + + case 'timestamp': + $formatted = $this->getTimestamp(); + break; + + case 'wp_timestamp': + /* + * Note: Even if the timezone has been changed, getTimestamp() will still + * return the original timestamp because DateTime doesn't directly allow + * conversion of the timestamp in terms of offset; it's immutable. + */ + $formatted = $this->getWPTimestamp(); + break; + + default: + $formatted = parent::format( $format ); + break; + } + + return $formatted; + } + + /** + * Retrieves the date timestamp with the WordPress offset applied. + * + * @since 3.0 + * + * @return int WordPress "local" timestamp. + */ + public function getWPTimestamp() { + return $this->getTimestamp() + EDD()->utils->get_gmt_offset(); + } + + /** + * Converts a localized date string to UTC. + * + * @since 3.1.4 + * @param $format string + * @return string + */ + public function get_utc_from_local( $format = 'Y-m-d H:i:s' ) { + $utc_timezone = new \DateTimeZone( 'utc' ); + $this->setTimezone( $utc_timezone ); + + return $this->format( $format ); + } +} diff --git a/includes/utils/class-edd-exception.php b/includes/utils/class-edd-exception.php new file mode 100644 index 00000000000..769087c73b6 --- /dev/null +++ b/includes/utils/class-edd-exception.php @@ -0,0 +1,24 @@ +offsetSet( $item_id, $attributes ); + + $result = true; + + } else { + + $message = sprintf( + 'The attributes were missing when attempting to add the \'%1$s\' %2$s.', + $item_id, + static::$item_error_label + ); + + throw new Exception( $message ); + } + + return $result; + } + + /** + * Removes an item from the registry by ID. + * + * @since 3.0 + * + * @param string $item_id Item ID. + */ + public function remove_item( $item_id ) { + if ( $this->offsetExists( $item_id ) ) { + $this->offsetUnset( $item_id ); + } + } + + /** + * Retrieves an item and its associated attributes. + * + * @since 3.0 + * + * @throws \EDD_Exception if the item does not exist. + * + * @param string $item_id Item ID. + * @return array Array of attributes for the item if the item is set, + * otherwise an empty array. + */ + public function get_item( $item_id ) { + + $item = array(); + + if ( $this->offsetExists( $item_id ) ) { + + $item = $this->offsetGet( $item_id ); + + } else { + + $message = sprintf( + 'The \'%1$s\' %2$s does not exist.', + $item_id, + static::$item_error_label + ); + + throw new Exception( $message ); + } + + return $item; + } + + /** + * Retrieves registered items. + * + * @since 3.0 + * + * @return array The list of registered items. + */ + public function get_items() { + return $this->getArrayCopy(); + } + + /** + * Retrieves the value of a given attribute for a given item. + * + * @since 3.0 + * + * @throws \EDD_Exception if the item does not exist. + * @throws \EDD_Exception if the attribute and/or item does not exist. + * + * @param string $key Key of the attribute to retrieve. + * @param string $item_id Collection to retrieve the attribute from. + * @return mixed|null The attribute value if set, otherwise null. + */ + public function get_attribute( $key, $item_id ) { + $attribute = null; + $item = $this->get_item( $item_id ); + + if ( ! empty( $item[ $key ] ) ) { + $attribute = $item[ $key ]; + } else { + throw Exceptions\Attribute_Not_Found::from_attr( $key, $item_id ); + } + + return $attribute; + } +} diff --git a/includes/utils/class-tokenizer.php b/includes/utils/class-tokenizer.php new file mode 100644 index 00000000000..b7a08a79511 --- /dev/null +++ b/includes/utils/class-tokenizer.php @@ -0,0 +1,120 @@ +data = $data; + } + + /** + * Retrieves the signing key. + * + * @since 2.11 + * + * @return string + */ + private function get_signing_key() { + $key = get_option( 'edd_tokenizer_signing_key' ); + if ( empty( $key ) ) { + $key = $this->generate_and_save_signing_key(); + } + + return $key; + } + + /** + * Generates and saves a new signing key. + * + * @since 2.11 + * + * @return string + */ + private function generate_and_save_signing_key() { + if ( function_exists( 'random_bytes' ) ) { + try { + $key = bin2hex( random_bytes( 32 ) ); + } catch ( \Exception $e ) { + // If this failed for some reason, we'll generate using the fallback below. + } + } + + if ( empty( $key ) ) { + $key = function_exists( 'openssl_random_pseudo_bytes' ) ? bin2hex( openssl_random_pseudo_bytes( 32 ) ) : md5( uniqid() ); + } + + update_option( 'edd_tokenizer_signing_key', $key ); + + return $key; + } + + /** + * Generates a token from the data. + * + * @since 2.11 + * + * @return string|false + */ + public function generate_token() { + return hash_hmac( 'sha256', $this->data, $this->get_signing_key() ); + } + + /** + * Determines whether or not the supplied token is valid for the + * supplied data. + * + * @since 2.11 + * + * @param string $token Token to check. + * @param string|int|float $data Data that's been tokenized. + * + * @return bool + */ + public static function is_token_valid( $token, $data ) { + $real_token = self::tokenize( $data ); + + return hash_equals( $token, $real_token ); + } + + /** + * Generates a token for the supplied data. + * + * @since 2.11 + * + * @param string|int|float $data + * + * @return string|false + */ + public static function tokenize( $data ) { + if ( is_array( $data ) ) { + $data = json_encode( $data ); + } + + $generator = new Tokenizer( $data ); + + return $generator->generate_token(); + } + +} diff --git a/includes/utils/exceptions/class-attribute-not-found.php b/includes/utils/exceptions/class-attribute-not-found.php new file mode 100644 index 00000000000..020fdabd4f7 --- /dev/null +++ b/includes/utils/exceptions/class-attribute-not-found.php @@ -0,0 +1,45 @@ + __( 'Display the downloads shopping cart', 'easy-digital-downloads' ) ) ); + add_filter( 'dynamic_sidebar_params', array( $this, 'cart_widget_class' ), 10, 1 ); + } - /** constructor */ - function edd_cart_widget() - { - parent::WP_Widget(false, __('Downloads Cart', 'edd'), array('description' => __('Display the downloads shopping cart', 'edd'))); - } - - /** @see WP_Widget::widget */ - function widget($args, $instance) - { - extract($args); - $title = apply_filters('widget_title', $instance['title']); - $quantity = isset($instance['quantity']) ? $instance['quantity'] : false; - - global $post, $edd_options; - - echo $before_widget; - if ($title) { - if ($quantity == 1) { - $quantity = ' - ' . edd_get_cart_quantity() . ''; - } else { - $quantity = ''; - } - echo $before_title . $title . $quantity . $after_title; - } - do_action('edd_before_cart_widget'); - edd_shopping_cart(true); - do_action('edd_after_cart_widget'); - echo $after_widget; - } - - /** @see WP_Widget::update */ - function update($new_instance, $old_instance) - { - $instance = $old_instance; - $instance['title'] = strip_tags($new_instance['title']); - $instance['quantity'] = strip_tags($new_instance['quantity']); - return $instance; - } - - /** @see WP_Widget::form */ - function form($instance) - { - $title = isset($instance['title']) ? esc_attr($instance['title']) : ''; - $quantity = isset($instance['quantity']) ? esc_attr($instance['quantity']) : ''; - ?> -

    - - -

    -

    - - /> -

    - __('Display the downloads categories or tags', 'edd'))); - } - - /** @see WP_Widget::widget */ - function widget($args, $instance) - { - extract($args); - $title = apply_filters('widget_title', $instance['title']); - $tax = $instance['taxonomy']; - - global $post, $edd_options; - - echo $before_widget; - if ($title) { - echo $before_title . $title . $after_title; - } - - do_action('edd_before_taxonomy_widget'); - $terms = get_terms($tax); - - if (is_wp_error($terms)) { - return; - } else { - echo "\n"; - } - - do_action('edd_after_taxonomy_widget'); - echo $after_widget; - } - - /** @see WP_Widget::update */ - function update($new_instance, $old_instance) - { - $instance = $old_instance; - $instance['title'] = strip_tags($new_instance['title']); - $instance['taxonomy'] = strip_tags($new_instance['taxonomy']); - return $instance; - } - - /** @see WP_Widget::form */ - function form($instance) - { - $title = isset($instance['title']) ? esc_attr($instance['title']) : ''; - $taxonomy = isset($instance['taxonomy']) ? esc_attr($instance['taxonomy']) : 'download_category'; - ?> -

    - - -

    -

    - - -

    - __('Display a user\'s purchase history', 'edd'))); - } - - /** @see WP_Widget::widget */ - function widget($args, $instance) - { - extract($args); - $title = apply_filters('widget_title', $instance['title']); - - global $user_ID, $edd_options; - - if(is_user_logged_in()) { - - $purchases = edd_get_users_purchases($user_ID); - - if($purchases) { - echo $before_widget; - if ($title) { - echo $before_title . $title . $after_title; - } - - foreach($purchases as $purchase) { - $purchase_data = get_post_meta($purchase->ID, '_edd_payment_meta', true); - $downloads = edd_get_downloads_of_purchase($purchase->ID); - if($downloads) { - foreach($downloads as $download) { - $id = isset($purchase_data['cart_details']) ? $download['id'] : $download; - $price_id = isset($download['options']['price_id']) ? $download['options']['price_id'] : null; - $download_files = edd_get_download_files( $id, $price_id ); - echo '
    '; - echo '
    ' . get_the_title($id) . '
    '; - echo '
      '; - if( ! edd_no_redownload() ) { - if($download_files) { - foreach($download_files as $filekey => $file) { - $download_url = edd_get_download_file_url($purchase_data['key'], $purchase_data['email'], $filekey, $id); - echo '
    • ' . $file['name'] . '
    • '; - } - } else { - echo '
    • ' . __('No downloadable files found.', 'edd'); - } - } - echo '
    '; - echo '
    '; - } - } - } - } - echo $after_widget; - } - } - - /** @see WP_Widget::update */ - function update($new_instance, $old_instance) - { - $instance = $old_instance; - $instance['title'] = strip_tags($new_instance['title']); - return $instance; - } - - /** @see WP_Widget::form */ - function form($instance) - { - $title = isset($instance['title']) ? esc_attr($instance['title']) : ''; - ?> -

    - - -

    - '', + 'hide_on_checkout' => false, + 'hide_on_empty' => false, + ); + + $instance = wp_parse_args( (array) $instance, $defaults ); ?> +

    + + +

    + + +

    + id="get_field_id( 'hide_on_checkout' ) ); ?>" name="get_field_name( 'hide_on_checkout' ) ); ?>" type="checkbox" /> + +

    + +

    + id="get_field_id( 'hide_on_empty' ) ); ?>" name="get_field_name( 'hide_on_empty' ) ); ?>" type="checkbox" /> + +

    + + get_settings(); + $instance_settings = $all_settings[ $instance_id ]; + + if ( ! empty( $instance_settings['hide_on_empty'] ) ) { + $cart_quantity = edd_get_cart_quantity(); + $class = empty( $cart_quantity ) ? 'cart-empty' : 'cart-not-empty'; + + $params[0]['before_widget'] = preg_replace( '/class="(.*?)"/', 'class="$1 edd-hide-on-empty ' . $class . '"', $params[0]['before_widget'] ); + } + } + + return $params; + } + +} /** - * Register Widgets + * Categories / Tags Widget. * - * Registers the EDD Widgets. + * Downloads categories / tags widget class. * - * @access private - * @since 1.0 - * @return void + * @since 1.0 + * @return void */ - -function edd_register_widgets() { - register_widget('edd_cart_widget'); - register_widget('edd_categories_tags_widget'); - register_widget('edd_purchase_history_widget'); +class edd_categories_tags_widget extends WP_Widget { + /** Constructor */ + function __construct() { + parent::__construct( 'edd_categories_tags_widget', __( 'Downloads Categories / Tags', 'easy-digital-downloads' ), array( 'description' => __( 'Display the downloads categories or tags', 'easy-digital-downloads' ) ) ); + } + + /** @see WP_Widget::widget */ + function widget( $args, $instance ) { + // Set defaults. + $args['id'] = ( isset( $args['id'] ) ) ? $args['id'] : 'edd_categories_tags_widget'; + $instance['title'] = ( isset( $instance['title'] ) ) ? $instance['title'] : ''; + $instance['taxonomy'] = ( isset( $instance['taxonomy'] ) ) ? $instance['taxonomy'] : 'download_category'; + + $title = apply_filters( 'widget_title', $instance['title'], $instance, $args['id'] ); + $tax = $instance['taxonomy']; + $count = isset( $instance['count'] ) && $instance['count'] == 'on' ? 1 : 0; + $hide_empty = isset( $instance['hide_empty'] ) && $instance['hide_empty'] == 'on' ? 1 : 0; + + echo $args['before_widget']; + + if ( $title ) { + echo $args['before_title'] . $title . $args['after_title']; + } + + do_action( 'edd_before_taxonomy_widget' ); + + echo "
      \n"; + wp_list_categories( 'title_li=&taxonomy=' . $tax . '&show_count=' . $count . '&hide_empty=' . $hide_empty ); + echo "
    \n"; + + do_action( 'edd_after_taxonomy_widget' ); + + echo $args['after_widget']; + } + + /** @see WP_Widget::update */ + function update( $new_instance, $old_instance ) { + $instance = $old_instance; + $instance['title'] = strip_tags( $new_instance['title'] ); + $instance['taxonomy'] = strip_tags( $new_instance['taxonomy'] ); + $instance['count'] = isset( $new_instance['count'] ) ? $new_instance['count'] : ''; + $instance['hide_empty'] = isset( $new_instance['hide_empty'] ) ? $new_instance['hide_empty'] : ''; + return $instance; + } + + /** @see WP_Widget::form */ + function form( $instance ) { + // Set up some default widget settings. + $defaults = array( + 'title' => '', + 'taxonomy' => 'download_category', + 'count' => 'off', + 'hide_empty' => 'off', + ); + + $instance = wp_parse_args( (array) $instance, $defaults ); ?> +

    + + +

    +

    + + +

    +

    + + id="get_field_id( 'count' ) ); ?>" name="get_field_name( 'count' ) ); ?>" type="checkbox" /> +

    +

    + + id="get_field_id( 'hide_empty' ) ); ?>" name="get_field_name( 'hide_empty' ) ); ?>" type="checkbox" /> +

    + sprintf( __( 'Display the details of a specific %s', 'easy-digital-downloads' ), edd_get_label_singular() ), + ) + ); + } + + /** @see WP_Widget::widget */ + public function widget( $args, $instance ) { + $args['id'] = ( isset( $args['id'] ) ) ? $args['id'] : 'edd_download_details_widget'; + + if ( ! empty( $instance['download_id'] ) ) { + if ( 'current' === ( $instance['download_id'] ) ) { + $instance['display_type'] = 'current'; + unset( $instance['download_id'] ); + } elseif ( is_numeric( $instance['download_id'] ) ) { + $instance['display_type'] = 'specific'; + } + } + + if ( ! isset( $instance['display_type'] ) || ( 'specific' === $instance['display_type'] && ! isset( $instance['download_id'] ) ) || ( 'current' == $instance['display_type'] && ! is_singular( 'download' ) ) ) { + return; + } + + // set correct download ID. + if ( 'current' == $instance['display_type'] && is_singular( 'download' ) ) { + $download_id = get_the_ID(); + } else { + $download_id = absint( $instance['download_id'] ); + } + + // Since we can take a typed in value, make sure it's a download we're looking for + $download = get_post( $download_id ); + if ( ! is_object( $download ) || 'download' !== $download->post_type ) { + return; + } + + // Variables from widget settings. + $title = apply_filters( 'widget_title', $instance['title'], $instance, $args['id'] ); + $download_title = $instance['download_title'] ? apply_filters( 'edd_product_details_widget_download_title', '

    ' . get_the_title( $download_id ) . '

    ', $download_id ) : ''; + $purchase_button = $instance['purchase_button'] ? apply_filters( 'edd_product_details_widget_purchase_button', edd_get_purchase_link( array( 'download_id' => $download_id ) ), $download_id ) : ''; + $categories = $instance['categories'] ? $instance['categories'] : ''; + $tags = $instance['tags'] ? $instance['tags'] : ''; + + // Used by themes. Opens the widget. + echo $args['before_widget']; + + // Display the widget title. + if ( $title ) { + echo $args['before_title'] . $title . $args['after_title']; + } + + do_action( 'edd_product_details_widget_before_title' , $instance , $download_id ); + + // Download title. + echo $download_title; + + do_action( 'edd_product_details_widget_before_purchase_button' , $instance , $download_id ); + + // Purchase button. + echo $purchase_button; + + // Categories. + $category_list = false; + $category_label = ''; + if ( $categories ) { + $category_terms = (array) get_the_terms( $download_id, 'download_category' ); + + if ( $category_terms && ! is_wp_error( $category_terms ) ) { + $category_list = get_the_term_list( $download_id, 'download_category', '', ', ' ); + $category_count = count( $category_terms ); + $category_labels = edd_get_taxonomy_labels( 'download_category' ); + $category_label = $category_count > 1 ? $category_labels['name'] : $category_labels['singular_name']; + } + } + + // Tags. + $tag_list = false; + $tag_label = ''; + + if ( $tags ) { + $tag_terms = (array) get_the_terms( $download_id, 'download_tag' ); + + if ( $tag_terms && ! is_wp_error( $tag_terms ) ) { + $tag_list = get_the_term_list( $download_id, 'download_tag', '', ', ' ); + $tag_count = count( $tag_terms ); + $tag_taxonomy = edd_get_taxonomy_labels( 'download_tag' ); + $tag_label = $tag_count > 1 ? $tag_taxonomy['name'] : $tag_taxonomy['singular_name']; + } + } + + $text = ''; + + if ( $category_list || $tag_list ) { + $text .= '

    '; + + if ( $category_list ) { + $text .= '%1$s: %2$s
    '; + } + + if ( $tag_list ) { + $text .= '%3$s: %4$s'; + } + + $text .= '

    '; + } + + do_action( 'edd_product_details_widget_before_categories_and_tags', $instance, $download_id ); + + printf( $text, $category_label, $category_list, $tag_label, $tag_list ); + + do_action( 'edd_product_details_widget_before_end', $instance, $download_id ); + + // Used by themes. Closes the widget. + echo $args['after_widget']; + } + + /** @see WP_Widget::form */ + public function form( $instance ) { + // Set up some default widget settings. + $defaults = array( + 'title' => sprintf( __( '%s Details', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'display_type' => 'current', + 'download_id' => false, + 'download_title' => 'on', + 'purchase_button' => 'on', + 'categories' => 'on', + 'tags' => 'on', + ); + + $instance = wp_parse_args( (array) $instance, $defaults ); ?> + + + + +

    + + +

    + +

    +
    + value="current" name="get_field_name( 'display_type' ) ); ?>" id="get_field_id( 'display_type' ) ); ?>-current"> + value="specific" name="get_field_name( 'display_type' ) ); ?>" id="get_field_id( 'display_type' ) ); ?>-specific"> +

    + + + +

    > + + + publish < 1000 ) : ?> + 'download', + 'posts_per_page' => -1, + 'post_status' => 'publish', + ); + $downloads = get_posts( $args ); + ?> + + +
    + + +

    + + +

    + id="get_field_id( 'download_title' ) ); ?>" name="get_field_name( 'download_title' ) ); ?>" type="checkbox" /> + +

    + + +

    + id="get_field_id( 'purchase_button' ) ); ?>" name="get_field_name( 'purchase_button' ) ); ?>" type="checkbox" /> + +

    + + +

    + + id="get_field_id( 'categories' ) ); ?>" name="get_field_name( 'categories' ) ); ?>" type="checkbox" /> + +

    + + +

    + + id="get_field_id( 'tags' ) ); ?>" name="get_field_name( 'tags' ) ); ?>" type="checkbox" /> + +

    + + + -
    -

    - - - - - - - - - - - -
    -
    -
    -

    - - - - - - - - - - - -
    -
    -
    - \n" -"Language-Team: Easy Digital Downloads \n" -"Language: en_US\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_x;_n;esc_attr__;esc_attr_e;esc_html__;esc_html_e;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_x:1,2c\n" -"X-Poedit-Basepath: ../\n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Generator: Poedit 1.5.3\n" -"X-Poedit-SearchPath-0: .\n" - -#: includes/template-functions.php:48 -#, php-format -msgid "No checkout page has been configured. Visit Settings to set one." -msgstr "" - -#: includes/template-functions.php:60 -msgid "Purchase" -msgstr "" - -#: includes/template-functions.php:137 -#: includes/template-functions.php:158 -#: includes/cart-template.php:46 -#: includes/cart-template.php:49 -msgid "Checkout" -msgstr "" - -#: includes/template-functions.php:167 -msgid "added to your cart" -msgstr "" - -#: includes/template-functions.php:263 -msgid "Gray" -msgstr "" - -#: includes/template-functions.php:264 -msgid "Pink" -msgstr "" - -#: includes/template-functions.php:265 -msgid "Blue" -msgstr "" - -#: includes/template-functions.php:266 -msgid "Green" -msgstr "" - -#: includes/template-functions.php:267 -msgid "Teal" -msgstr "" - -#: includes/template-functions.php:268 -msgid "Black" -msgstr "" - -#: includes/template-functions.php:269 -msgid "Dark Gray" -msgstr "" - -#: includes/template-functions.php:270 -msgid "Orange" -msgstr "" - -#: includes/template-functions.php:271 -msgid "Purple" -msgstr "" - -#: includes/template-functions.php:272 -msgid "Slate" -msgstr "" - -#: includes/template-functions.php:293 -msgid "Button" -msgstr "" - -#: includes/template-functions.php:294 -msgid "Plain Text" -msgstr "" - -#: includes/template-functions.php:317 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "" - -#: includes/payment-functions.php:299 -msgid "Pending" -msgstr "" - -#: includes/payment-functions.php:300 -msgid "Complete" -msgstr "" - -#: includes/payment-functions.php:301 -msgid "Refunded" -msgstr "" - -#: includes/email-template.php:23 -msgid "Default Template" -msgstr "" - -#: includes/email-template.php:24 -msgid "No template, plain text only" -msgstr "" - -#: includes/email-template.php:115 -msgid "Sample Product Title" -msgstr "" - -#: includes/email-template.php:118 -msgid "Sample Download File Name" -msgstr "" - -#: includes/email-template.php:118 -msgid "Optional notes about this download." -msgstr "" - -#: includes/email-template.php:129 -msgid "These are some sample notes added to a product." -msgstr "" - -#: includes/email-template.php:168 -msgid "Purchase Receipt Preview" -msgstr "" - -#: includes/email-template.php:168 -msgid "Preview Purchase Receipt" -msgstr "" - -#: includes/email-template.php:209 -msgid "Dear" -msgstr "" - -#: includes/email-template.php:210 -msgid "Thank you for your purchase. Please click on the link(s) below to download your files." -msgstr "" - -#: includes/cart-template.php:80 -msgid "remove" -msgstr "" - -#: includes/cart-template.php:99 -msgid "Your cart is empty." -msgstr "" - -#: includes/register-settings.php:41 -msgid "Test Mode" -msgstr "" - -#: includes/register-settings.php:42 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "" - -#: includes/register-settings.php:47 -msgid "Checkout Page" -msgstr "" - -#: includes/register-settings.php:48 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "" - -#: includes/register-settings.php:54 -msgid "Success Page" -msgstr "" - -#: includes/register-settings.php:55 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "" - -#: includes/register-settings.php:61 -msgid "Download Links on Success Page" -msgstr "" - -#: includes/register-settings.php:62 -msgid "Show a list of all download links on the success page after completing a purchase?" -msgstr "" - -#: includes/register-settings.php:67 -msgid "Currency Settings" -msgstr "" - -#: includes/register-settings.php:68 -msgid "Configure the currency options" -msgstr "" - -#: includes/register-settings.php:73 -msgid "Currency" -msgstr "" - -#: includes/register-settings.php:74 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "" - -#: includes/register-settings.php:80 -msgid "Currency Position" -msgstr "" - -#: includes/register-settings.php:81 -msgid "Choose the location of the currency sign." -msgstr "" - -#: includes/register-settings.php:84 -msgid "Before - $10" -msgstr "" - -#: includes/register-settings.php:85 -msgid "After - 10$" -msgstr "" - -#: includes/register-settings.php:90 -msgid "Thousands Separator" -msgstr "" - -#: includes/register-settings.php:91 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "" - -#: includes/register-settings.php:98 -msgid "Decimal Separator" -msgstr "" - -#: includes/register-settings.php:99 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "" - -#: includes/register-settings.php:110 -msgid "Payment Gateways" -msgstr "" - -#: includes/register-settings.php:111 -msgid "Choose the payment gateways you want to enable." -msgstr "" - -#: includes/register-settings.php:117 -msgid "Accepted Payment Method Icons" -msgstr "" - -#: includes/register-settings.php:118 -msgid "Display icons for the selected payment methods" -msgstr "" - -#: includes/register-settings.php:118 -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "" - -#: includes/register-settings.php:131 -msgid "PayPal Settings" -msgstr "" - -#: includes/register-settings.php:132 -msgid "Configure the PayPal settings" -msgstr "" - -#: includes/register-settings.php:137 -msgid "PayPal Email" -msgstr "" - -#: includes/register-settings.php:138 -msgid "Enter your PayPal account's email" -msgstr "" - -#: includes/register-settings.php:144 -msgid "Alternate PayPal Purchase Verification" -msgstr "" - -#: includes/register-settings.php:145 -msgid "If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal." -msgstr "" - -#: includes/register-settings.php:150 -msgid "Disable PayPal IPN Verification" -msgstr "" - -#: includes/register-settings.php:151 -msgid "If payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases." -msgstr "" - -#: includes/register-settings.php:160 -msgid "Email Template" -msgstr "" - -#: includes/register-settings.php:161 -msgid "Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" to see the new template." -msgstr "" - -#: includes/register-settings.php:173 -msgid "From Name" -msgstr "" - -#: includes/register-settings.php:174 -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "" - -#: includes/register-settings.php:179 -msgid "From Email" -msgstr "" - -#: includes/register-settings.php:180 -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "" - -#: includes/register-settings.php:185 -msgid "Purchase Email Subject" -msgstr "" - -#: includes/register-settings.php:186 -msgid "Enter the subject line for the purchase receipt email" -msgstr "" - -#: includes/register-settings.php:191 -msgid "Purchase Receipt" -msgstr "" - -#: includes/register-settings.php:192 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "" - -#: includes/register-settings.php:193 -msgid "A list of download URLs for each download purchased" -msgstr "" - -#: includes/register-settings.php:194 -msgid "The buyer's name" -msgstr "" - -#: includes/register-settings.php:195 -msgid "The date of the purchase" -msgstr "" - -#: includes/register-settings.php:196 -msgid "The total price of the purchase" -msgstr "" - -#: includes/register-settings.php:197 -msgid "The unique ID number for this purchase receipt" -msgstr "" - -#: includes/register-settings.php:198 -msgid "The method of payment used for this purchase" -msgstr "" - -#: includes/register-settings.php:199 -msgid "Your site name" -msgstr "" - -#: includes/register-settings.php:208 -msgid "Disable Styles" -msgstr "" - -#: includes/register-settings.php:209 -msgid "Check this to disable all included styling" -msgstr "" - -#: includes/register-settings.php:214 -msgid "Buttons" -msgstr "" - -#: includes/register-settings.php:215 -msgid "Options for add to cart and purchase buttons" -msgstr "" - -#: includes/register-settings.php:220 -msgid "Default Button Style" -msgstr "" - -#: includes/register-settings.php:221 -msgid "Choose the style you want to use for the buttons." -msgstr "" - -#: includes/register-settings.php:227 -msgid "Default Button Color" -msgstr "" - -#: includes/register-settings.php:228 -msgid "Choose the color you want to use for the buttons." -msgstr "" - -#: includes/register-settings.php:238 -msgid "Disable Ajax" -msgstr "" - -#: includes/register-settings.php:239 -msgid "Check this to disable AJAX for the shopping cart." -msgstr "" - -#: includes/register-settings.php:244 -msgid "Enable jQuery Validation" -msgstr "" - -#: includes/register-settings.php:245 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "" - -#: includes/register-settings.php:250 -msgid "Disable Guest Checkout" -msgstr "" - -#: includes/register-settings.php:251 -msgid "Require that users be logged-in to purchase files." -msgstr "" - -#: includes/register-settings.php:256 -msgid "Show Register / Login Form?" -msgstr "" - -#: includes/register-settings.php:257 -msgid "Display the registration and login forms on the checkout page for non-logged-in users." -msgstr "" - -#: includes/register-settings.php:262 -msgid "Download Link Expiration" -msgstr "" - -#: includes/register-settings.php:263 -msgid "How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours." -msgstr "" - -#: includes/register-settings.php:269 -msgid "Disable Redownload?" -msgstr "" - -#: includes/register-settings.php:270 -msgid "Check this if you do not want to allow users to redownload items from their purchase history." -msgstr "" - -#: includes/register-settings.php:275 -msgid "Terms of Agreement" -msgstr "" - -#: includes/register-settings.php:281 -msgid "Agree to Terms" -msgstr "" - -#: includes/register-settings.php:282 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing." -msgstr "" - -#: includes/register-settings.php:287 -msgid "Agree to Terms Label" -msgstr "" - -#: includes/register-settings.php:288 -msgid "Label shown next to the agree to terms check box." -msgstr "" - -#: includes/register-settings.php:294 -msgid "Agreement Text" -msgstr "" - -#: includes/register-settings.php:295 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "" - -#: includes/register-settings.php:300 -msgid "Complete Purchase Text" -msgstr "" - -#: includes/register-settings.php:301 -msgid "The button label for completing a purchase." -msgstr "" - -#: includes/register-settings.php:306 -msgid "Add to Cart Text" -msgstr "" - -#: includes/register-settings.php:307 -msgid "Text shown on the Add to Cart Buttons" -msgstr "" - -#: includes/register-settings.php:333 -msgid "General Settings" -msgstr "" - -#: includes/register-settings.php:359 -msgid "Payment Gateway Settings" -msgstr "" - -#: includes/register-settings.php:385 -msgid "Email Settings" -msgstr "" - -#: includes/register-settings.php:411 -msgid "Style Settings" -msgstr "" - -#: includes/register-settings.php:438 -msgid "Misc Settings" -msgstr "" - -#: includes/register-settings.php:727 -msgid "Upload File" -msgstr "" - -#: includes/register-settings.php:765 -msgid "Settings Updated" -msgstr "" - -#: includes/query-filters.php:42 -msgid "You do not have permission to view this file." -msgstr "" - -#: includes/query-filters.php:42 -msgid "Error" -msgstr "" - -#: includes/cart-functions.php:405 -#, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "" - -#: includes/cart-functions.php:406 -msgid "Checkout." -msgstr "" - -#: includes/widgets.php:39 -msgid "Downloads Cart" -msgstr "" - -#: includes/widgets.php:39 -msgid "Display the downloads shopping cart" -msgstr "" - -#: includes/widgets.php:82 -#: includes/widgets.php:162 -msgid "Title:" -msgstr "" - -#: includes/widgets.php:87 -msgid "Show Quantity:" -msgstr "" - -#: includes/widgets.php:112 -msgid "Downloads Categories / Tags" -msgstr "" - -#: includes/widgets.php:112 -msgid "Display the downloads categories or tags" -msgstr "" - -#: includes/widgets.php:167 -msgid "Taxonomy:" -msgstr "" - -#: includes/widgets.php:169 -msgid "Categories" -msgstr "" - -#: includes/widgets.php:170 -msgid "Tags" -msgstr "" - -#: includes/widgets.php:193 -msgid "Purchase History" -msgstr "" - -#: includes/widgets.php:193 -msgid "Display a user's purchase history" -msgstr "" - -#: includes/widgets.php:232 -msgid "No downloadable files found." -msgstr "" - -#: includes/widgets.php:259 -msgid "Title" -msgstr "" - -#: includes/widgets.php:299 -msgid "Easy Digital Downloads Sales Summary" -msgstr "" - -#: includes/widgets.php:318 -msgid "Current Month" -msgstr "" - -#: includes/widgets.php:323 -msgid "Earnings" -msgstr "" - -#: includes/widgets.php:327 -msgid "Sales" -msgstr "" - -#: includes/widgets.php:333 -msgid "Totals" -msgstr "" - -#: includes/widgets.php:338 -msgid "Total Earnings" -msgstr "" - -#: includes/widgets.php:342 -msgid "Total Sales" -msgstr "" - -#: includes/scripts.php:40 -msgid "Please enter a discount code" -msgstr "" - -#: includes/scripts.php:41 -msgid "Discount Applied" -msgstr "" - -#: includes/scripts.php:42 -msgid "Please enter an email address before applying a discount code" -msgstr "" - -#: includes/scripts.php:44 -msgid "You have already added this item to your cart" -msgstr "" - -#: includes/scripts.php:45 -msgid "Your cart is empty" -msgstr "" - -#: includes/scripts.php:46 -msgid "Loading" -msgstr "" - -#: includes/scripts.php:123 -msgid "Add New Download" -msgstr "" - -#: includes/scripts.php:124 -msgid "Use This File" -msgstr "" - -#: includes/scripts.php:125 -msgid "Sorry, not available for variable priced products." -msgstr "" - -#: includes/scripts.php:126 -msgid "Are you sure you wish to delete this payment?" -msgstr "" - -#: includes/scripts.php:127 -msgid "You must have at least one price" -msgstr "" - -#: includes/scripts.php:128 -msgid "You must have at least one file" -msgstr "" - -#: includes/scripts.php:129 -msgid "You must have at least one field" -msgstr "" - -#: includes/ajax-functions.php:102 -msgid "This discount code has been used already" -msgstr "" - -#: includes/ajax-functions.php:118 -#: includes/process-purchase.php:239 -msgid "The discount you entered is invalid" -msgstr "" - -#: includes/login-register.php:45 -msgid "Log into Your Account" -msgstr "" - -#: includes/login-register.php:47 -#: includes/login-register.php:48 -#: includes/checkout-template.php:325 -#: includes/checkout-template.php:326 -#: includes/checkout-template.php:373 -msgid "Username" -msgstr "" - -#: includes/login-register.php:51 -#: includes/checkout-template.php:329 -#: includes/checkout-template.php:330 -#: includes/checkout-template.php:377 -msgid "Password" -msgstr "" - -#: includes/login-register.php:58 -#: includes/checkout-template.php:320 -msgid "Login" -msgstr "" - -#: includes/login-register.php:61 -msgid "Lost Password" -msgstr "" - -#: includes/login-register.php:62 -msgid "Lost Password?" -msgstr "" - -#: includes/login-register.php:69 -msgid "You are already logged in" -msgstr "" - -#: includes/login-register.php:92 -#: includes/process-purchase.php:472 -msgid "The password you entered is incorrect" -msgstr "" - -#: includes/login-register.php:95 -#: includes/process-purchase.php:491 -msgid "The username you entered does not exist" -msgstr "" - -#: includes/misc-functions.php:185 -msgid "US Dollars ($)" -msgstr "" - -#: includes/misc-functions.php:186 -msgid "Euros (€)" -msgstr "" - -#: includes/misc-functions.php:187 -msgid "Pounds Sterling (£)" -msgstr "" - -#: includes/misc-functions.php:188 -msgid "Australian Dollars ($)" -msgstr "" - -#: includes/misc-functions.php:189 -msgid "Brazilian Real ($)" -msgstr "" - -#: includes/misc-functions.php:190 -msgid "Canadian Dollars ($)" -msgstr "" - -#: includes/misc-functions.php:191 -msgid "Czech Koruna" -msgstr "" - -#: includes/misc-functions.php:192 -msgid "Danish Krone" -msgstr "" - -#: includes/misc-functions.php:193 -msgid "Hong Kong Dollar ($)" -msgstr "" - -#: includes/misc-functions.php:194 -msgid "Hungarian Forint" -msgstr "" - -#: includes/misc-functions.php:195 -msgid "Israeli Shekel" -msgstr "" - -#: includes/misc-functions.php:196 -msgid "Japanese Yen (¥)" -msgstr "" - -#: includes/misc-functions.php:197 -msgid "Malaysian Ringgits" -msgstr "" - -#: includes/misc-functions.php:198 -msgid "Mexican Peso ($)" -msgstr "" - -#: includes/misc-functions.php:199 -msgid "New Zealand Dollar ($)" -msgstr "" - -#: includes/misc-functions.php:200 -msgid "Norwegian Krone" -msgstr "" - -#: includes/misc-functions.php:201 -msgid "Philippine Pesos" -msgstr "" - -#: includes/misc-functions.php:202 -msgid "Polish Zloty" -msgstr "" - -#: includes/misc-functions.php:203 -msgid "Singapore Dollar ($)" -msgstr "" - -#: includes/misc-functions.php:204 -msgid "Swedish Krona" -msgstr "" - -#: includes/misc-functions.php:205 -msgid "Swiss Franc" -msgstr "" - -#: includes/misc-functions.php:206 -msgid "Taiwan New Dollars" -msgstr "" - -#: includes/misc-functions.php:207 -msgid "Thai Baht" -msgstr "" - -#: includes/misc-functions.php:208 -msgid "Indian Rupee" -msgstr "" - -#: includes/misc-functions.php:209 -msgid "Turkish Lira" -msgstr "" - -#: includes/misc-functions.php:210 -msgid "Iranian Rial" -msgstr "" - -#: includes/process-download.php:266 -#: includes/process-download.php:283 -msgid "Sorry but this file does not exist." -msgstr "" - -#: includes/process-download.php:294 -msgid "You do not have permission to download this file" -msgstr "" - -#: includes/process-download.php:294 -msgid "Purchase Verification Failed" -msgstr "" - -#: includes/checkout-template.php:101 -msgid "Personal Info" -msgstr "" - -#: includes/checkout-template.php:104 -msgid "Email address" -msgstr "" - -#: includes/checkout-template.php:105 -msgid "Email Address" -msgstr "" - -#: includes/checkout-template.php:109 -#: includes/checkout-template.php:110 -#: includes/checkout-template.php:343 -#: includes/checkout-template.php:344 -msgid "First Name" -msgstr "" - -#: includes/checkout-template.php:113 -#: includes/checkout-template.php:347 -msgid "Last name" -msgstr "" - -#: includes/checkout-template.php:114 -#: includes/checkout-template.php:348 -msgid "Last Name" -msgstr "" - -#: includes/checkout-template.php:142 -msgid "Show Terms" -msgstr "" - -#: includes/checkout-template.php:143 -msgid "Hide Terms" -msgstr "" - -#: includes/checkout-template.php:146 -msgid "Agree to Terms?" -msgstr "" - -#: includes/checkout-template.php:166 -msgid "Go back" -msgstr "" - -#: includes/checkout-template.php:170 -msgid "You must be logged in to complete your purchase" -msgstr "" - -#: includes/checkout-template.php:199 -msgid "Credit Card Info" -msgstr "" - -#: includes/checkout-template.php:201 -msgid "Card name" -msgstr "" - -#: includes/checkout-template.php:202 -msgid "Name on the Card" -msgstr "" - -#: includes/checkout-template.php:205 -msgid "Card number" -msgstr "" - -#: includes/checkout-template.php:206 -msgid "Card Number" -msgstr "" - -#: includes/checkout-template.php:209 -msgid "Security code" -msgstr "" - -#: includes/checkout-template.php:210 -msgid "CVC" -msgstr "" - -#: includes/checkout-template.php:216 -msgid "Month" -msgstr "" - -#: includes/checkout-template.php:218 -msgid "Year" -msgstr "" - -#: includes/checkout-template.php:219 -msgid "Expiration (MM/YYYY)" -msgstr "" - -#: includes/checkout-template.php:249 -msgid "Address line 1" -msgstr "" - -#: includes/checkout-template.php:250 -msgid "Billing Address" -msgstr "" - -#: includes/checkout-template.php:253 -msgid "Address line 2" -msgstr "" - -#: includes/checkout-template.php:254 -msgid "Billing Address Line 2" -msgstr "" - -#: includes/checkout-template.php:257 -msgid "City" -msgstr "" - -#: includes/checkout-template.php:258 -msgid "Billing City" -msgstr "" - -#: includes/checkout-template.php:269 -msgid "Billing Country" -msgstr "" - -#: includes/checkout-template.php:272 -msgid "State / Province" -msgstr "" - -#: includes/checkout-template.php:289 -msgid "Billing State / Province" -msgstr "" - -#: includes/checkout-template.php:292 -msgid "Zip / Postal code" -msgstr "" - -#: includes/checkout-template.php:293 -msgid "Billing Zip / Postal Code" -msgstr "" - -#: includes/checkout-template.php:320 -msgid "Already have an account?" -msgstr "" - -#: includes/checkout-template.php:322 -msgid "Create an account" -msgstr "" - -#: includes/checkout-template.php:322 -msgid "(optional)" -msgstr "" - -#: includes/checkout-template.php:333 -msgid "Confirm password" -msgstr "" - -#: includes/checkout-template.php:334 -msgid "Password Again" -msgstr "" - -#: includes/checkout-template.php:339 -#: includes/checkout-template.php:340 -msgid "Email" -msgstr "" - -#: includes/checkout-template.php:369 -msgid "Login to your account" -msgstr "" - -#: includes/checkout-template.php:372 -msgid "Your username" -msgstr "" - -#: includes/checkout-template.php:376 -msgid "Your password" -msgstr "" - -#: includes/checkout-template.php:384 -msgid "Need to create an account?" -msgstr "" - -#: includes/checkout-template.php:386 -msgid "Register" -msgstr "" - -#: includes/checkout-template.php:386 -msgid "or checkout as a guest." -msgstr "" - -#: includes/checkout-template.php:415 -msgid "Choose Your Payment Method" -msgstr "" - -#: includes/checkout-template.php:443 -msgid "Enter discount" -msgstr "" - -#: includes/checkout-template.php:445 -msgid "Discount" -msgstr "" - -#: includes/checkout-template.php:447 -msgid "Apply Discount" -msgstr "" - -#: includes/checkout-template.php:473 -msgid "Next" -msgstr "" - -#: includes/email-functions.php:62 -msgid "Hello" -msgstr "" - -#: includes/email-functions.php:62 -msgid "A download purchase has been made" -msgstr "" - -#: includes/email-functions.php:63 -msgid "Downloads sold:" -msgstr "" - -#: includes/email-functions.php:76 -msgid "Purchased by: " -msgstr "" - -#: includes/email-functions.php:77 -msgid "Amount: " -msgstr "" - -#: includes/email-functions.php:78 -msgid "Payment Method: " -msgstr "" - -#: includes/email-functions.php:79 -msgid "Thank you" -msgstr "" - -#: includes/email-functions.php:82 -msgid "New download purchase" -msgstr "" - -#: includes/install.php:42 -msgid "Purchase Confirmation" -msgstr "" - -#: includes/install.php:43 -msgid "Thank you for your purchase!" -msgstr "" - -#: includes/process-purchase.php:206 -msgid "The selected gateway is not active" -msgstr "" - -#: includes/process-purchase.php:210 -msgid "No gateway has been selected" -msgstr "" - -#: includes/process-purchase.php:259 -msgid "You must agree to the terms of use" -msgstr "" - -#: includes/process-purchase.php:289 -msgid "Please enter a valid email address." -msgstr "" - -#: includes/process-purchase.php:303 -msgid "The user information is invalid." -msgstr "" - -#: includes/process-purchase.php:349 -msgid "Username already taken" -msgstr "" - -#: includes/process-purchase.php:355 -msgid "Invalid username" -msgstr "" - -#: includes/process-purchase.php:366 -msgid "You must register or login to complete your purchase" -msgstr "" - -#: includes/process-purchase.php:378 -#: includes/process-purchase.php:522 -msgid "Invalid email" -msgstr "" - -#: includes/process-purchase.php:384 -msgid "Email already used" -msgstr "" - -#: includes/process-purchase.php:396 -#: includes/process-purchase.php:529 -msgid "Enter an email" -msgstr "" - -#: includes/process-purchase.php:407 -msgid "Passwords don't match" -msgstr "" - -#: includes/process-purchase.php:422 -#: includes/process-purchase.php:487 -msgid "Enter a password" -msgstr "" - -#: includes/process-purchase.php:427 -msgid "Enter the password confirmation" -msgstr "" - -#: includes/process-purchase.php:454 -msgid "You must login or register to complete your purchase" -msgstr "" - -#: includes/gateway-functions.php:28 -msgid "Test Payment" -msgstr "" - -#: includes/post-types.php:43 -#: includes/post-types.php:81 -msgid "Add New" -msgstr "" - -#: includes/post-types.php:44 -#, php-format -msgid "Add New %1$s" -msgstr "" - -#: includes/post-types.php:45 -#, php-format -msgid "Edit %1$s" -msgstr "" - -#: includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "" - -#: includes/post-types.php:47 -#, php-format -msgid "All %2$s" -msgstr "" - -#: includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "" - -#: includes/post-types.php:49 -#, php-format -msgid "Search %2$s" -msgstr "" - -#: includes/post-types.php:50 -#, php-format -msgid "No %2$s found" -msgstr "" - -#: includes/post-types.php:51 -#, php-format -msgid "No %2$s found in Trash" -msgstr "" - -#: includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "" - -#: includes/post-types.php:79 -msgctxt "post type general name" -msgid "Payments" -msgstr "" - -#: includes/post-types.php:80 -msgctxt "post type singular name" -msgid "Payment" -msgstr "" - -#: includes/post-types.php:82 -msgid "Add New Payment" -msgstr "" - -#: includes/post-types.php:83 -msgid "Edit Payment" -msgstr "" - -#: includes/post-types.php:84 -msgid "New Payment" -msgstr "" - -#: includes/post-types.php:85 -msgid "All Payments" -msgstr "" - -#: includes/post-types.php:86 -msgid "View Payment" -msgstr "" - -#: includes/post-types.php:87 -msgid "Search Payments" -msgstr "" - -#: includes/post-types.php:88 -msgid "No Payments found" -msgstr "" - -#: includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "" - -#: includes/post-types.php:91 -msgid "Payment History" -msgstr "" - -#: includes/post-types.php:125 -msgid "Download" -msgstr "" - -#: includes/post-types.php:126 -msgid "Downloads" -msgstr "" - -#: includes/post-types.php:173 -msgctxt "taxonomy general name" -msgid "Categories" -msgstr "" - -#: includes/post-types.php:174 -msgctxt "taxonomy singular name" -msgid "Category" -msgstr "" - -#: includes/post-types.php:175 -msgid "Search Categories" -msgstr "" - -#: includes/post-types.php:176 -msgid "All Categories" -msgstr "" - -#: includes/post-types.php:177 -msgid "Parent Category" -msgstr "" - -#: includes/post-types.php:178 -msgid "Parent Category:" -msgstr "" - -#: includes/post-types.php:179 -msgid "Edit Category" -msgstr "" - -#: includes/post-types.php:180 -msgid "Update Category" -msgstr "" - -#: includes/post-types.php:181 -msgid "Add New Category" -msgstr "" - -#: includes/post-types.php:182 -msgid "New Category Name" -msgstr "" - -#: includes/post-types.php:198 -msgctxt "taxonomy general name" -msgid "Tags" -msgstr "" - -#: includes/post-types.php:199 -msgctxt "taxonomy singular name" -msgid "Tag" -msgstr "" - -#: includes/post-types.php:200 -msgid "Search Tags" -msgstr "" - -#: includes/post-types.php:201 -msgid "All Tags" -msgstr "" - -#: includes/post-types.php:202 -msgid "Parent Tag" -msgstr "" - -#: includes/post-types.php:203 -msgid "Parent Tag:" -msgstr "" - -#: includes/post-types.php:204 -msgid "Edit Tag" -msgstr "" - -#: includes/post-types.php:205 -msgid "Update Tag" -msgstr "" - -#: includes/post-types.php:206 -msgid "Add New Tag" -msgstr "" - -#: includes/post-types.php:207 -msgid "New Tag Name" -msgstr "" - -#: includes/post-types.php:239 -#: includes/post-types.php:240 -msgid "Download updated." -msgstr "" - -#: includes/post-types.php:241 -msgid "Download published." -msgstr "" - -#: includes/post-types.php:242 -msgid "Download saved." -msgstr "" - -#: includes/post-types.php:243 -msgid "Download submitted." -msgstr "" - -#: includes/shortcodes.php:194 -msgid "Purchase All Items" -msgstr "" - -#: includes/shortcodes.php:336 -#, php-format -msgctxt "download post type name" -msgid "No %s found" -msgstr "" - -#: includes/gateways/paypal.php:271 -msgid "Invalid IPN" -msgstr "" - -#: includes/templates/history-purchases.php:11 -msgid "Purchase ID" -msgstr "" - -#: includes/templates/history-purchases.php:12 -#: includes/admin/export-functions.php:48 -msgid "Date" -msgstr "" - -#: includes/templates/history-purchases.php:13 -msgid "Amount" -msgstr "" - -#: includes/templates/history-purchases.php:14 -#: includes/templates/history-downloads.php:12 -msgid "Files" -msgstr "" - -#: includes/templates/history-purchases.php:61 -msgid "You have not made any purchases" -msgstr "" - -#: includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "" - -#: includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "" - -#: includes/templates/checkout_cart.php:8 -msgid "Actions" -msgstr "" - -#: includes/templates/checkout_cart.php:49 -msgid "Total" -msgstr "" - -#: includes/templates/history-downloads.php:10 -msgid "Download Name" -msgstr "" - -#: includes/templates/history-downloads.php:68 -msgid "You have not purchased any downloads" -msgstr "" - -#: includes/admin/export-functions.php:39 -msgid "ID" -msgstr "" - -#: includes/admin/export-functions.php:43 -msgid "Products" -msgstr "" - -#: includes/admin/export-functions.php:44 -msgid "Discounts," -msgstr "" - -#: includes/admin/export-functions.php:45 -msgid "Amount paid" -msgstr "" - -#: includes/admin/export-functions.php:46 -msgid "Payment method" -msgstr "" - -#: includes/admin/export-functions.php:47 -msgid "Key" -msgstr "" - -#: includes/admin/export-functions.php:49 -msgid "User" -msgstr "" - -#: includes/admin/export-functions.php:50 -msgid "Status" -msgstr "" - -#: includes/admin/export-functions.php:111 -#: includes/admin/export-functions.php:119 -msgid "none" -msgstr "" - -#: includes/admin/export-functions.php:127 -msgid "guest" -msgstr "" - -#: includes/admin/export-functions.php:135 -msgid "No payments recorded yet" -msgstr "" - -#: includes/admin/export-functions.php:169 -msgid "Export not allowed for non-administrators." -msgstr "" - -#: includes/admin/thickbox.php:29 -#: includes/admin/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "" - -#: includes/admin/thickbox.php:30 -msgid "Insert Download" -msgstr "" - -#: includes/admin/thickbox.php:65 -msgid "You must choose a download" -msgstr "" - -#: includes/admin/thickbox.php:88 -#, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "" - -#: includes/admin/thickbox.php:91 -#, php-format -msgid "Choose a %s" -msgstr "" - -#: includes/admin/thickbox.php:100 -msgid "Choose a style" -msgstr "" - -#: includes/admin/thickbox.php:111 -msgid "Choose a button color" -msgstr "" - -#: includes/admin/thickbox.php:120 -msgid "Link text . . ." -msgstr "" - -#: includes/admin/thickbox.php:124 -msgid "Cancel" -msgstr "" - -#: includes/admin/thickbox.php:126 -msgid "Button Styles" -msgstr "" - -#: includes/admin/admin-pages.php:27 -#: includes/admin/discounts/discount-codes.php:34 -msgid "Discount Codes" -msgstr "" - -#: includes/admin/admin-pages.php:28 -msgid "Earnings and Sales Reports" -msgstr "" - -#: includes/admin/admin-pages.php:28 -#: includes/admin/reporting/reports.php:28 -msgid "Reports" -msgstr "" - -#: includes/admin/admin-pages.php:29 -msgid "Easy Digital Download Settings" -msgstr "" - -#: includes/admin/admin-pages.php:29 -msgid "Settings" -msgstr "" - -#: includes/admin/admin-pages.php:30 -msgid "Easy Digital Download Add Ons" -msgstr "" - -#: includes/admin/admin-pages.php:30 -msgid "Add Ons" -msgstr "" - -#: includes/admin/admin-notices.php:30 -msgid "Discount code updated." -msgstr "" - -#: includes/admin/admin-notices.php:33 -msgid "There was a problem updating your discount code, please try again." -msgstr "" - -#: includes/admin/admin-notices.php:36 -msgid "The payment has been deleted." -msgstr "" - -#: includes/admin/admin-notices.php:39 -msgid "The purchase receipt has been resent." -msgstr "" - -#: includes/admin/admin-notices.php:44 -#, php-format -msgid "The payment history needs updated. %s" -msgstr "" - -#: includes/admin/admin-notices.php:44 -msgid "Click to Upgrade" -msgstr "" - -#: includes/admin/admin-notices.php:62 -msgid "There seems to be an issue with the server. Please try again in a few minutes." -msgstr "" - -#: includes/admin/add-ons.php:69 -msgid "Add Ons for Easy Digital Downloads" -msgstr "" - -#: includes/admin/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "" - -#: includes/admin/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "" - -#: includes/admin/downloads/dashboard-columns.php:26 -#: includes/admin/discounts/edit-discount.php:23 -#: includes/admin/discounts/discount-codes.php:39 -#: includes/admin/discounts/discount-codes.php:52 -#: includes/admin/discounts/add-discount.php:18 -msgid "Name" -msgstr "" - -#: includes/admin/downloads/dashboard-columns.php:29 -#: includes/admin/downloads/dashboard-columns.php:242 -#: includes/admin/downloads/metabox.php:171 -#: includes/admin/payments/payments-history.php:142 -#: includes/admin/payments/payments-history.php:158 -#: includes/admin/reporting/pdf-reports.php:66 -msgid "Price" -msgstr "" - -#: includes/admin/downloads/dashboard-columns.php:32 -msgid "Short Code" -msgstr "" - -#: includes/admin/downloads/dashboard-columns.php:200 -msgid "Show all categories" -msgstr "" - -#: includes/admin/downloads/dashboard-columns.php:211 -msgid "Show all tags" -msgstr "" - -#: includes/admin/downloads/dashboard-columns.php:239 -#, php-format -msgid "%s Data" -msgstr "" - -#: includes/admin/downloads/metabox.php:23 -#, php-format -msgid "%1$s Configuration" -msgstr "" - -#: includes/admin/downloads/metabox.php:26 -msgid "Product Notes" -msgstr "" - -#: includes/admin/downloads/metabox.php:29 -#, php-format -msgid "%1$s Stats" -msgstr "" - -#: includes/admin/downloads/metabox.php:32 -msgid "Purchase Log" -msgstr "" - -#: includes/admin/downloads/metabox.php:35 -msgid "File Download Log" -msgstr "" - -#: includes/admin/downloads/metabox.php:145 -msgid "Pricing Options:" -msgstr "" - -#: includes/admin/downloads/metabox.php:151 -msgid "Enable variable pricing" -msgstr "" - -#: includes/admin/downloads/metabox.php:170 -#: includes/admin/downloads/metabox.php:233 -msgid "Option Name" -msgstr "" - -#: includes/admin/downloads/metabox.php:199 -msgid "Add New Price" -msgstr "" - -#: includes/admin/downloads/metabox.php:275 -msgid "File Downloads:" -msgstr "" - -#: includes/admin/downloads/metabox.php:284 -#: includes/admin/downloads/metabox.php:351 -msgid "File Name" -msgstr "" - -#: includes/admin/downloads/metabox.php:285 -msgid "File URL" -msgstr "" - -#: includes/admin/downloads/metabox.php:286 -msgid "Price Assignment" -msgstr "" - -#: includes/admin/downloads/metabox.php:314 -msgid "Add New File" -msgstr "" - -#: includes/admin/downloads/metabox.php:355 -msgid "http://" -msgstr "" - -#: includes/admin/downloads/metabox.php:358 -msgid "Upload a File" -msgstr "" - -#: includes/admin/downloads/metabox.php:364 -msgid "All Prices" -msgstr "" - -#: includes/admin/downloads/metabox.php:391 -msgid "Button Options" -msgstr "" - -#: includes/admin/downloads/metabox.php:397 -msgid "Disable the automatic output of the purchase button" -msgstr "" - -#: includes/admin/downloads/metabox.php:457 -msgid "Special notes or instructions for this product. These notes will be added to the purchase receipt." -msgstr "" - -#: includes/admin/downloads/metabox.php:479 -msgid "Sales:" -msgstr "" - -#: includes/admin/downloads/metabox.php:485 -msgid "Earnings:" -msgstr "" - -#: includes/admin/downloads/metabox.php:521 -msgid "Sales Log" -msgstr "" - -#: includes/admin/downloads/metabox.php:523 -msgid "Each sale for this download is listed below." -msgstr "" - -#: includes/admin/downloads/metabox.php:537 -#: includes/admin/downloads/metabox.php:629 -msgid "Date:" -msgstr "" - -#: includes/admin/downloads/metabox.php:541 -msgid "Buyer:" -msgstr "" - -#: includes/admin/downloads/metabox.php:545 -msgid "Purchase ID:" -msgstr "" - -#: includes/admin/downloads/metabox.php:553 -msgid "No sales yet" -msgstr "" - -#: includes/admin/downloads/metabox.php:569 -#: includes/admin/downloads/metabox.php:666 -#: includes/admin/payments/payments-history.php:318 -msgid "Previous" -msgstr "" - -#: includes/admin/downloads/metabox.php:610 -msgid "Download Log" -msgstr "" - -#: includes/admin/downloads/metabox.php:612 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "" - -#: includes/admin/downloads/metabox.php:633 -msgid "Downloaded by:" -msgstr "" - -#: includes/admin/downloads/metabox.php:637 -msgid "IP Address:" -msgstr "" - -#: includes/admin/downloads/metabox.php:641 -msgid "File: " -msgstr "" - -#: includes/admin/downloads/metabox.php:650 -msgid "No file downloads yet yet" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:13 -msgid "Something went wrong." -msgstr "" - -#: includes/admin/discounts/edit-discount.php:17 -msgid "Edit Discount" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:17 -#: includes/admin/payments/edit-payment.php:16 -msgid "Go Back" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:27 -#: includes/admin/discounts/add-discount.php:22 -msgid "The name of this discount" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:32 -#: includes/admin/discounts/discount-codes.php:40 -#: includes/admin/discounts/discount-codes.php:53 -#: includes/admin/discounts/add-discount.php:27 -msgid "Code" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:36 -#: includes/admin/discounts/add-discount.php:31 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:41 -#: includes/admin/discounts/add-discount.php:36 -msgid "Type" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:45 -#: includes/admin/discounts/add-discount.php:40 -msgid "Percentage" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:46 -#: includes/admin/discounts/add-discount.php:41 -msgid "Flat amount" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:48 -#: includes/admin/discounts/add-discount.php:43 -msgid "The kind of discount to apply for this discount." -msgstr "" - -#: includes/admin/discounts/edit-discount.php:57 -#: includes/admin/discounts/add-discount.php:52 -msgid "The amount of this discount code." -msgstr "" - -#: includes/admin/discounts/edit-discount.php:62 -#: includes/admin/discounts/add-discount.php:57 -msgid "Start date" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:66 -#: includes/admin/discounts/add-discount.php:61 -msgid "Enter the start date for this discount code in the format of mm/dd/yyyy. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "" - -#: includes/admin/discounts/edit-discount.php:71 -#: includes/admin/discounts/add-discount.php:66 -msgid "Expiration date" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:75 -#: includes/admin/discounts/add-discount.php:70 -msgid "Enter the expiration date for this discount code in the format of mm/dd/yyyy. For no expiration, leave blank" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:80 -#: includes/admin/discounts/discount-codes.php:43 -#: includes/admin/discounts/discount-codes.php:56 -#: includes/admin/discounts/add-discount.php:84 -msgid "Max Uses" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:84 -#: includes/admin/discounts/add-discount.php:88 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "" - -#: includes/admin/discounts/edit-discount.php:89 -#: includes/admin/discounts/add-discount.php:75 -msgid "Minimum Amount" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:93 -#: includes/admin/discounts/add-discount.php:79 -msgid "The minimum amount that must be purchased before this discount can be used. Leave blank for no minimum." -msgstr "" - -#: includes/admin/discounts/edit-discount.php:102 -msgid "Active" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:103 -msgid "Inactive" -msgstr "" - -#: includes/admin/discounts/edit-discount.php:105 -msgid "The status of this discount code." -msgstr "" - -#: includes/admin/discounts/edit-discount.php:115 -msgid "Update Discount Code" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:42 -#: includes/admin/discounts/discount-codes.php:55 -msgid "Uses" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:44 -#: includes/admin/discounts/discount-codes.php:57 -msgid "Start Date" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:45 -#: includes/admin/discounts/discount-codes.php:58 -msgid "Expiration" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:82 -#: includes/admin/discounts/discount-codes.php:84 -msgid "unlimited" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:93 -msgid "No start date" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:100 -msgid "Expired" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:102 -msgid "no expiration" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:108 -#: includes/admin/payments/payments-history.php:183 -msgid "Edit" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:110 -msgid "Deactivate" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:112 -msgid "Activate" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:114 -#: includes/admin/payments/payments-history.php:185 -msgid "Delete" -msgstr "" - -#: includes/admin/discounts/discount-codes.php:119 -msgid "No discount codes have been created." -msgstr "" - -#: includes/admin/discounts/add-discount.php:12 -msgid "Add New Discount" -msgstr "" - -#: includes/admin/discounts/add-discount.php:96 -msgid "Add Discount Code" -msgstr "" - -#: includes/admin/payments/payments-history.php:90 -msgid "All" -msgstr "" - -#: includes/admin/payments/payments-history.php:95 -msgid "Completed" -msgstr "" - -#: includes/admin/payments/payments-history.php:106 -msgid "Export" -msgstr "" - -#: includes/admin/payments/payments-history.php:110 -msgid "Payment mode" -msgstr "" - -#: includes/admin/payments/payments-history.php:112 -msgid "Live" -msgstr "" - -#: includes/admin/payments/payments-history.php:113 -msgid "Test" -msgstr "" - -#: includes/admin/payments/payments-history.php:123 -msgid "Payments per page" -msgstr "" - -#: includes/admin/payments/payments-history.php:125 -msgid "Show" -msgstr "" - -#: includes/admin/payments/payments-history.php:131 -msgid "Showing payments for: " -msgstr "" - -#: includes/admin/payments/payments-history.php:131 -msgid "clear" -msgstr "" - -#: includes/admin/payments/payments-history.php:184 -msgid "Resend Purchase Receipt" -msgstr "" - -#: includes/admin/payments/payments-history.php:197 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "" - -#: includes/admin/payments/payments-history.php:197 -msgid "View Order Details" -msgstr "" - -#: includes/admin/payments/payments-history.php:205 -msgid "Purchased File" -msgstr "" - -#: includes/admin/payments/payments-history.php:205 -msgid "Purchased Files" -msgstr "" - -#: includes/admin/payments/payments-history.php:249 -msgid "Date and Time:" -msgstr "" - -#: includes/admin/payments/payments-history.php:250 -msgid "Discount used:" -msgstr "" - -#: includes/admin/payments/payments-history.php:251 -msgid "Total:" -msgstr "" - -#: includes/admin/payments/payments-history.php:254 -msgid "Buyer's Personal Details:" -msgstr "" - -#: includes/admin/payments/payments-history.php:256 -msgid "Name:" -msgstr "" - -#: includes/admin/payments/payments-history.php:257 -msgid "Email:" -msgstr "" - -#: includes/admin/payments/payments-history.php:266 -msgid "Payment Method:" -msgstr "" - -#: includes/admin/payments/payments-history.php:271 -msgid "Purchase Key" -msgstr "" - -#: includes/admin/payments/payments-history.php:274 -#: includes/admin/payments/edit-payment.php:92 -msgid "Close" -msgstr "" - -#: includes/admin/payments/payments-history.php:304 -msgid "Total Earnings:" -msgstr "" - -#: includes/admin/payments/edit-payment.php:22 -msgid "Buyer's Email" -msgstr "" - -#: includes/admin/payments/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "" - -#: includes/admin/payments/edit-payment.php:31 -msgid "Downloads Purchased" -msgstr "" - -#: includes/admin/payments/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "" - -#: includes/admin/payments/edit-payment.php:43 -msgid "Add download to purchase" -msgstr "" - -#: includes/admin/payments/edit-payment.php:48 -msgid "Payment Status" -msgstr "" - -#: includes/admin/payments/edit-payment.php:64 -msgid "Send Purchase Receipt" -msgstr "" - -#: includes/admin/payments/edit-payment.php:68 -msgid "Check this box to send the purchase receipt, including all download links." -msgstr "" - -#: includes/admin/payments/edit-payment.php:78 -msgid "Update Payment" -msgstr "" - -#: includes/admin/payments/edit-payment.php:91 -msgid "Add Selected Downloads" -msgstr "" - -#: includes/admin/reporting/reports.php:41 -msgid "Download Sales and Earnings PDF Report for all Products" -msgstr "" - -#: includes/admin/reporting/reports.php:42 -msgid "Download a CSV Customers List" -msgstr "" - -#: includes/admin/reporting/reports.php:44 -msgid "Please Note: Transactions created while in test mode are not included on this page or in the PDF reports." -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:36 -msgid "to" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:41 -#: includes/admin/reporting/pdf-reports.php:52 -msgid "Sales and earnings reports for the current year for all products" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:42 -#: includes/admin/reporting/pdf-reports.php:43 -msgid "Easy Digital Downloads" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:57 -msgid "Date Range: " -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:61 -msgid "Table View" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:65 -msgid "Product Name" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:69 -msgid "Number of Sales" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:70 -msgid "Earnings to Date" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:112 -msgid "No Downloads found." -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:119 -msgid "Graph View" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:212 -msgid "Sales and Earnings by Month for all Products" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:226 -msgid "Jan" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:227 -msgid "Feb" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:228 -msgid "Mar" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:229 -msgid "Apr" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:230 -msgid "May" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:231 -msgid "June" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:232 -msgid "July" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:233 -msgid "Aug" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:234 -msgid "Sept" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:235 -msgid "Oct" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:236 -msgid "Nov" -msgstr "" - -#: includes/admin/reporting/pdf-reports.php:237 -msgid "Dec" -msgstr "" - -#: includes/admin/reporting/graphing.php:42 -#, php-format -msgid "%s Performance in Sales" -msgstr "" - -#: includes/admin/reporting/graphing.php:88 -#, php-format -msgid "%s Performance in Earnings" -msgstr "" - -#: includes/admin/reporting/graphing.php:136 -msgid "Earnings per month" -msgstr "" - -#: includes/admin/reporting/graphing.php:168 -msgid "Day" -msgstr "" - -#: includes/admin/reporting/graphing.php:189 -#, php-format -msgid "Earnings per day for last %s days" -msgstr "" - -#: includes/admin/reporting/graphing.php:236 -msgid "Sales per month" -msgstr "" - -#: includes/admin/settings/settings.php:33 -msgid "General" -msgstr "" - -#: includes/admin/settings/settings.php:35 -msgid "Emails" -msgstr "" - -#: includes/admin/settings/settings.php:36 -msgid "Styles" -msgstr "" - -#: includes/admin/settings/settings.php:37 -msgid "Misc" -msgstr "" - diff --git a/languages/easy-digital-downloads.pot b/languages/easy-digital-downloads.pot new file mode 100644 index 00000000000..798d7cfe07d --- /dev/null +++ b/languages/easy-digital-downloads.pot @@ -0,0 +1,20422 @@ +# Copyright (C) 2023 Easy Digital Downloads +# This file is distributed under the same license as the Easy Digital Downloads (Pro) plugin. +msgid "" +msgstr "" +"Project-Id-Version: Easy Digital Downloads (Pro) 3.1.5\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/easy-digital-downloads-pro\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2023-07-21T20:11:51+00:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.8.1\n" + +#. Plugin Name of the plugin +msgid "Easy Digital Downloads (Pro)" +msgstr "" + +#. Plugin URI of the plugin +#. Author URI of the plugin +msgid "https://easydigitaldownloads.com" +msgstr "" + +#. Description of the plugin +msgid "The easiest way to sell digital products with WordPress." +msgstr "" + +#. Author of the plugin +#: includes/blocks/includes/functions.php:27 +#: src/Admin/Onboarding/Steps/Tools.php:128 +#: src/Admin/SiteHealth/Test.php:21 +msgid "Easy Digital Downloads" +msgstr "" + +#: easy-digital-downloads.php:132 +msgid "Heads up!" +msgstr "" + +#: easy-digital-downloads.php:133 +msgid "Your site already has Easy Digital Downloads (Pro) activated. If you want to switch to Easy Digital Downloads, please first go to Plugins → Installed Plugins and deactivate Easy Digital Downloads (Pro). Then, you can activate Easy Digital Downloads." +msgstr "" + +#: i18n/countries.php:7 +msgid "United States" +msgstr "" + +#: i18n/countries.php:8 +msgid "Canada" +msgstr "" + +#: i18n/countries.php:9 +msgid "United Kingdom" +msgstr "" + +#: i18n/countries.php:10 +msgid "Afghanistan" +msgstr "" + +#: i18n/countries.php:11 +msgid "Åland Islands" +msgstr "" + +#: i18n/countries.php:12 +msgid "Albania" +msgstr "" + +#: i18n/countries.php:13 +msgid "Algeria" +msgstr "" + +#: i18n/countries.php:14 +#: i18n/states-us.php:58 +msgid "American Samoa" +msgstr "" + +#: i18n/countries.php:15 +msgid "Andorra" +msgstr "" + +#: i18n/countries.php:16 +msgid "Angola" +msgstr "" + +#: i18n/countries.php:17 +msgid "Anguilla" +msgstr "" + +#: i18n/countries.php:18 +msgid "Antarctica" +msgstr "" + +#: i18n/countries.php:19 +msgid "Antigua and Barbuda" +msgstr "" + +#: i18n/countries.php:20 +msgid "Argentina" +msgstr "" + +#: i18n/countries.php:21 +msgid "Armenia" +msgstr "" + +#: i18n/countries.php:22 +msgid "Aruba" +msgstr "" + +#: i18n/countries.php:23 +msgid "Australia" +msgstr "" + +#: i18n/countries.php:24 +msgid "Austria" +msgstr "" + +#: i18n/countries.php:25 +msgid "Azerbaijan" +msgstr "" + +#: i18n/countries.php:26 +msgid "Bahamas" +msgstr "" + +#: i18n/countries.php:27 +msgid "Bahrain" +msgstr "" + +#: i18n/countries.php:28 +msgid "Bangladesh" +msgstr "" + +#: i18n/countries.php:29 +msgid "Barbados" +msgstr "" + +#: i18n/countries.php:30 +msgid "Belarus" +msgstr "" + +#: i18n/countries.php:31 +msgid "Belgium" +msgstr "" + +#: i18n/countries.php:32 +msgid "Belize" +msgstr "" + +#: i18n/countries.php:33 +msgid "Benin" +msgstr "" + +#: i18n/countries.php:34 +msgid "Bermuda" +msgstr "" + +#: i18n/countries.php:35 +msgid "Bhutan" +msgstr "" + +#: i18n/countries.php:36 +msgid "Bolivia" +msgstr "" + +#: i18n/countries.php:37 +msgid "Bonaire, Saint Eustatius and Saba" +msgstr "" + +#: i18n/countries.php:38 +msgid "Bosnia and Herzegovina" +msgstr "" + +#: i18n/countries.php:39 +msgid "Botswana" +msgstr "" + +#: i18n/countries.php:40 +msgid "Bouvet Island" +msgstr "" + +#: i18n/countries.php:41 +msgid "Brazil" +msgstr "" + +#: i18n/countries.php:42 +msgid "British Indian Ocean Territory" +msgstr "" + +#: i18n/countries.php:43 +msgid "Brunei Darrussalam" +msgstr "" + +#: i18n/countries.php:44 +msgid "Bulgaria" +msgstr "" + +#: i18n/countries.php:45 +msgid "Burkina Faso" +msgstr "" + +#: i18n/countries.php:46 +msgid "Burundi" +msgstr "" + +#: i18n/countries.php:47 +msgid "Cambodia" +msgstr "" + +#: i18n/countries.php:48 +msgid "Cameroon" +msgstr "" + +#: i18n/countries.php:49 +msgid "Cape Verde" +msgstr "" + +#: i18n/countries.php:50 +msgid "Cayman Islands" +msgstr "" + +#: i18n/countries.php:51 +msgid "Central African Republic" +msgstr "" + +#: i18n/countries.php:52 +msgid "Chad" +msgstr "" + +#: i18n/countries.php:53 +msgid "Chile" +msgstr "" + +#: i18n/countries.php:54 +msgid "China" +msgstr "" + +#: i18n/countries.php:55 +msgid "Christmas Island" +msgstr "" + +#: i18n/countries.php:56 +msgid "Cocos Islands" +msgstr "" + +#: i18n/countries.php:57 +msgid "Colombia" +msgstr "" + +#: i18n/countries.php:58 +msgid "Comoros" +msgstr "" + +#: i18n/countries.php:59 +msgid "Congo, Democratic People's Republic" +msgstr "" + +#: i18n/countries.php:60 +msgid "Congo, Republic of" +msgstr "" + +#: i18n/countries.php:61 +msgid "Cook Islands" +msgstr "" + +#: i18n/countries.php:62 +msgid "Costa Rica" +msgstr "" + +#: i18n/countries.php:63 +msgid "Cote d'Ivoire" +msgstr "" + +#: i18n/countries.php:64 +msgid "Croatia/Hrvatska" +msgstr "" + +#: i18n/countries.php:65 +msgid "Cuba" +msgstr "" + +#: i18n/countries.php:66 +msgid "CuraÇao" +msgstr "" + +#: i18n/countries.php:67 +msgid "Cyprus" +msgstr "" + +#: i18n/countries.php:68 +msgid "Czechia" +msgstr "" + +#: i18n/countries.php:69 +msgid "Denmark" +msgstr "" + +#: i18n/countries.php:70 +msgid "Djibouti" +msgstr "" + +#: i18n/countries.php:71 +msgid "Dominica" +msgstr "" + +#: i18n/countries.php:72 +msgid "Dominican Republic" +msgstr "" + +#: i18n/countries.php:73 +msgid "East Timor" +msgstr "" + +#: i18n/countries.php:74 +msgid "Ecuador" +msgstr "" + +#: i18n/countries.php:75 +msgid "Egypt" +msgstr "" + +#: i18n/countries.php:76 +msgid "Equatorial Guinea" +msgstr "" + +#: i18n/countries.php:77 +msgid "El Salvador" +msgstr "" + +#: i18n/countries.php:78 +msgid "Eritrea" +msgstr "" + +#: i18n/countries.php:79 +msgid "Estonia" +msgstr "" + +#: i18n/countries.php:80 +msgid "Ethiopia" +msgstr "" + +#: i18n/countries.php:81 +msgid "Falkland Islands" +msgstr "" + +#: i18n/countries.php:82 +msgid "Faroe Islands" +msgstr "" + +#: i18n/countries.php:83 +msgid "Fiji" +msgstr "" + +#: i18n/countries.php:84 +msgid "Finland" +msgstr "" + +#: i18n/countries.php:85 +msgid "France" +msgstr "" + +#: i18n/countries.php:86 +msgid "French Guiana" +msgstr "" + +#: i18n/countries.php:87 +msgid "French Polynesia" +msgstr "" + +#: i18n/countries.php:88 +msgid "French Southern Territories" +msgstr "" + +#: i18n/countries.php:89 +msgid "Gabon" +msgstr "" + +#: i18n/countries.php:90 +msgid "Gambia" +msgstr "" + +#: i18n/countries.php:91 +#: i18n/states-us.php:17 +msgid "Georgia" +msgstr "" + +#: i18n/countries.php:92 +msgid "Germany" +msgstr "" + +#: i18n/countries.php:93 +msgid "Greece" +msgstr "" + +#: i18n/countries.php:94 +msgid "Ghana" +msgstr "" + +#: i18n/countries.php:95 +msgid "Gibraltar" +msgstr "" + +#: i18n/countries.php:96 +msgid "Greenland" +msgstr "" + +#: i18n/countries.php:97 +msgid "Grenada" +msgstr "" + +#: i18n/countries.php:98 +msgid "Guadeloupe" +msgstr "" + +#: i18n/countries.php:99 +#: i18n/states-us.php:62 +msgid "Guam" +msgstr "" + +#: i18n/countries.php:100 +msgid "Guatemala" +msgstr "" + +#: i18n/countries.php:101 +msgid "Guernsey" +msgstr "" + +#: i18n/countries.php:102 +msgid "Guinea" +msgstr "" + +#: i18n/countries.php:103 +msgid "Guinea-Bissau" +msgstr "" + +#: i18n/countries.php:104 +msgid "Guyana" +msgstr "" + +#: i18n/countries.php:105 +msgid "Haiti" +msgstr "" + +#: i18n/countries.php:106 +msgid "Heard and McDonald Islands" +msgstr "" + +#: i18n/countries.php:107 +msgid "Holy See (City Vatican State)" +msgstr "" + +#: i18n/countries.php:108 +msgid "Honduras" +msgstr "" + +#: i18n/countries.php:109 +msgid "Hong Kong" +msgstr "" + +#: i18n/countries.php:110 +msgid "Hungary" +msgstr "" + +#: i18n/countries.php:111 +msgid "Iceland" +msgstr "" + +#: i18n/countries.php:112 +msgid "India" +msgstr "" + +#: i18n/countries.php:113 +msgid "Indonesia" +msgstr "" + +#: i18n/countries.php:114 +msgid "Iran" +msgstr "" + +#: i18n/countries.php:115 +msgid "Iraq" +msgstr "" + +#: i18n/countries.php:116 +msgid "Ireland" +msgstr "" + +#: i18n/countries.php:117 +msgid "Isle of Man" +msgstr "" + +#: i18n/countries.php:118 +msgid "Israel" +msgstr "" + +#: i18n/countries.php:119 +msgid "Italy" +msgstr "" + +#: i18n/countries.php:120 +msgid "Jamaica" +msgstr "" + +#: i18n/countries.php:121 +msgid "Japan" +msgstr "" + +#: i18n/countries.php:122 +msgid "Jersey" +msgstr "" + +#: i18n/countries.php:123 +msgid "Jordan" +msgstr "" + +#: i18n/countries.php:124 +msgid "Kazakhstan" +msgstr "" + +#: i18n/countries.php:125 +msgid "Kenya" +msgstr "" + +#: i18n/countries.php:126 +msgid "Kiribati" +msgstr "" + +#: i18n/countries.php:127 +msgid "Kuwait" +msgstr "" + +#: i18n/countries.php:128 +msgid "Kyrgyzstan" +msgstr "" + +#: i18n/countries.php:129 +msgid "Lao People's Democratic Republic" +msgstr "" + +#: i18n/countries.php:130 +msgid "Latvia" +msgstr "" + +#: i18n/countries.php:131 +msgid "Lebanon" +msgstr "" + +#: i18n/countries.php:132 +msgid "Lesotho" +msgstr "" + +#: i18n/countries.php:133 +msgid "Liberia" +msgstr "" + +#: i18n/countries.php:134 +msgid "Libyan Arab Jamahiriya" +msgstr "" + +#: i18n/countries.php:135 +msgid "Liechtenstein" +msgstr "" + +#: i18n/countries.php:136 +msgid "Lithuania" +msgstr "" + +#: i18n/countries.php:137 +msgid "Luxembourg" +msgstr "" + +#: i18n/countries.php:138 +msgid "Macau" +msgstr "" + +#: i18n/countries.php:139 +msgid "Macedonia" +msgstr "" + +#: i18n/countries.php:140 +msgid "Madagascar" +msgstr "" + +#: i18n/countries.php:141 +msgid "Malawi" +msgstr "" + +#: i18n/countries.php:142 +msgid "Malaysia" +msgstr "" + +#: i18n/countries.php:143 +msgid "Maldives" +msgstr "" + +#: i18n/countries.php:144 +msgid "Mali" +msgstr "" + +#: i18n/countries.php:145 +msgid "Malta" +msgstr "" + +#: i18n/countries.php:146 +#: i18n/states-us.php:63 +msgid "Marshall Islands" +msgstr "" + +#: i18n/countries.php:147 +msgid "Martinique" +msgstr "" + +#: i18n/countries.php:148 +msgid "Mauritania" +msgstr "" + +#: i18n/countries.php:149 +msgid "Mauritius" +msgstr "" + +#: i18n/countries.php:150 +msgid "Mayotte" +msgstr "" + +#: i18n/countries.php:151 +msgid "Mexico" +msgstr "" + +#: i18n/countries.php:152 +msgid "Micronesia" +msgstr "" + +#: i18n/countries.php:153 +msgid "Moldova, Republic of" +msgstr "" + +#: i18n/countries.php:154 +msgid "Monaco" +msgstr "" + +#: i18n/countries.php:155 +msgid "Mongolia" +msgstr "" + +#: i18n/countries.php:156 +msgid "Montenegro" +msgstr "" + +#: i18n/countries.php:157 +msgid "Montserrat" +msgstr "" + +#: i18n/countries.php:158 +msgid "Morocco" +msgstr "" + +#: i18n/countries.php:159 +msgid "Mozambique" +msgstr "" + +#: i18n/countries.php:160 +msgid "Myanmar" +msgstr "" + +#: i18n/countries.php:161 +msgid "Namibia" +msgstr "" + +#: i18n/countries.php:162 +msgid "Nauru" +msgstr "" + +#: i18n/countries.php:163 +msgid "Nepal" +msgstr "" + +#: i18n/countries.php:164 +msgid "Netherlands" +msgstr "" + +#: i18n/countries.php:165 +msgid "Netherlands Antilles" +msgstr "" + +#: i18n/countries.php:166 +msgid "New Caledonia" +msgstr "" + +#: i18n/countries.php:167 +msgid "New Zealand" +msgstr "" + +#: i18n/countries.php:168 +msgid "Nicaragua" +msgstr "" + +#: i18n/countries.php:169 +msgid "Niger" +msgstr "" + +#: i18n/countries.php:170 +msgid "Nigeria" +msgstr "" + +#: i18n/countries.php:171 +msgid "Niue" +msgstr "" + +#: i18n/countries.php:172 +msgid "Norfolk Island" +msgstr "" + +#: i18n/countries.php:173 +msgid "North Korea" +msgstr "" + +#: i18n/countries.php:174 +#: i18n/states-us.php:64 +msgid "Northern Mariana Islands" +msgstr "" + +#: i18n/countries.php:175 +msgid "Norway" +msgstr "" + +#: i18n/countries.php:176 +msgid "Oman" +msgstr "" + +#: i18n/countries.php:177 +msgid "Pakistan" +msgstr "" + +#: i18n/countries.php:178 +#: i18n/states-us.php:65 +msgid "Palau" +msgstr "" + +#: i18n/countries.php:179 +msgid "Palestinian Territories" +msgstr "" + +#: i18n/countries.php:180 +msgid "Panama" +msgstr "" + +#: i18n/countries.php:181 +msgid "Papua New Guinea" +msgstr "" + +#: i18n/countries.php:182 +msgid "Paraguay" +msgstr "" + +#: i18n/countries.php:183 +msgid "Peru" +msgstr "" + +#: i18n/countries.php:184 +msgid "Philippines" +msgstr "" + +#: i18n/countries.php:185 +msgid "Pitcairn Island" +msgstr "" + +#: i18n/countries.php:186 +msgid "Poland" +msgstr "" + +#: i18n/countries.php:187 +msgid "Portugal" +msgstr "" + +#: i18n/countries.php:188 +#: i18n/states-us.php:67 +msgid "Puerto Rico" +msgstr "" + +#: i18n/countries.php:189 +msgid "Qatar" +msgstr "" + +#: i18n/countries.php:190 +msgid "Republic of Kosovo" +msgstr "" + +#: i18n/countries.php:191 +msgid "Reunion Island" +msgstr "" + +#: i18n/countries.php:192 +msgid "Romania" +msgstr "" + +#: i18n/countries.php:193 +msgid "Russian Federation" +msgstr "" + +#: i18n/countries.php:194 +msgid "Rwanda" +msgstr "" + +#: i18n/countries.php:195 +msgid "Saint Barthélemy" +msgstr "" + +#: i18n/countries.php:196 +msgid "Saint Helena" +msgstr "" + +#: i18n/countries.php:197 +msgid "Saint Kitts and Nevis" +msgstr "" + +#: i18n/countries.php:198 +msgid "Saint Lucia" +msgstr "" + +#: i18n/countries.php:199 +msgid "Saint Martin (French)" +msgstr "" + +#: i18n/countries.php:200 +msgid "Saint Martin (Dutch)" +msgstr "" + +#: i18n/countries.php:201 +msgid "Saint Pierre and Miquelon" +msgstr "" + +#: i18n/countries.php:202 +msgid "Saint Vincent and the Grenadines" +msgstr "" + +#: i18n/countries.php:203 +msgid "San Marino" +msgstr "" + +#: i18n/countries.php:204 +msgid "São Tomé and Príncipe" +msgstr "" + +#: i18n/countries.php:205 +msgid "Saudi Arabia" +msgstr "" + +#: i18n/countries.php:206 +msgid "Senegal" +msgstr "" + +#: i18n/countries.php:207 +msgid "Serbia" +msgstr "" + +#: i18n/countries.php:208 +msgid "Seychelles" +msgstr "" + +#: i18n/countries.php:209 +msgid "Sierra Leone" +msgstr "" + +#: i18n/countries.php:210 +msgid "Singapore" +msgstr "" + +#: i18n/countries.php:211 +msgid "Slovak Republic" +msgstr "" + +#: i18n/countries.php:212 +msgid "Slovenia" +msgstr "" + +#: i18n/countries.php:213 +msgid "Solomon Islands" +msgstr "" + +#: i18n/countries.php:214 +msgid "Somalia" +msgstr "" + +#: i18n/countries.php:215 +msgid "South Africa" +msgstr "" + +#: i18n/countries.php:216 +msgid "South Georgia" +msgstr "" + +#: i18n/countries.php:217 +msgid "South Korea" +msgstr "" + +#: i18n/countries.php:218 +msgid "South Sudan" +msgstr "" + +#: i18n/countries.php:219 +msgid "Spain" +msgstr "" + +#: i18n/countries.php:220 +msgid "Sri Lanka" +msgstr "" + +#: i18n/countries.php:221 +msgid "Sudan" +msgstr "" + +#: i18n/countries.php:222 +msgid "Suriname" +msgstr "" + +#: i18n/countries.php:223 +msgid "Svalbard and Jan Mayen Islands" +msgstr "" + +#: i18n/countries.php:224 +msgid "Swaziland" +msgstr "" + +#: i18n/countries.php:225 +msgid "Sweden" +msgstr "" + +#: i18n/countries.php:226 +msgid "Switzerland" +msgstr "" + +#: i18n/countries.php:227 +msgid "Syrian Arab Republic" +msgstr "" + +#: i18n/countries.php:228 +msgid "Taiwan" +msgstr "" + +#: i18n/countries.php:229 +msgid "Tajikistan" +msgstr "" + +#: i18n/countries.php:230 +msgid "Tanzania" +msgstr "" + +#: i18n/countries.php:231 +msgid "Thailand" +msgstr "" + +#: i18n/countries.php:232 +msgid "Timor-Leste" +msgstr "" + +#: i18n/countries.php:233 +msgid "Togo" +msgstr "" + +#: i18n/countries.php:234 +msgid "Tokelau" +msgstr "" + +#: i18n/countries.php:235 +msgid "Tonga" +msgstr "" + +#: i18n/countries.php:236 +msgid "Trinidad and Tobago" +msgstr "" + +#: i18n/countries.php:237 +msgid "Tunisia" +msgstr "" + +#: i18n/countries.php:238 +msgid "Turkey" +msgstr "" + +#: i18n/countries.php:239 +msgid "Turkmenistan" +msgstr "" + +#: i18n/countries.php:240 +msgid "Turks and Caicos Islands" +msgstr "" + +#: i18n/countries.php:241 +msgid "Tuvalu" +msgstr "" + +#: i18n/countries.php:242 +msgid "Uganda" +msgstr "" + +#: i18n/countries.php:243 +msgid "Ukraine" +msgstr "" + +#: i18n/countries.php:244 +msgid "United Arab Emirates" +msgstr "" + +#: i18n/countries.php:245 +msgid "Uruguay" +msgstr "" + +#: i18n/countries.php:246 +msgid "US Minor Outlying Islands" +msgstr "" + +#: i18n/countries.php:247 +msgid "Uzbekistan" +msgstr "" + +#: i18n/countries.php:248 +msgid "Vanuatu" +msgstr "" + +#: i18n/countries.php:249 +msgid "Venezuela" +msgstr "" + +#: i18n/countries.php:250 +msgid "Vietnam" +msgstr "" + +#: i18n/countries.php:251 +msgid "Virgin Islands (British)" +msgstr "" + +#: i18n/countries.php:252 +msgid "Virgin Islands (USA)" +msgstr "" + +#: i18n/countries.php:253 +msgid "Wallis and Futuna Islands" +msgstr "" + +#: i18n/countries.php:254 +msgid "Western Sahara" +msgstr "" + +#: i18n/countries.php:255 +msgid "Western Samoa" +msgstr "" + +#: i18n/countries.php:256 +msgid "Yemen" +msgstr "" + +#: i18n/countries.php:257 +msgid "Zambia" +msgstr "" + +#: i18n/countries.php:258 +msgid "Zimbabwe" +msgstr "" + +#: i18n/states-bd.php:7 +msgid "Bagerhat" +msgstr "" + +#: i18n/states-bd.php:8 +msgid "Bandarban" +msgstr "" + +#: i18n/states-bd.php:9 +msgid "Barguna" +msgstr "" + +#: i18n/states-bd.php:10 +msgid "Barisal" +msgstr "" + +#: i18n/states-bd.php:11 +msgid "Bhola" +msgstr "" + +#: i18n/states-bd.php:12 +msgid "Bogra" +msgstr "" + +#: i18n/states-bd.php:13 +msgid "Brahmanbaria" +msgstr "" + +#: i18n/states-bd.php:14 +msgid "Chandpur" +msgstr "" + +#: i18n/states-bd.php:15 +msgid "Chittagong" +msgstr "" + +#: i18n/states-bd.php:16 +msgid "Chuadanga" +msgstr "" + +#: i18n/states-bd.php:17 +msgid "Comilla" +msgstr "" + +#: i18n/states-bd.php:18 +msgid "Cox's Bazar" +msgstr "" + +#: i18n/states-bd.php:19 +msgid "Dhaka" +msgstr "" + +#: i18n/states-bd.php:20 +msgid "Dinajpur" +msgstr "" + +#: i18n/states-bd.php:21 +msgid "Faridpur" +msgstr "" + +#: i18n/states-bd.php:22 +msgid "Feni" +msgstr "" + +#: i18n/states-bd.php:23 +msgid "Gaibandha" +msgstr "" + +#: i18n/states-bd.php:24 +msgid "Gazipur" +msgstr "" + +#: i18n/states-bd.php:25 +msgid "Gopalganj" +msgstr "" + +#: i18n/states-bd.php:26 +msgid "Habiganj" +msgstr "" + +#: i18n/states-bd.php:27 +msgid "Jamalpur" +msgstr "" + +#: i18n/states-bd.php:28 +msgid "Jessore" +msgstr "" + +#: i18n/states-bd.php:29 +msgid "Jhalokati" +msgstr "" + +#: i18n/states-bd.php:30 +msgid "Jhenaidah" +msgstr "" + +#: i18n/states-bd.php:31 +msgid "Joypurhat" +msgstr "" + +#: i18n/states-bd.php:32 +msgid "Khagrachhari" +msgstr "" + +#: i18n/states-bd.php:33 +msgid "Khulna" +msgstr "" + +#: i18n/states-bd.php:34 +msgid "Kishoreganj" +msgstr "" + +#: i18n/states-bd.php:35 +msgid "Kurigram" +msgstr "" + +#: i18n/states-bd.php:36 +msgid "Kushtia" +msgstr "" + +#: i18n/states-bd.php:37 +msgid "Lakshmipur" +msgstr "" + +#: i18n/states-bd.php:38 +msgid "Lalmonirhat" +msgstr "" + +#: i18n/states-bd.php:39 +msgid "Madaripur" +msgstr "" + +#: i18n/states-bd.php:40 +msgid "Magura" +msgstr "" + +#: i18n/states-bd.php:41 +msgid "Manikganj" +msgstr "" + +#: i18n/states-bd.php:42 +msgid "Meherpur" +msgstr "" + +#: i18n/states-bd.php:43 +msgid "Moulvibazar" +msgstr "" + +#: i18n/states-bd.php:44 +msgid "Munshiganj" +msgstr "" + +#: i18n/states-bd.php:45 +msgid "Mymensingh" +msgstr "" + +#: i18n/states-bd.php:46 +msgid "Naogaon" +msgstr "" + +#: i18n/states-bd.php:47 +msgid "Narail" +msgstr "" + +#: i18n/states-bd.php:48 +msgid "Narayanganj" +msgstr "" + +#: i18n/states-bd.php:49 +msgid "Narsingdi" +msgstr "" + +#: i18n/states-bd.php:50 +msgid "Natore" +msgstr "" + +#: i18n/states-bd.php:51 +msgid "Nawabganj" +msgstr "" + +#: i18n/states-bd.php:52 +msgid "Netrakona" +msgstr "" + +#: i18n/states-bd.php:53 +msgid "Nilphamari" +msgstr "" + +#: i18n/states-bd.php:54 +msgid "Noakhali" +msgstr "" + +#: i18n/states-bd.php:55 +msgid "Pabna" +msgstr "" + +#: i18n/states-bd.php:56 +msgid "Panchagarh" +msgstr "" + +#: i18n/states-bd.php:57 +msgid "Patuakhali" +msgstr "" + +#: i18n/states-bd.php:58 +msgid "Pirojpur" +msgstr "" + +#: i18n/states-bd.php:59 +msgid "Rajbari" +msgstr "" + +#: i18n/states-bd.php:60 +msgid "Rajshahi" +msgstr "" + +#: i18n/states-bd.php:61 +msgid "Rangamati" +msgstr "" + +#: i18n/states-bd.php:62 +msgid "Rangpur" +msgstr "" + +#: i18n/states-bd.php:63 +msgid "Satkhira" +msgstr "" + +#: i18n/states-bd.php:64 +msgid "Shariatpur" +msgstr "" + +#: i18n/states-bd.php:65 +msgid "Sherpur" +msgstr "" + +#: i18n/states-bd.php:66 +msgid "Sirajganj" +msgstr "" + +#: i18n/states-bd.php:67 +msgid "Sunamganj" +msgstr "" + +#: i18n/states-bd.php:68 +msgid "Sylhet" +msgstr "" + +#: i18n/states-bd.php:69 +msgid "Tangail" +msgstr "" + +#: i18n/states-bd.php:70 +msgid "Thakurgaon" +msgstr "" + +#: i18n/states-es.php:7 +msgid "A Coruña" +msgstr "" + +#: i18n/states-es.php:8 +msgid "Araba" +msgstr "" + +#: i18n/states-es.php:9 +msgid "Albacete" +msgstr "" + +#: i18n/states-es.php:10 +msgid "Alicante" +msgstr "" + +#: i18n/states-es.php:11 +msgid "Almería" +msgstr "" + +#: i18n/states-es.php:12 +msgid "Asturias" +msgstr "" + +#: i18n/states-es.php:13 +msgid "Ávila" +msgstr "" + +#: i18n/states-es.php:14 +msgid "Badajoz" +msgstr "" + +#: i18n/states-es.php:15 +msgid "Baleares" +msgstr "" + +#: i18n/states-es.php:16 +msgid "Barcelona" +msgstr "" + +#: i18n/states-es.php:17 +msgid "Burgos" +msgstr "" + +#: i18n/states-es.php:18 +msgid "Cáceres" +msgstr "" + +#: i18n/states-es.php:19 +msgid "Cádiz" +msgstr "" + +#: i18n/states-es.php:20 +msgid "Cantabria" +msgstr "" + +#: i18n/states-es.php:21 +msgid "Castellón" +msgstr "" + +#: i18n/states-es.php:22 +msgid "Ceuta" +msgstr "" + +#: i18n/states-es.php:23 +msgid "Ciudad Real" +msgstr "" + +#: i18n/states-es.php:24 +msgid "Córdoba" +msgstr "" + +#: i18n/states-es.php:25 +msgid "Cuenca" +msgstr "" + +#: i18n/states-es.php:26 +msgid "Girona" +msgstr "" + +#: i18n/states-es.php:27 +msgid "Granada" +msgstr "" + +#: i18n/states-es.php:28 +msgid "Guadalajara" +msgstr "" + +#: i18n/states-es.php:29 +msgid "Gipuzkoa" +msgstr "" + +#: i18n/states-es.php:30 +msgid "Huelva" +msgstr "" + +#: i18n/states-es.php:31 +msgid "Huesca" +msgstr "" + +#: i18n/states-es.php:32 +msgid "Jaén" +msgstr "" + +#: i18n/states-es.php:33 +msgid "La Rioja" +msgstr "" + +#: i18n/states-es.php:34 +msgid "Las Palmas" +msgstr "" + +#: i18n/states-es.php:35 +msgid "León" +msgstr "" + +#: i18n/states-es.php:36 +msgid "Lleida" +msgstr "" + +#: i18n/states-es.php:37 +msgid "Lugo" +msgstr "" + +#: i18n/states-es.php:38 +msgid "Madrid" +msgstr "" + +#: i18n/states-es.php:39 +msgid "Málaga" +msgstr "" + +#: i18n/states-es.php:40 +msgid "Melilla" +msgstr "" + +#: i18n/states-es.php:41 +msgid "Murcia" +msgstr "" + +#: i18n/states-es.php:42 +msgid "Navarra" +msgstr "" + +#: i18n/states-es.php:43 +msgid "Ourense" +msgstr "" + +#: i18n/states-es.php:44 +msgid "Palencia" +msgstr "" + +#: i18n/states-es.php:45 +msgid "Pontevedra" +msgstr "" + +#: i18n/states-es.php:46 +msgid "Salamanca" +msgstr "" + +#: i18n/states-es.php:47 +msgid "Santa Cruz de Tenerife" +msgstr "" + +#: i18n/states-es.php:48 +msgid "Segovia" +msgstr "" + +#: i18n/states-es.php:49 +msgid "Sevilla" +msgstr "" + +#: i18n/states-es.php:50 +msgid "Soria" +msgstr "" + +#: i18n/states-es.php:51 +msgid "Tarragona" +msgstr "" + +#: i18n/states-es.php:52 +msgid "Teruel" +msgstr "" + +#: i18n/states-es.php:53 +msgid "Toledo" +msgstr "" + +#: i18n/states-es.php:54 +msgid "Valencia" +msgstr "" + +#: i18n/states-es.php:55 +msgid "Valladolid" +msgstr "" + +#: i18n/states-es.php:56 +msgid "Bizkaia" +msgstr "" + +#: i18n/states-es.php:57 +msgid "Zamora" +msgstr "" + +#: i18n/states-es.php:58 +msgid "Zaragoza" +msgstr "" + +#: i18n/states-gb.php:7 +msgid "Aberdeen City" +msgstr "" + +#: i18n/states-gb.php:8 +msgid "Aberdeenshire" +msgstr "" + +#: i18n/states-gb.php:9 +msgid "Angus" +msgstr "" + +#: i18n/states-gb.php:10 +msgid "Antrim and Newtownabbey" +msgstr "" + +#: i18n/states-gb.php:11 +msgid "Ards and North Down" +msgstr "" + +#: i18n/states-gb.php:12 +msgid "Argyll and Bute" +msgstr "" + +#: i18n/states-gb.php:13 +msgid "Armagh, Banbridge and Craigavon" +msgstr "" + +#: i18n/states-gb.php:14 +msgid "Barking and Dagenham" +msgstr "" + +#: i18n/states-gb.php:15 +msgid "Barnet" +msgstr "" + +#: i18n/states-gb.php:16 +msgid "Barnsley" +msgstr "" + +#: i18n/states-gb.php:17 +msgid "Bath and North East Somerset" +msgstr "" + +#: i18n/states-gb.php:18 +msgid "Bedford" +msgstr "" + +#: i18n/states-gb.php:19 +msgid "Belfast" +msgstr "" + +#: i18n/states-gb.php:20 +msgid "Bexley" +msgstr "" + +#: i18n/states-gb.php:21 +msgid "Birmingham" +msgstr "" + +#: i18n/states-gb.php:22 +msgid "Blackburn with Darwen" +msgstr "" + +#: i18n/states-gb.php:23 +msgid "Blackpool" +msgstr "" + +#: i18n/states-gb.php:24 +msgid "Blaenau Gwent" +msgstr "" + +#: i18n/states-gb.php:25 +msgid "Bolton" +msgstr "" + +#: i18n/states-gb.php:26 +msgid "Bournemouth" +msgstr "" + +#: i18n/states-gb.php:27 +msgid "Bracknell Forest" +msgstr "" + +#: i18n/states-gb.php:28 +msgid "Bradford" +msgstr "" + +#: i18n/states-gb.php:29 +msgid "Brent" +msgstr "" + +#: i18n/states-gb.php:30 +msgid "Bridgend" +msgstr "" + +#: i18n/states-gb.php:31 +msgid "Brighton and Hove" +msgstr "" + +#: i18n/states-gb.php:32 +msgid "Bristol, City of" +msgstr "" + +#: i18n/states-gb.php:33 +msgid "Bromley" +msgstr "" + +#: i18n/states-gb.php:34 +msgid "Buckinghamshire" +msgstr "" + +#: i18n/states-gb.php:35 +msgid "Bury" +msgstr "" + +#: i18n/states-gb.php:36 +msgid "Caerphilly" +msgstr "" + +#: i18n/states-gb.php:37 +msgid "Calderdale" +msgstr "" + +#: i18n/states-gb.php:38 +msgid "Cambridgeshire" +msgstr "" + +#: i18n/states-gb.php:39 +msgid "Camden" +msgstr "" + +#: i18n/states-gb.php:40 +msgid "Cardiff" +msgstr "" + +#: i18n/states-gb.php:41 +msgid "Carmarthenshire" +msgstr "" + +#: i18n/states-gb.php:42 +msgid "Causeway Coast and Glens" +msgstr "" + +#: i18n/states-gb.php:43 +msgid "Central Bedfordshire" +msgstr "" + +#: i18n/states-gb.php:44 +msgid "Ceredigion" +msgstr "" + +#: i18n/states-gb.php:45 +msgid "Cheshire East" +msgstr "" + +#: i18n/states-gb.php:46 +msgid "Cheshire West and Chester" +msgstr "" + +#: i18n/states-gb.php:47 +msgid "Clackmannanshire" +msgstr "" + +#: i18n/states-gb.php:48 +msgid "Conwy" +msgstr "" + +#: i18n/states-gb.php:49 +msgid "Cornwall" +msgstr "" + +#: i18n/states-gb.php:50 +msgid "Coventry" +msgstr "" + +#: i18n/states-gb.php:51 +msgid "Croydon" +msgstr "" + +#: i18n/states-gb.php:52 +msgid "Cumbria" +msgstr "" + +#: i18n/states-gb.php:53 +msgid "Darlington" +msgstr "" + +#: i18n/states-gb.php:54 +msgid "Denbighshire" +msgstr "" + +#: i18n/states-gb.php:55 +msgid "Derby" +msgstr "" + +#: i18n/states-gb.php:56 +msgid "Derbyshire" +msgstr "" + +#: i18n/states-gb.php:57 +msgid "Derry and Strabane" +msgstr "" + +#: i18n/states-gb.php:58 +msgid "Devon" +msgstr "" + +#: i18n/states-gb.php:59 +msgid "Doncaster" +msgstr "" + +#: i18n/states-gb.php:60 +msgid "Dorset" +msgstr "" + +#: i18n/states-gb.php:61 +msgid "Dudley" +msgstr "" + +#: i18n/states-gb.php:62 +msgid "Dumfries and Galloway" +msgstr "" + +#: i18n/states-gb.php:63 +msgid "Dundee City" +msgstr "" + +#: i18n/states-gb.php:64 +msgid "Durham, County" +msgstr "" + +#: i18n/states-gb.php:65 +msgid "Ealing" +msgstr "" + +#: i18n/states-gb.php:66 +msgid "East Ayrshire" +msgstr "" + +#: i18n/states-gb.php:67 +msgid "East Dunbartonshire" +msgstr "" + +#: i18n/states-gb.php:68 +msgid "East Lothian" +msgstr "" + +#: i18n/states-gb.php:69 +msgid "East Renfrewshire" +msgstr "" + +#: i18n/states-gb.php:70 +msgid "East Riding of Yorkshire" +msgstr "" + +#: i18n/states-gb.php:71 +msgid "East Sussex" +msgstr "" + +#: i18n/states-gb.php:72 +msgid "Edinburgh, City of" +msgstr "" + +#: i18n/states-gb.php:73 +msgid "Eilean Siar" +msgstr "" + +#: i18n/states-gb.php:74 +msgid "Enfield" +msgstr "" + +#: i18n/states-gb.php:75 +msgid "Essex" +msgstr "" + +#: i18n/states-gb.php:76 +msgid "Falkirk" +msgstr "" + +#: i18n/states-gb.php:77 +msgid "Fermanagh and Omagh" +msgstr "" + +#: i18n/states-gb.php:78 +msgid "Fife" +msgstr "" + +#: i18n/states-gb.php:79 +msgid "Flintshire" +msgstr "" + +#: i18n/states-gb.php:80 +msgid "Gateshead" +msgstr "" + +#: i18n/states-gb.php:81 +msgid "Glasgow City" +msgstr "" + +#: i18n/states-gb.php:82 +msgid "Gloucestershire" +msgstr "" + +#: i18n/states-gb.php:83 +msgid "Greenwich" +msgstr "" + +#: i18n/states-gb.php:84 +msgid "Gwynedd" +msgstr "" + +#: i18n/states-gb.php:85 +msgid "Hackney" +msgstr "" + +#: i18n/states-gb.php:86 +msgid "Halton" +msgstr "" + +#: i18n/states-gb.php:87 +msgid "Hammersmith and Fulham" +msgstr "" + +#: i18n/states-gb.php:88 +msgid "Hampshire" +msgstr "" + +#: i18n/states-gb.php:89 +msgid "Haringey" +msgstr "" + +#: i18n/states-gb.php:90 +msgid "Harrow" +msgstr "" + +#: i18n/states-gb.php:91 +msgid "Hartlepool" +msgstr "" + +#: i18n/states-gb.php:92 +msgid "Havering" +msgstr "" + +#: i18n/states-gb.php:93 +msgid "Herefordshire" +msgstr "" + +#: i18n/states-gb.php:94 +msgid "Hertfordshire" +msgstr "" + +#: i18n/states-gb.php:95 +msgid "Highland" +msgstr "" + +#: i18n/states-gb.php:96 +msgid "Hillingdon" +msgstr "" + +#: i18n/states-gb.php:97 +msgid "Hounslow" +msgstr "" + +#: i18n/states-gb.php:98 +msgid "Inverclyde" +msgstr "" + +#: i18n/states-gb.php:99 +msgid "Isle of Anglesey" +msgstr "" + +#: i18n/states-gb.php:100 +msgid "Isle of Wight" +msgstr "" + +#: i18n/states-gb.php:101 +msgid "Isles of Scilly" +msgstr "" + +#: i18n/states-gb.php:102 +msgid "Islington" +msgstr "" + +#: i18n/states-gb.php:103 +msgid "Kensington and Chelsea" +msgstr "" + +#: i18n/states-gb.php:104 +msgid "Kent" +msgstr "" + +#: i18n/states-gb.php:105 +msgid "Kingston upon Hull" +msgstr "" + +#: i18n/states-gb.php:106 +msgid "Kingston upon Thames" +msgstr "" + +#: i18n/states-gb.php:107 +msgid "Kirklees" +msgstr "" + +#: i18n/states-gb.php:108 +msgid "Knowsley" +msgstr "" + +#: i18n/states-gb.php:109 +msgid "Lambeth" +msgstr "" + +#: i18n/states-gb.php:110 +msgid "Lancashire" +msgstr "" + +#: i18n/states-gb.php:111 +msgid "Leeds" +msgstr "" + +#: i18n/states-gb.php:112 +msgid "Leicester" +msgstr "" + +#: i18n/states-gb.php:113 +msgid "Leicestershire" +msgstr "" + +#: i18n/states-gb.php:114 +msgid "Lewisham" +msgstr "" + +#: i18n/states-gb.php:115 +msgid "Lincolnshire" +msgstr "" + +#: i18n/states-gb.php:116 +msgid "Lisburn and Castlereagh" +msgstr "" + +#: i18n/states-gb.php:117 +msgid "Liverpool" +msgstr "" + +#: i18n/states-gb.php:118 +msgid "London, City of" +msgstr "" + +#: i18n/states-gb.php:119 +msgid "Luton" +msgstr "" + +#: i18n/states-gb.php:120 +msgid "Manchester" +msgstr "" + +#: i18n/states-gb.php:121 +msgid "Medway" +msgstr "" + +#: i18n/states-gb.php:122 +msgid "Merthyr Tydfil" +msgstr "" + +#: i18n/states-gb.php:123 +msgid "Merton" +msgstr "" + +#: i18n/states-gb.php:124 +msgid "Mid and East Antrim" +msgstr "" + +#: i18n/states-gb.php:125 +msgid "Mid Ulster" +msgstr "" + +#: i18n/states-gb.php:126 +msgid "Middlesbrough" +msgstr "" + +#: i18n/states-gb.php:127 +msgid "Midlothian" +msgstr "" + +#: i18n/states-gb.php:128 +msgid "Milton Keynes" +msgstr "" + +#: i18n/states-gb.php:129 +msgid "Monmouthshire" +msgstr "" + +#: i18n/states-gb.php:130 +msgid "Moray" +msgstr "" + +#: i18n/states-gb.php:131 +msgid "Neath Port Talbot" +msgstr "" + +#: i18n/states-gb.php:132 +msgid "Newcastle upon Tyne" +msgstr "" + +#: i18n/states-gb.php:133 +msgid "Newham" +msgstr "" + +#: i18n/states-gb.php:134 +msgid "Newport" +msgstr "" + +#: i18n/states-gb.php:135 +msgid "Newry, Mourne and Down" +msgstr "" + +#: i18n/states-gb.php:136 +msgid "Norfolk" +msgstr "" + +#: i18n/states-gb.php:137 +msgid "North Ayrshire" +msgstr "" + +#: i18n/states-gb.php:138 +msgid "North East Lincolnshire" +msgstr "" + +#: i18n/states-gb.php:139 +msgid "North Lanarkshire" +msgstr "" + +#: i18n/states-gb.php:140 +msgid "North Lincolnshire" +msgstr "" + +#: i18n/states-gb.php:141 +msgid "North Somerset" +msgstr "" + +#: i18n/states-gb.php:142 +msgid "North Tyneside" +msgstr "" + +#: i18n/states-gb.php:143 +msgid "North Yorkshire" +msgstr "" + +#: i18n/states-gb.php:144 +msgid "Northamptonshire" +msgstr "" + +#: i18n/states-gb.php:145 +msgid "Northumberland" +msgstr "" + +#: i18n/states-gb.php:146 +msgid "Nottingham" +msgstr "" + +#: i18n/states-gb.php:147 +msgid "Nottinghamshire" +msgstr "" + +#: i18n/states-gb.php:148 +msgid "Oldham" +msgstr "" + +#: i18n/states-gb.php:149 +msgid "Orkney Islands" +msgstr "" + +#: i18n/states-gb.php:150 +msgid "Oxfordshire" +msgstr "" + +#: i18n/states-gb.php:151 +msgid "Pembrokeshire" +msgstr "" + +#: i18n/states-gb.php:152 +msgid "Perth and Kinross" +msgstr "" + +#: i18n/states-gb.php:153 +msgid "Peterborough" +msgstr "" + +#: i18n/states-gb.php:154 +msgid "Plymouth" +msgstr "" + +#: i18n/states-gb.php:155 +msgid "Poole" +msgstr "" + +#: i18n/states-gb.php:156 +msgid "Portsmouth" +msgstr "" + +#: i18n/states-gb.php:157 +msgid "Powys" +msgstr "" + +#: i18n/states-gb.php:158 +msgid "Reading" +msgstr "" + +#: i18n/states-gb.php:159 +msgid "Redbridge" +msgstr "" + +#: i18n/states-gb.php:160 +msgid "Redcar and Cleveland" +msgstr "" + +#: i18n/states-gb.php:161 +msgid "Renfrewshire" +msgstr "" + +#: i18n/states-gb.php:162 +msgid "Rhondda, Cynon, Taff" +msgstr "" + +#: i18n/states-gb.php:163 +msgid "Richmond upon Thames" +msgstr "" + +#: i18n/states-gb.php:164 +msgid "Rochdale" +msgstr "" + +#: i18n/states-gb.php:165 +msgid "Rotherham" +msgstr "" + +#: i18n/states-gb.php:166 +msgid "Rutland" +msgstr "" + +#: i18n/states-gb.php:167 +msgid "Salford" +msgstr "" + +#: i18n/states-gb.php:168 +msgid "Sandwell" +msgstr "" + +#: i18n/states-gb.php:169 +msgid "Scottish Borders, The" +msgstr "" + +#: i18n/states-gb.php:170 +msgid "Sefton" +msgstr "" + +#: i18n/states-gb.php:171 +msgid "Sheffield" +msgstr "" + +#: i18n/states-gb.php:172 +msgid "Shetland Islands" +msgstr "" + +#: i18n/states-gb.php:173 +msgid "Shropshire" +msgstr "" + +#: i18n/states-gb.php:174 +msgid "Slough" +msgstr "" + +#: i18n/states-gb.php:175 +msgid "Solihull" +msgstr "" + +#: i18n/states-gb.php:176 +msgid "Somerset" +msgstr "" + +#: i18n/states-gb.php:177 +msgid "South Ayrshire" +msgstr "" + +#: i18n/states-gb.php:178 +msgid "South Gloucestershire" +msgstr "" + +#: i18n/states-gb.php:179 +msgid "South Lanarkshire" +msgstr "" + +#: i18n/states-gb.php:180 +msgid "South Tyneside" +msgstr "" + +#: i18n/states-gb.php:181 +msgid "Southampton" +msgstr "" + +#: i18n/states-gb.php:182 +msgid "Southend-on-Sea" +msgstr "" + +#: i18n/states-gb.php:183 +msgid "Southwark" +msgstr "" + +#: i18n/states-gb.php:184 +msgid "St. Helens" +msgstr "" + +#: i18n/states-gb.php:185 +msgid "Staffordshire" +msgstr "" + +#: i18n/states-gb.php:186 +msgid "Stirling" +msgstr "" + +#: i18n/states-gb.php:187 +msgid "Stockport" +msgstr "" + +#: i18n/states-gb.php:188 +msgid "Stockton-on-Tees" +msgstr "" + +#: i18n/states-gb.php:189 +msgid "Stoke-on-Trent" +msgstr "" + +#: i18n/states-gb.php:190 +msgid "Suffolk" +msgstr "" + +#: i18n/states-gb.php:191 +msgid "Sunderland" +msgstr "" + +#: i18n/states-gb.php:192 +msgid "Surrey" +msgstr "" + +#: i18n/states-gb.php:193 +msgid "Sutton" +msgstr "" + +#: i18n/states-gb.php:194 +msgid "Swansea" +msgstr "" + +#: i18n/states-gb.php:195 +msgid "Swindon" +msgstr "" + +#: i18n/states-gb.php:196 +msgid "Tameside" +msgstr "" + +#: i18n/states-gb.php:197 +msgid "Telford and Wrekin" +msgstr "" + +#: i18n/states-gb.php:198 +msgid "Thurrock" +msgstr "" + +#: i18n/states-gb.php:199 +msgid "Torbay" +msgstr "" + +#: i18n/states-gb.php:200 +msgid "Torfaen" +msgstr "" + +#: i18n/states-gb.php:201 +msgid "Tower Hamlets" +msgstr "" + +#: i18n/states-gb.php:202 +msgid "Trafford" +msgstr "" + +#: i18n/states-gb.php:203 +msgid "Vale of Glamorgan, The" +msgstr "" + +#: i18n/states-gb.php:204 +msgid "Wakefield" +msgstr "" + +#: i18n/states-gb.php:205 +msgid "Walsall" +msgstr "" + +#: i18n/states-gb.php:206 +msgid "Waltham Forest" +msgstr "" + +#: i18n/states-gb.php:207 +msgid "Wandsworth" +msgstr "" + +#: i18n/states-gb.php:208 +msgid "Warrington" +msgstr "" + +#: i18n/states-gb.php:209 +msgid "Warwickshire" +msgstr "" + +#: i18n/states-gb.php:210 +msgid "West Berkshire" +msgstr "" + +#: i18n/states-gb.php:211 +msgid "West Dunbartonshire" +msgstr "" + +#: i18n/states-gb.php:212 +msgid "West Lothian" +msgstr "" + +#: i18n/states-gb.php:213 +msgid "West Sussex" +msgstr "" + +#: i18n/states-gb.php:214 +msgid "Westminster" +msgstr "" + +#: i18n/states-gb.php:215 +msgid "Wigan" +msgstr "" + +#: i18n/states-gb.php:216 +msgid "Wiltshire" +msgstr "" + +#: i18n/states-gb.php:217 +msgid "Windsor and Maidenhead" +msgstr "" + +#: i18n/states-gb.php:218 +msgid "Wirral" +msgstr "" + +#: i18n/states-gb.php:219 +msgid "Wokingham" +msgstr "" + +#: i18n/states-gb.php:220 +msgid "Wolverhampton" +msgstr "" + +#: i18n/states-gb.php:221 +msgid "Worcestershire" +msgstr "" + +#: i18n/states-gb.php:222 +msgid "Wrexham" +msgstr "" + +#: i18n/states-gb.php:223 +msgid "York" +msgstr "" + +#: i18n/states-in.php:7 +msgid "Andhra Pradesh" +msgstr "" + +#: i18n/states-in.php:8 +msgid "Arunachal Pradesh" +msgstr "" + +#: i18n/states-in.php:9 +msgid "Assam" +msgstr "" + +#: i18n/states-in.php:10 +msgid "Bihar" +msgstr "" + +#: i18n/states-in.php:11 +msgid "Chhattisgarh" +msgstr "" + +#: i18n/states-in.php:12 +msgid "Goa" +msgstr "" + +#: i18n/states-in.php:13 +msgid "Gujarat" +msgstr "" + +#: i18n/states-in.php:14 +msgid "Haryana" +msgstr "" + +#: i18n/states-in.php:15 +msgid "Himachal Pradesh" +msgstr "" + +#: i18n/states-in.php:16 +msgid "Jammu and Kashmir" +msgstr "" + +#: i18n/states-in.php:17 +msgid "Jharkhand" +msgstr "" + +#: i18n/states-in.php:18 +msgid "Karnataka" +msgstr "" + +#: i18n/states-in.php:19 +msgid "Kerala" +msgstr "" + +#: i18n/states-in.php:20 +msgid "Madhya Pradesh" +msgstr "" + +#: i18n/states-in.php:21 +msgid "Maharashtra" +msgstr "" + +#: i18n/states-in.php:22 +msgid "Manipur" +msgstr "" + +#: i18n/states-in.php:23 +msgid "Meghalaya" +msgstr "" + +#: i18n/states-in.php:24 +msgid "Mizoram" +msgstr "" + +#: i18n/states-in.php:25 +msgid "Nagaland" +msgstr "" + +#: i18n/states-in.php:26 +msgid "Orissa" +msgstr "" + +#: i18n/states-in.php:27 +msgid "Punjab" +msgstr "" + +#: i18n/states-in.php:28 +msgid "Rajasthan" +msgstr "" + +#: i18n/states-in.php:29 +msgid "Sikkim" +msgstr "" + +#: i18n/states-in.php:30 +msgid "Tamil Nadu" +msgstr "" + +#: i18n/states-in.php:31 +msgid "Telangana" +msgstr "" + +#: i18n/states-in.php:32 +msgid "Tripura" +msgstr "" + +#: i18n/states-in.php:33 +msgid "Uttarakhand" +msgstr "" + +#: i18n/states-in.php:34 +msgid "Uttar Pradesh" +msgstr "" + +#: i18n/states-in.php:35 +msgid "West Bengal" +msgstr "" + +#: i18n/states-in.php:36 +msgid "Andaman and Nicobar Islands" +msgstr "" + +#: i18n/states-in.php:37 +msgid "Chandigarh" +msgstr "" + +#: i18n/states-in.php:38 +msgid "Dadar and Nagar Haveli" +msgstr "" + +#: i18n/states-in.php:39 +msgid "Daman and Diu" +msgstr "" + +#: i18n/states-in.php:40 +msgid "Delhi" +msgstr "" + +#: i18n/states-in.php:41 +msgid "Lakshadweep" +msgstr "" + +#: i18n/states-in.php:42 +msgid "Pondicherry (Puducherry)" +msgstr "" + +#: i18n/states-it.php:7 +msgid "Agrigento" +msgstr "" + +#: i18n/states-it.php:8 +msgid "Alessandria" +msgstr "" + +#: i18n/states-it.php:9 +msgid "Ancona" +msgstr "" + +#: i18n/states-it.php:10 +msgid "Aosta" +msgstr "" + +#: i18n/states-it.php:11 +msgid "Arezzo" +msgstr "" + +#: i18n/states-it.php:12 +msgid "Ascoli Piceno" +msgstr "" + +#: i18n/states-it.php:13 +msgid "Asti" +msgstr "" + +#: i18n/states-it.php:14 +msgid "Avellino" +msgstr "" + +#: i18n/states-it.php:15 +msgid "Bari" +msgstr "" + +#: i18n/states-it.php:16 +msgid "Barletta-Andria-Trani" +msgstr "" + +#: i18n/states-it.php:17 +msgid "Belluno" +msgstr "" + +#: i18n/states-it.php:18 +msgid "Benevento" +msgstr "" + +#: i18n/states-it.php:19 +msgid "Bergamo" +msgstr "" + +#: i18n/states-it.php:20 +msgid "Biella" +msgstr "" + +#: i18n/states-it.php:21 +msgid "Bologna" +msgstr "" + +#: i18n/states-it.php:22 +msgid "Bolzano" +msgstr "" + +#: i18n/states-it.php:23 +msgid "Brescia" +msgstr "" + +#: i18n/states-it.php:24 +msgid "Brindisi" +msgstr "" + +#: i18n/states-it.php:25 +msgid "Cagliari" +msgstr "" + +#: i18n/states-it.php:26 +msgid "Caltanissetta" +msgstr "" + +#: i18n/states-it.php:27 +msgid "Campobasso" +msgstr "" + +#: i18n/states-it.php:28 +msgid "Caserta" +msgstr "" + +#: i18n/states-it.php:29 +msgid "Catania" +msgstr "" + +#: i18n/states-it.php:30 +msgid "Catanzaro" +msgstr "" + +#: i18n/states-it.php:31 +msgid "Chieti" +msgstr "" + +#: i18n/states-it.php:32 +msgid "Como" +msgstr "" + +#: i18n/states-it.php:33 +msgid "Cosenza" +msgstr "" + +#: i18n/states-it.php:34 +msgid "Cremona" +msgstr "" + +#: i18n/states-it.php:35 +msgid "Crotone" +msgstr "" + +#: i18n/states-it.php:36 +msgid "Cuneo" +msgstr "" + +#: i18n/states-it.php:37 +msgid "Enna" +msgstr "" + +#: i18n/states-it.php:38 +msgid "Fermo" +msgstr "" + +#: i18n/states-it.php:39 +msgid "Ferrara" +msgstr "" + +#: i18n/states-it.php:40 +msgid "Firenze" +msgstr "" + +#: i18n/states-it.php:41 +msgid "Foggia" +msgstr "" + +#: i18n/states-it.php:42 +msgid "Forli-Cesena" +msgstr "" + +#: i18n/states-it.php:43 +msgid "Frosinone" +msgstr "" + +#: i18n/states-it.php:44 +msgid "Genoa" +msgstr "" + +#: i18n/states-it.php:45 +msgid "Gorizia" +msgstr "" + +#: i18n/states-it.php:46 +msgid "Grosseto" +msgstr "" + +#: i18n/states-it.php:47 +msgid "Imperia" +msgstr "" + +#: i18n/states-it.php:48 +msgid "Isernia" +msgstr "" + +#: i18n/states-it.php:49 +msgid "La Spezia" +msgstr "" + +#: i18n/states-it.php:50 +msgid "L'Aquila" +msgstr "" + +#: i18n/states-it.php:51 +msgid "Latina" +msgstr "" + +#: i18n/states-it.php:52 +msgid "Lecce" +msgstr "" + +#: i18n/states-it.php:53 +msgid "Lecco" +msgstr "" + +#: i18n/states-it.php:54 +msgid "Livorno" +msgstr "" + +#: i18n/states-it.php:55 +msgid "Lodi" +msgstr "" + +#: i18n/states-it.php:56 +msgid "Lucca" +msgstr "" + +#: i18n/states-it.php:57 +msgid "Macerata" +msgstr "" + +#: i18n/states-it.php:58 +msgid "Mantova" +msgstr "" + +#: i18n/states-it.php:59 +msgid "Massa-Carrara" +msgstr "" + +#: i18n/states-it.php:60 +msgid "Matera" +msgstr "" + +#: i18n/states-it.php:61 +msgid "Messina" +msgstr "" + +#: i18n/states-it.php:62 +msgid "Milano" +msgstr "" + +#: i18n/states-it.php:63 +msgid "Modena" +msgstr "" + +#: i18n/states-it.php:64 +msgid "Monza e della Brianza" +msgstr "" + +#: i18n/states-it.php:65 +msgid "Napoli" +msgstr "" + +#: i18n/states-it.php:66 +msgid "Novara" +msgstr "" + +#: i18n/states-it.php:67 +msgid "Nuoro" +msgstr "" + +#: i18n/states-it.php:68 +msgid "Olbia-Tempio" +msgstr "" + +#: i18n/states-it.php:69 +msgid "Oristano" +msgstr "" + +#: i18n/states-it.php:70 +msgid "Padova" +msgstr "" + +#: i18n/states-it.php:71 +msgid "Palermo" +msgstr "" + +#: i18n/states-it.php:72 +msgid "Parma" +msgstr "" + +#: i18n/states-it.php:73 +msgid "Pavia" +msgstr "" + +#: i18n/states-it.php:74 +msgid "Perugia" +msgstr "" + +#: i18n/states-it.php:75 +msgid "Pesaro e Urbino" +msgstr "" + +#: i18n/states-it.php:76 +msgid "Pescara" +msgstr "" + +#: i18n/states-it.php:77 +msgid "Piacenza" +msgstr "" + +#: i18n/states-it.php:78 +msgid "Pisa" +msgstr "" + +#: i18n/states-it.php:79 +msgid "Pistoia" +msgstr "" + +#: i18n/states-it.php:80 +msgid "Pordenone" +msgstr "" + +#: i18n/states-it.php:81 +msgid "Potenza" +msgstr "" + +#: i18n/states-it.php:82 +msgid "Prato" +msgstr "" + +#: i18n/states-it.php:83 +msgid "Ragusa" +msgstr "" + +#: i18n/states-it.php:84 +msgid "Ravenna" +msgstr "" + +#: i18n/states-it.php:85 +msgid "Reggio Calabria" +msgstr "" + +#: i18n/states-it.php:86 +msgid "Reggio Emilia" +msgstr "" + +#: i18n/states-it.php:87 +msgid "Rieti" +msgstr "" + +#: i18n/states-it.php:88 +msgid "Rimini" +msgstr "" + +#: i18n/states-it.php:89 +msgid "Roma" +msgstr "" + +#: i18n/states-it.php:90 +msgid "Rovigo" +msgstr "" + +#: i18n/states-it.php:91 +msgid "Salerno" +msgstr "" + +#: i18n/states-it.php:92 +msgid "Medio Campidano" +msgstr "" + +#: i18n/states-it.php:93 +msgid "Sassari" +msgstr "" + +#: i18n/states-it.php:94 +msgid "Savona" +msgstr "" + +#: i18n/states-it.php:95 +msgid "Siena" +msgstr "" + +#: i18n/states-it.php:96 +msgid "Siracusa" +msgstr "" + +#: i18n/states-it.php:97 +msgid "Sondrio" +msgstr "" + +#: i18n/states-it.php:98 +msgid "Taranto" +msgstr "" + +#: i18n/states-it.php:99 +msgid "Teramo" +msgstr "" + +#: i18n/states-it.php:100 +msgid "Terni" +msgstr "" + +#: i18n/states-it.php:101 +msgid "Torino" +msgstr "" + +#: i18n/states-it.php:102 +msgid "Ogliastra" +msgstr "" + +#: i18n/states-it.php:103 +msgid "Trapani" +msgstr "" + +#: i18n/states-it.php:104 +msgid "Trento" +msgstr "" + +#: i18n/states-it.php:105 +msgid "Treviso" +msgstr "" + +#: i18n/states-it.php:106 +msgid "Trieste" +msgstr "" + +#: i18n/states-it.php:107 +msgid "Udine" +msgstr "" + +#: i18n/states-it.php:108 +msgid "Varesa" +msgstr "" + +#: i18n/states-it.php:109 +msgid "Venezia" +msgstr "" + +#: i18n/states-it.php:110 +msgid "Verbano-Cusio-Ossola" +msgstr "" + +#: i18n/states-it.php:111 +msgid "Vercelli" +msgstr "" + +#: i18n/states-it.php:112 +msgid "Verona" +msgstr "" + +#: i18n/states-it.php:113 +msgid "Vibo Valentia" +msgstr "" + +#: i18n/states-it.php:114 +msgid "Vicenza" +msgstr "" + +#: i18n/states-it.php:115 +msgid "Viterbo" +msgstr "" + +#: i18n/states-jp.php:7 +msgid "Hokkaido" +msgstr "" + +#: i18n/states-jp.php:8 +msgid "Aomori" +msgstr "" + +#: i18n/states-jp.php:9 +msgid "Iwate" +msgstr "" + +#: i18n/states-jp.php:10 +msgid "Miyagi" +msgstr "" + +#: i18n/states-jp.php:11 +msgid "Akita" +msgstr "" + +#: i18n/states-jp.php:12 +msgid "Yamagata" +msgstr "" + +#: i18n/states-jp.php:13 +msgid "Fukushima" +msgstr "" + +#: i18n/states-jp.php:14 +msgid "Ibaraki" +msgstr "" + +#: i18n/states-jp.php:15 +msgid "Tochigi" +msgstr "" + +#: i18n/states-jp.php:16 +msgid "Gunma" +msgstr "" + +#: i18n/states-jp.php:17 +msgid "Saitama" +msgstr "" + +#: i18n/states-jp.php:18 +msgid "Chiba" +msgstr "" + +#: i18n/states-jp.php:19 +msgid "Tokyo" +msgstr "" + +#: i18n/states-jp.php:20 +msgid "Kanagawa" +msgstr "" + +#: i18n/states-jp.php:21 +msgid "Niigata" +msgstr "" + +#: i18n/states-jp.php:22 +msgid "Toyama" +msgstr "" + +#: i18n/states-jp.php:23 +msgid "Ishikawa" +msgstr "" + +#: i18n/states-jp.php:24 +msgid "Fukui" +msgstr "" + +#: i18n/states-jp.php:25 +msgid "Yamanashi" +msgstr "" + +#: i18n/states-jp.php:26 +msgid "Nagano" +msgstr "" + +#: i18n/states-jp.php:27 +msgid "Gifu" +msgstr "" + +#: i18n/states-jp.php:28 +msgid "Shizuoka" +msgstr "" + +#: i18n/states-jp.php:29 +msgid "Aichi" +msgstr "" + +#: i18n/states-jp.php:30 +msgid "Mie" +msgstr "" + +#: i18n/states-jp.php:31 +msgid "Shiga" +msgstr "" + +#: i18n/states-jp.php:32 +msgid "Kyouto" +msgstr "" + +#: i18n/states-jp.php:33 +msgid "Osaka" +msgstr "" + +#: i18n/states-jp.php:34 +msgid "Hyougo" +msgstr "" + +#: i18n/states-jp.php:35 +msgid "Nara" +msgstr "" + +#: i18n/states-jp.php:36 +msgid "Wakayama" +msgstr "" + +#: i18n/states-jp.php:37 +msgid "Tottori" +msgstr "" + +#: i18n/states-jp.php:38 +msgid "Shimane" +msgstr "" + +#: i18n/states-jp.php:39 +msgid "Okayama" +msgstr "" + +#: i18n/states-jp.php:40 +msgid "Hiroshima" +msgstr "" + +#: i18n/states-jp.php:41 +msgid "Yamaguchi" +msgstr "" + +#: i18n/states-jp.php:42 +msgid "Tokushima" +msgstr "" + +#: i18n/states-jp.php:43 +msgid "Kagawa" +msgstr "" + +#: i18n/states-jp.php:44 +msgid "Ehime" +msgstr "" + +#: i18n/states-jp.php:45 +msgid "Kochi" +msgstr "" + +#: i18n/states-jp.php:46 +msgid "Fukuoka" +msgstr "" + +#: i18n/states-jp.php:47 +msgid "Saga" +msgstr "" + +#: i18n/states-jp.php:48 +msgid "Nagasaki" +msgstr "" + +#: i18n/states-jp.php:49 +msgid "Kumamoto" +msgstr "" + +#: i18n/states-jp.php:50 +msgid "Oita" +msgstr "" + +#: i18n/states-jp.php:51 +msgid "Miyazaki" +msgstr "" + +#: i18n/states-jp.php:52 +msgid "Kagoshima" +msgstr "" + +#: i18n/states-jp.php:53 +msgid "Okinawa" +msgstr "" + +#: i18n/states-np.php:7 +msgid "Illam" +msgstr "" + +#: i18n/states-np.php:8 +msgid "Jhapa" +msgstr "" + +#: i18n/states-np.php:9 +msgid "Panchthar" +msgstr "" + +#: i18n/states-np.php:10 +msgid "Taplejung" +msgstr "" + +#: i18n/states-np.php:11 +msgid "Bhojpur" +msgstr "" + +#: i18n/states-np.php:12 +msgid "Dhankuta" +msgstr "" + +#: i18n/states-np.php:13 +msgid "Morang" +msgstr "" + +#: i18n/states-np.php:14 +msgid "Sunsari" +msgstr "" + +#: i18n/states-np.php:15 +msgid "Sankhuwa" +msgstr "" + +#: i18n/states-np.php:16 +msgid "Terhathum" +msgstr "" + +#: i18n/states-np.php:17 +msgid "Khotang" +msgstr "" + +#: i18n/states-np.php:18 +msgid "Okhaldhunga" +msgstr "" + +#: i18n/states-np.php:19 +msgid "Saptari" +msgstr "" + +#: i18n/states-np.php:20 +msgid "Siraha" +msgstr "" + +#: i18n/states-np.php:21 +msgid "Solukhumbu" +msgstr "" + +#: i18n/states-np.php:22 +msgid "Udayapur" +msgstr "" + +#: i18n/states-np.php:23 +msgid "Dhanusa" +msgstr "" + +#: i18n/states-np.php:24 +msgid "Dolakha" +msgstr "" + +#: i18n/states-np.php:25 +msgid "Mohottari" +msgstr "" + +#: i18n/states-np.php:26 +msgid "Ramechha" +msgstr "" + +#: i18n/states-np.php:27 +msgid "Sarlahi" +msgstr "" + +#: i18n/states-np.php:28 +msgid "Sindhuli" +msgstr "" + +#: i18n/states-np.php:29 +msgid "Bhaktapur" +msgstr "" + +#: i18n/states-np.php:30 +msgid "Dhading" +msgstr "" + +#: i18n/states-np.php:31 +msgid "Kathmandu" +msgstr "" + +#: i18n/states-np.php:32 +msgid "Kavrepalanchowk" +msgstr "" + +#: i18n/states-np.php:33 +msgid "Lalitpur" +msgstr "" + +#: i18n/states-np.php:34 +msgid "Nuwakot" +msgstr "" + +#: i18n/states-np.php:35 +msgid "Rasuwa" +msgstr "" + +#: i18n/states-np.php:36 +msgid "Sindhupalchowk" +msgstr "" + +#: i18n/states-np.php:37 +msgid "Bara" +msgstr "" + +#: i18n/states-np.php:38 +msgid "Chitwan" +msgstr "" + +#: i18n/states-np.php:39 +msgid "Makwanpur" +msgstr "" + +#: i18n/states-np.php:40 +msgid "Parsa" +msgstr "" + +#: i18n/states-np.php:41 +msgid "Rautahat" +msgstr "" + +#: i18n/states-np.php:42 +msgid "Gorkha" +msgstr "" + +#: i18n/states-np.php:43 +msgid "Kaski" +msgstr "" + +#: i18n/states-np.php:44 +msgid "Lamjung" +msgstr "" + +#: i18n/states-np.php:45 +msgid "Manang" +msgstr "" + +#: i18n/states-np.php:46 +msgid "Syangja" +msgstr "" + +#: i18n/states-np.php:47 +msgid "Tanahun" +msgstr "" + +#: i18n/states-np.php:48 +msgid "Baglung" +msgstr "" + +#: i18n/states-np.php:49 +msgid "Parbat" +msgstr "" + +#: i18n/states-np.php:50 +msgid "Mustang" +msgstr "" + +#: i18n/states-np.php:51 +msgid "Myagdi" +msgstr "" + +#: i18n/states-np.php:52 +msgid "Agrghakanchi" +msgstr "" + +#: i18n/states-np.php:53 +msgid "Gulmi" +msgstr "" + +#: i18n/states-np.php:54 +msgid "Kapilbastu" +msgstr "" + +#: i18n/states-np.php:55 +msgid "Nawalparasi" +msgstr "" + +#: i18n/states-np.php:56 +msgid "Palpa" +msgstr "" + +#: i18n/states-np.php:57 +msgid "Rupandehi" +msgstr "" + +#: i18n/states-np.php:58 +msgid "Dang" +msgstr "" + +#: i18n/states-np.php:59 +msgid "Pyuthan" +msgstr "" + +#: i18n/states-np.php:60 +msgid "Rolpa" +msgstr "" + +#: i18n/states-np.php:61 +msgid "Rukum" +msgstr "" + +#: i18n/states-np.php:62 +msgid "Salyan" +msgstr "" + +#: i18n/states-np.php:63 +msgid "Banke" +msgstr "" + +#: i18n/states-np.php:64 +msgid "Bardiya" +msgstr "" + +#: i18n/states-np.php:65 +msgid "Dailekh" +msgstr "" + +#: i18n/states-np.php:66 +msgid "Jajarkot" +msgstr "" + +#: i18n/states-np.php:67 +msgid "Surkhet" +msgstr "" + +#: i18n/states-np.php:68 +msgid "Dolpa" +msgstr "" + +#: i18n/states-np.php:69 +msgid "Humla" +msgstr "" + +#: i18n/states-np.php:70 +msgid "Jumla" +msgstr "" + +#: i18n/states-np.php:71 +msgid "Kalikot" +msgstr "" + +#: i18n/states-np.php:72 +msgid "Mugu" +msgstr "" + +#: i18n/states-np.php:73 +msgid "Achham" +msgstr "" + +#: i18n/states-np.php:74 +msgid "Bajhang" +msgstr "" + +#: i18n/states-np.php:75 +msgid "Bajura" +msgstr "" + +#: i18n/states-np.php:76 +msgid "Doti" +msgstr "" + +#: i18n/states-np.php:77 +msgid "Kailali" +msgstr "" + +#: i18n/states-np.php:78 +msgid "Baitadi" +msgstr "" + +#: i18n/states-np.php:79 +msgid "Dadeldhura" +msgstr "" + +#: i18n/states-np.php:80 +msgid "Darchula" +msgstr "" + +#: i18n/states-np.php:81 +msgid "Kanchanpur" +msgstr "" + +#: i18n/states-th.php:7 +msgid "Amnat Charoen (อำนาจเจริญ)" +msgstr "" + +#: i18n/states-th.php:8 +msgid "Ang Thong (อ่างทอง)" +msgstr "" + +#: i18n/states-th.php:9 +msgid "Ayutthaya (พระนครศรีอยุธยา)" +msgstr "" + +#: i18n/states-th.php:10 +msgid "Bangkok (กรุงเทพมหานคร)" +msgstr "" + +#: i18n/states-th.php:11 +msgid "Bueng Kan (บึงกาฬ)" +msgstr "" + +#: i18n/states-th.php:12 +msgid "Buri Ram (บุรีรัมย์)" +msgstr "" + +#: i18n/states-th.php:13 +msgid "Chachoengsao (ฉะเชิงเทรา)" +msgstr "" + +#: i18n/states-th.php:14 +msgid "Chai Nat (ชัยนาท)" +msgstr "" + +#: i18n/states-th.php:15 +msgid "Chaiyaphum (ชัยภูมิ)" +msgstr "" + +#: i18n/states-th.php:16 +msgid "Chanthaburi (จันทบุรี)" +msgstr "" + +#: i18n/states-th.php:17 +msgid "Chiang Mai (เชียงใหม่)" +msgstr "" + +#: i18n/states-th.php:18 +msgid "Chiang Rai (เชียงราย)" +msgstr "" + +#: i18n/states-th.php:19 +msgid "Chonburi (ชลบุรี)" +msgstr "" + +#: i18n/states-th.php:20 +msgid "Chumphon (ชุมพร)" +msgstr "" + +#: i18n/states-th.php:21 +msgid "Kalasin (กาฬสินธุ์)" +msgstr "" + +#: i18n/states-th.php:22 +msgid "Kamphaeng Phet (กำแพงเพชร)" +msgstr "" + +#: i18n/states-th.php:23 +msgid "Kanchanaburi (กาญจนบุรี)" +msgstr "" + +#: i18n/states-th.php:24 +msgid "Khon Kaen (ขอนแก่น)" +msgstr "" + +#: i18n/states-th.php:25 +msgid "Krabi (กระบี่)" +msgstr "" + +#: i18n/states-th.php:26 +msgid "Lampang (ลำปาง)" +msgstr "" + +#: i18n/states-th.php:27 +msgid "Lamphun (ลำพูน)" +msgstr "" + +#: i18n/states-th.php:28 +msgid "Loei (เลย)" +msgstr "" + +#: i18n/states-th.php:29 +msgid "Lopburi (ลพบุรี)" +msgstr "" + +#: i18n/states-th.php:30 +msgid "Mae Hong Son (แม่ฮ่องสอน)" +msgstr "" + +#: i18n/states-th.php:31 +msgid "Maha Sarakham (มหาสารคาม)" +msgstr "" + +#: i18n/states-th.php:32 +msgid "Mukdahan (มุกดาหาร)" +msgstr "" + +#: i18n/states-th.php:33 +msgid "Nakhon Nayok (นครนายก)" +msgstr "" + +#: i18n/states-th.php:34 +msgid "Nakhon Pathom (นครปฐม)" +msgstr "" + +#: i18n/states-th.php:35 +msgid "Nakhon Phanom (นครพนม)" +msgstr "" + +#: i18n/states-th.php:36 +msgid "Nakhon Ratchasima (นครราชสีมา)" +msgstr "" + +#: i18n/states-th.php:37 +msgid "Nakhon Sawan (นครสวรรค์)" +msgstr "" + +#: i18n/states-th.php:38 +msgid "Nakhon Si Thammarat (นครศรีธรรมราช)" +msgstr "" + +#: i18n/states-th.php:39 +msgid "Nan (น่าน)" +msgstr "" + +#: i18n/states-th.php:40 +msgid "Narathiwat (นราธิวาส)" +msgstr "" + +#: i18n/states-th.php:41 +msgid "Nong Bua Lam Phu (หนองบัวลำภู)" +msgstr "" + +#: i18n/states-th.php:42 +msgid "Nong Khai (หนองคาย)" +msgstr "" + +#: i18n/states-th.php:43 +msgid "Nonthaburi (นนทบุรี)" +msgstr "" + +#: i18n/states-th.php:44 +msgid "Pathum Thani (ปทุมธานี)" +msgstr "" + +#: i18n/states-th.php:45 +msgid "Pattani (ปัตตานี)" +msgstr "" + +#: i18n/states-th.php:46 +msgid "Phang Nga (พังงา)" +msgstr "" + +#: i18n/states-th.php:47 +msgid "Phatthalung (พัทลุง)" +msgstr "" + +#: i18n/states-th.php:48 +msgid "Phayao (พะเยา)" +msgstr "" + +#: i18n/states-th.php:49 +msgid "Phetchabun (เพชรบูรณ์)" +msgstr "" + +#: i18n/states-th.php:50 +msgid "Phetchaburi (เพชรบุรี)" +msgstr "" + +#: i18n/states-th.php:51 +msgid "Phichit (พิจิตร)" +msgstr "" + +#: i18n/states-th.php:52 +msgid "Phitsanulok (พิษณุโลก)" +msgstr "" + +#: i18n/states-th.php:53 +msgid "Phrae (แพร่)" +msgstr "" + +#: i18n/states-th.php:54 +msgid "Phuket (ภูเก็ต)" +msgstr "" + +#: i18n/states-th.php:55 +msgid "Prachin Buri (ปราจีนบุรี)" +msgstr "" + +#: i18n/states-th.php:56 +msgid "Prachuap Khiri Khan (ประจวบคีรีขันธ์)" +msgstr "" + +#: i18n/states-th.php:57 +msgid "Ranong (ระนอง)" +msgstr "" + +#: i18n/states-th.php:58 +msgid "Ratchaburi (ราชบุรี)" +msgstr "" + +#: i18n/states-th.php:59 +msgid "Rayong (ระยอง)" +msgstr "" + +#: i18n/states-th.php:60 +msgid "Roi Et (ร้อยเอ็ด)" +msgstr "" + +#: i18n/states-th.php:61 +msgid "Sa Kaeo (สระแก้ว)" +msgstr "" + +#: i18n/states-th.php:62 +msgid "Sakon Nakhon (สกลนคร)" +msgstr "" + +#: i18n/states-th.php:63 +msgid "Samut Prakan (สมุทรปราการ)" +msgstr "" + +#: i18n/states-th.php:64 +msgid "Samut Sakhon (สมุทรสาคร)" +msgstr "" + +#: i18n/states-th.php:65 +msgid "Samut Songkhram (สมุทรสงคราม)" +msgstr "" + +#: i18n/states-th.php:66 +msgid "Saraburi (สระบุรี)" +msgstr "" + +#: i18n/states-th.php:67 +msgid "Satun (สตูล)" +msgstr "" + +#: i18n/states-th.php:68 +msgid "Sing Buri (สิงห์บุรี)" +msgstr "" + +#: i18n/states-th.php:69 +msgid "Sisaket (ศรีสะเกษ)" +msgstr "" + +#: i18n/states-th.php:70 +msgid "Songkhla (สงขลา)" +msgstr "" + +#: i18n/states-th.php:71 +msgid "Sukhothai (สุโขทัย)" +msgstr "" + +#: i18n/states-th.php:72 +msgid "Suphan Buri (สุพรรณบุรี)" +msgstr "" + +#: i18n/states-th.php:73 +msgid "Surat Thani (สุราษฎร์ธานี)" +msgstr "" + +#: i18n/states-th.php:74 +msgid "Surin (สุรินทร์)" +msgstr "" + +#: i18n/states-th.php:75 +msgid "Tak (ตาก)" +msgstr "" + +#: i18n/states-th.php:76 +msgid "Trang (ตรัง)" +msgstr "" + +#: i18n/states-th.php:77 +msgid "Trat (ตราด)" +msgstr "" + +#: i18n/states-th.php:78 +msgid "Ubon Ratchathani (อุบลราชธานี)" +msgstr "" + +#: i18n/states-th.php:79 +msgid "Udon Thani (อุดรธานี)" +msgstr "" + +#: i18n/states-th.php:80 +msgid "Uthai Thani (อุทัยธานี)" +msgstr "" + +#: i18n/states-th.php:81 +msgid "Uttaradit (อุตรดิตถ์)" +msgstr "" + +#: i18n/states-th.php:82 +msgid "Yala (ยะลา)" +msgstr "" + +#: i18n/states-th.php:83 +msgid "Yasothon (ยโสธร)" +msgstr "" + +#: i18n/states-tr.php:7 +msgid "Adana" +msgstr "" + +#: i18n/states-tr.php:8 +msgid "Adıyaman" +msgstr "" + +#: i18n/states-tr.php:9 +msgid "Afyon" +msgstr "" + +#: i18n/states-tr.php:10 +msgid "Ağrı" +msgstr "" + +#: i18n/states-tr.php:11 +msgid "Amasya" +msgstr "" + +#: i18n/states-tr.php:12 +msgid "Ankara" +msgstr "" + +#: i18n/states-tr.php:13 +msgid "Antalya" +msgstr "" + +#: i18n/states-tr.php:14 +msgid "Artvin" +msgstr "" + +#: i18n/states-tr.php:15 +msgid "Aydın" +msgstr "" + +#: i18n/states-tr.php:16 +msgid "Balıkesir" +msgstr "" + +#: i18n/states-tr.php:17 +msgid "Bilecik" +msgstr "" + +#: i18n/states-tr.php:18 +msgid "Bingöl" +msgstr "" + +#: i18n/states-tr.php:19 +msgid "Bitlis" +msgstr "" + +#: i18n/states-tr.php:20 +msgid "Bolu" +msgstr "" + +#: i18n/states-tr.php:21 +msgid "Burdur" +msgstr "" + +#: i18n/states-tr.php:22 +msgid "Bursa" +msgstr "" + +#: i18n/states-tr.php:23 +msgid "Çanakkale" +msgstr "" + +#: i18n/states-tr.php:24 +msgid "Çankıkesir" +msgstr "" + +#: i18n/states-tr.php:25 +msgid "Çorum" +msgstr "" + +#: i18n/states-tr.php:26 +msgid "Denizli" +msgstr "" + +#: i18n/states-tr.php:27 +msgid "Diyarbakır" +msgstr "" + +#: i18n/states-tr.php:28 +msgid "Edirne" +msgstr "" + +#: i18n/states-tr.php:29 +msgid "Elazığ" +msgstr "" + +#: i18n/states-tr.php:30 +msgid "Erzincan" +msgstr "" + +#: i18n/states-tr.php:31 +msgid "Erzurum" +msgstr "" + +#: i18n/states-tr.php:32 +msgid "Eskişehir" +msgstr "" + +#: i18n/states-tr.php:33 +msgid "Gaziantep" +msgstr "" + +#: i18n/states-tr.php:34 +msgid "Giresun" +msgstr "" + +#: i18n/states-tr.php:35 +msgid "Gümüşhane" +msgstr "" + +#: i18n/states-tr.php:36 +msgid "Hakkari" +msgstr "" + +#: i18n/states-tr.php:37 +msgid "Hatay" +msgstr "" + +#: i18n/states-tr.php:38 +msgid "Isparta" +msgstr "" + +#: i18n/states-tr.php:39 +msgid "İçel" +msgstr "" + +#: i18n/states-tr.php:40 +msgid "İstanbul" +msgstr "" + +#: i18n/states-tr.php:41 +msgid "İzmir" +msgstr "" + +#: i18n/states-tr.php:42 +msgid "Kars" +msgstr "" + +#: i18n/states-tr.php:43 +msgid "Kastamonu" +msgstr "" + +#: i18n/states-tr.php:44 +msgid "Kayseri" +msgstr "" + +#: i18n/states-tr.php:45 +msgid "Kırklareli" +msgstr "" + +#: i18n/states-tr.php:46 +msgid "Kırşehir" +msgstr "" + +#: i18n/states-tr.php:47 +msgid "Kocaeli" +msgstr "" + +#: i18n/states-tr.php:48 +msgid "Konya" +msgstr "" + +#: i18n/states-tr.php:49 +msgid "Kütahya" +msgstr "" + +#: i18n/states-tr.php:50 +msgid "Malatya" +msgstr "" + +#: i18n/states-tr.php:51 +msgid "Manisa" +msgstr "" + +#: i18n/states-tr.php:52 +msgid "Kahramanmaraş" +msgstr "" + +#: i18n/states-tr.php:53 +msgid "Mardin" +msgstr "" + +#: i18n/states-tr.php:54 +msgid "Muğla" +msgstr "" + +#: i18n/states-tr.php:55 +msgid "Muş" +msgstr "" + +#: i18n/states-tr.php:56 +msgid "Nevşehir" +msgstr "" + +#: i18n/states-tr.php:57 +msgid "Niğde" +msgstr "" + +#: i18n/states-tr.php:58 +msgid "Ordu" +msgstr "" + +#: i18n/states-tr.php:59 +msgid "Rize" +msgstr "" + +#: i18n/states-tr.php:60 +msgid "Sakarya" +msgstr "" + +#: i18n/states-tr.php:61 +msgid "Samsun" +msgstr "" + +#: i18n/states-tr.php:62 +msgid "Siirt" +msgstr "" + +#: i18n/states-tr.php:63 +msgid "Sinop" +msgstr "" + +#: i18n/states-tr.php:64 +msgid "Sivas" +msgstr "" + +#: i18n/states-tr.php:65 +msgid "Tekirdağ" +msgstr "" + +#: i18n/states-tr.php:66 +msgid "Tokat" +msgstr "" + +#: i18n/states-tr.php:67 +msgid "Trabzon" +msgstr "" + +#: i18n/states-tr.php:68 +msgid "Tunceli" +msgstr "" + +#: i18n/states-tr.php:69 +msgid "Şanlıurfa" +msgstr "" + +#: i18n/states-tr.php:70 +msgid "Uşak" +msgstr "" + +#: i18n/states-tr.php:71 +msgid "Van" +msgstr "" + +#: i18n/states-tr.php:72 +msgid "Yozgat" +msgstr "" + +#: i18n/states-tr.php:73 +msgid "Zonguldak" +msgstr "" + +#: i18n/states-tr.php:74 +msgid "Aksaray" +msgstr "" + +#: i18n/states-tr.php:75 +msgid "Bayburt" +msgstr "" + +#: i18n/states-tr.php:76 +msgid "Karaman" +msgstr "" + +#: i18n/states-tr.php:77 +msgid "Kırıkkale" +msgstr "" + +#: i18n/states-tr.php:78 +msgid "Batman" +msgstr "" + +#: i18n/states-tr.php:79 +msgid "Şırnak" +msgstr "" + +#: i18n/states-tr.php:80 +msgid "Bartın" +msgstr "" + +#: i18n/states-tr.php:81 +msgid "Ardahan" +msgstr "" + +#: i18n/states-tr.php:82 +msgid "Iğdır" +msgstr "" + +#: i18n/states-tr.php:83 +msgid "Yalova" +msgstr "" + +#: i18n/states-tr.php:84 +msgid "Karabük" +msgstr "" + +#: i18n/states-tr.php:85 +msgid "Kilis" +msgstr "" + +#: i18n/states-tr.php:86 +msgid "Osmaniye" +msgstr "" + +#: i18n/states-tr.php:87 +msgid "Düzce" +msgstr "" + +#: i18n/states-us.php:7 +msgid "Alabama" +msgstr "" + +#: i18n/states-us.php:8 +msgid "Alaska" +msgstr "" + +#: i18n/states-us.php:9 +msgid "Arizona" +msgstr "" + +#: i18n/states-us.php:10 +msgid "Arkansas" +msgstr "" + +#: i18n/states-us.php:11 +msgid "California" +msgstr "" + +#: i18n/states-us.php:12 +msgid "Colorado" +msgstr "" + +#: i18n/states-us.php:13 +msgid "Connecticut" +msgstr "" + +#: i18n/states-us.php:14 +msgid "Delaware" +msgstr "" + +#: i18n/states-us.php:15 +msgid "District of Columbia" +msgstr "" + +#: i18n/states-us.php:16 +msgid "Florida" +msgstr "" + +#: i18n/states-us.php:18 +msgid "Hawaii" +msgstr "" + +#: i18n/states-us.php:19 +msgid "Idaho" +msgstr "" + +#: i18n/states-us.php:20 +msgid "Illinois" +msgstr "" + +#: i18n/states-us.php:21 +msgid "Indiana" +msgstr "" + +#: i18n/states-us.php:22 +msgid "Iowa" +msgstr "" + +#: i18n/states-us.php:23 +msgid "Kansas" +msgstr "" + +#: i18n/states-us.php:24 +msgid "Kentucky" +msgstr "" + +#: i18n/states-us.php:25 +msgid "Louisiana" +msgstr "" + +#: i18n/states-us.php:26 +msgid "Maine" +msgstr "" + +#: i18n/states-us.php:27 +msgid "Maryland" +msgstr "" + +#: i18n/states-us.php:28 +msgid "Massachusetts" +msgstr "" + +#: i18n/states-us.php:29 +msgid "Michigan" +msgstr "" + +#: i18n/states-us.php:30 +msgid "Minnesota" +msgstr "" + +#: i18n/states-us.php:31 +msgid "Mississippi" +msgstr "" + +#: i18n/states-us.php:32 +msgid "Missouri" +msgstr "" + +#: i18n/states-us.php:33 +#: i18n/states.php:98 +msgid "Montana" +msgstr "" + +#: i18n/states-us.php:34 +msgid "Nebraska" +msgstr "" + +#: i18n/states-us.php:35 +msgid "Nevada" +msgstr "" + +#: i18n/states-us.php:36 +msgid "New Hampshire" +msgstr "" + +#: i18n/states-us.php:37 +msgid "New Jersey" +msgstr "" + +#: i18n/states-us.php:38 +msgid "New Mexico" +msgstr "" + +#: i18n/states-us.php:39 +msgid "New York" +msgstr "" + +#: i18n/states-us.php:40 +msgid "North Carolina" +msgstr "" + +#: i18n/states-us.php:41 +msgid "North Dakota" +msgstr "" + +#: i18n/states-us.php:42 +msgid "Ohio" +msgstr "" + +#: i18n/states-us.php:43 +msgid "Oklahoma" +msgstr "" + +#: i18n/states-us.php:44 +msgid "Oregon" +msgstr "" + +#: i18n/states-us.php:45 +msgid "Pennsylvania" +msgstr "" + +#: i18n/states-us.php:46 +msgid "Rhode Island" +msgstr "" + +#: i18n/states-us.php:47 +msgid "South Carolina" +msgstr "" + +#: i18n/states-us.php:48 +msgid "South Dakota" +msgstr "" + +#: i18n/states-us.php:49 +msgid "Tennessee" +msgstr "" + +#: i18n/states-us.php:50 +msgid "Texas" +msgstr "" + +#: i18n/states-us.php:51 +msgid "Utah" +msgstr "" + +#: i18n/states-us.php:52 +msgid "Vermont" +msgstr "" + +#: i18n/states-us.php:53 +msgid "Virginia" +msgstr "" + +#: i18n/states-us.php:54 +msgid "Washington" +msgstr "" + +#: i18n/states-us.php:55 +msgid "West Virginia" +msgstr "" + +#: i18n/states-us.php:56 +msgid "Wisconsin" +msgstr "" + +#: i18n/states-us.php:57 +msgid "Wyoming" +msgstr "" + +#: i18n/states-us.php:59 +msgid "Canal Zone" +msgstr "" + +#: i18n/states-us.php:60 +msgid "Commonwealth of the Northern Mariana Islands" +msgstr "" + +#: i18n/states-us.php:61 +msgid "Federated States of Micronesia" +msgstr "" + +#: i18n/states-us.php:66 +msgid "Philippine Islands" +msgstr "" + +#: i18n/states-us.php:68 +msgid "Trust Territory of the Pacific Islands" +msgstr "" + +#: i18n/states-us.php:69 +msgid "Virgin Islands" +msgstr "" + +#: i18n/states-us.php:70 +msgid "Armed Forces - Americas" +msgstr "" + +#: i18n/states-us.php:71 +msgid "Armed Forces - Europe, Canada, Middle East, Africa" +msgstr "" + +#: i18n/states-us.php:72 +msgid "Armed Forces - Pacific" +msgstr "" + +#: i18n/states.php:10 +msgid "Alberta" +msgstr "" + +#: i18n/states.php:11 +msgid "British Columbia" +msgstr "" + +#: i18n/states.php:12 +msgid "Manitoba" +msgstr "" + +#: i18n/states.php:13 +msgid "New Brunswick" +msgstr "" + +#: i18n/states.php:14 +msgid "Newfoundland and Labrador" +msgstr "" + +#: i18n/states.php:15 +msgid "Nova Scotia" +msgstr "" + +#: i18n/states.php:16 +msgid "Northwest Territories" +msgstr "" + +#: i18n/states.php:17 +msgid "Nunavut" +msgstr "" + +#: i18n/states.php:18 +msgid "Ontario" +msgstr "" + +#: i18n/states.php:19 +msgid "Prince Edward Island" +msgstr "" + +#: i18n/states.php:20 +msgid "Quebec" +msgstr "" + +#: i18n/states.php:21 +msgid "Saskatchewan" +msgstr "" + +#: i18n/states.php:22 +msgid "Yukon" +msgstr "" + +#: i18n/states.php:27 +msgid "Bengo" +msgstr "" + +#: i18n/states.php:28 +msgid "Benguela" +msgstr "" + +#: i18n/states.php:29 +msgid "Bié" +msgstr "" + +#: i18n/states.php:30 +msgid "Cabinda" +msgstr "" + +#: i18n/states.php:31 +msgid "Cunene" +msgstr "" + +#: i18n/states.php:32 +msgid "Huambo" +msgstr "" + +#: i18n/states.php:33 +msgid "Huíla" +msgstr "" + +#: i18n/states.php:34 +msgid "Kuando Kubango" +msgstr "" + +#: i18n/states.php:35 +msgid "Kwanza-Norte" +msgstr "" + +#: i18n/states.php:36 +msgid "Kwanza-Sul" +msgstr "" + +#: i18n/states.php:37 +msgid "Luanda" +msgstr "" + +#: i18n/states.php:38 +msgid "Lunda-Norte" +msgstr "" + +#: i18n/states.php:39 +msgid "Lunda-Sul" +msgstr "" + +#: i18n/states.php:40 +msgid "Malanje" +msgstr "" + +#: i18n/states.php:41 +msgid "Moxico" +msgstr "" + +#: i18n/states.php:42 +msgid "Namibe" +msgstr "" + +#: i18n/states.php:43 +msgid "Uíge" +msgstr "" + +#: i18n/states.php:44 +msgid "Zaire" +msgstr "" + +#: i18n/states.php:48 +msgid "Australian Capital Territory" +msgstr "" + +#: i18n/states.php:49 +msgid "New South Wales" +msgstr "" + +#: i18n/states.php:50 +msgid "Northern Territory" +msgstr "" + +#: i18n/states.php:51 +msgid "Queensland" +msgstr "" + +#: i18n/states.php:52 +msgid "South Australia" +msgstr "" + +#: i18n/states.php:53 +msgid "Tasmania" +msgstr "" + +#: i18n/states.php:54 +msgid "Victoria" +msgstr "" + +#: i18n/states.php:55 +msgid "Western Australia" +msgstr "" + +#: i18n/states.php:60 +msgid "Acre" +msgstr "" + +#: i18n/states.php:61 +msgid "Alagoas" +msgstr "" + +#: i18n/states.php:62 +msgid "Amapá" +msgstr "" + +#: i18n/states.php:63 +#: i18n/states.php:355 +msgid "Amazonas" +msgstr "" + +#: i18n/states.php:64 +msgid "Bahia" +msgstr "" + +#: i18n/states.php:65 +msgid "Ceará" +msgstr "" + +#: i18n/states.php:66 +#: i18n/states.php:278 +msgid "Distrito Federal" +msgstr "" + +#: i18n/states.php:67 +msgid "Espírito Santo" +msgstr "" + +#: i18n/states.php:68 +msgid "Goiás" +msgstr "" + +#: i18n/states.php:69 +msgid "Maranhão" +msgstr "" + +#: i18n/states.php:70 +msgid "Mato Grosso" +msgstr "" + +#: i18n/states.php:71 +msgid "Mato Grosso do Sul" +msgstr "" + +#: i18n/states.php:72 +msgid "Minas Gerais" +msgstr "" + +#: i18n/states.php:73 +msgid "Pará" +msgstr "" + +#: i18n/states.php:74 +msgid "Paraíba" +msgstr "" + +#: i18n/states.php:75 +msgid "Paraná" +msgstr "" + +#: i18n/states.php:76 +msgid "Pernambuco" +msgstr "" + +#: i18n/states.php:77 +msgid "Piauí" +msgstr "" + +#: i18n/states.php:78 +msgid "Rio de Janeiro" +msgstr "" + +#: i18n/states.php:79 +msgid "Rio Grande do Norte" +msgstr "" + +#: i18n/states.php:80 +msgid "Rio Grande do Sul" +msgstr "" + +#: i18n/states.php:81 +msgid "Rondônia" +msgstr "" + +#: i18n/states.php:82 +msgid "Roraima" +msgstr "" + +#: i18n/states.php:83 +msgid "Santa Catarina" +msgstr "" + +#: i18n/states.php:84 +msgid "São Paulo" +msgstr "" + +#: i18n/states.php:85 +msgid "Sergipe" +msgstr "" + +#: i18n/states.php:86 +msgid "Tocantins" +msgstr "" + +#: i18n/states.php:90 +msgid "Blagoevgrad" +msgstr "" + +#: i18n/states.php:91 +msgid "Burgas" +msgstr "" + +#: i18n/states.php:92 +msgid "Dobrich" +msgstr "" + +#: i18n/states.php:93 +msgid "Gabrovo" +msgstr "" + +#: i18n/states.php:94 +msgid "Haskovo" +msgstr "" + +#: i18n/states.php:95 +msgid "Kardzhali" +msgstr "" + +#: i18n/states.php:96 +msgid "Kyustendil" +msgstr "" + +#: i18n/states.php:97 +msgid "Lovech" +msgstr "" + +#: i18n/states.php:99 +msgid "Pazardzhik" +msgstr "" + +#: i18n/states.php:100 +msgid "Pernik" +msgstr "" + +#: i18n/states.php:101 +msgid "Pleven" +msgstr "" + +#: i18n/states.php:102 +msgid "Plovdiv" +msgstr "" + +#: i18n/states.php:103 +msgid "Razgrad" +msgstr "" + +#: i18n/states.php:104 +msgid "Ruse" +msgstr "" + +#: i18n/states.php:105 +msgid "Shumen" +msgstr "" + +#: i18n/states.php:106 +msgid "Silistra" +msgstr "" + +#: i18n/states.php:107 +msgid "Sliven" +msgstr "" + +#: i18n/states.php:108 +msgid "Smolyan" +msgstr "" + +#: i18n/states.php:109 +msgid "Sofia" +msgstr "" + +#: i18n/states.php:110 +msgid "Sofia-Grad" +msgstr "" + +#: i18n/states.php:111 +msgid "Stara Zagora" +msgstr "" + +#: i18n/states.php:112 +msgid "Targovishte" +msgstr "" + +#: i18n/states.php:113 +msgid "Varna" +msgstr "" + +#: i18n/states.php:114 +msgid "Veliko Tarnovo" +msgstr "" + +#: i18n/states.php:115 +msgid "Vidin" +msgstr "" + +#: i18n/states.php:116 +msgid "Vratsa" +msgstr "" + +#: i18n/states.php:117 +msgid "Yambol" +msgstr "" + +#: i18n/states.php:121 +msgid "Yunnan / 云南" +msgstr "" + +#: i18n/states.php:122 +msgid "Beijing / 北京" +msgstr "" + +#: i18n/states.php:123 +msgid "Tianjin / 天津" +msgstr "" + +#: i18n/states.php:124 +msgid "Hebei / 河北" +msgstr "" + +#: i18n/states.php:125 +msgid "Shanxi / 山西" +msgstr "" + +#: i18n/states.php:126 +msgid "Inner Mongolia / 內蒙古" +msgstr "" + +#: i18n/states.php:127 +msgid "Liaoning / 辽宁" +msgstr "" + +#: i18n/states.php:128 +msgid "Jilin / 吉林" +msgstr "" + +#: i18n/states.php:129 +msgid "Heilongjiang / 黑龙江" +msgstr "" + +#: i18n/states.php:130 +msgid "Shanghai / 上海" +msgstr "" + +#: i18n/states.php:131 +msgid "Jiangsu / 江苏" +msgstr "" + +#: i18n/states.php:132 +msgid "Zhejiang / 浙江" +msgstr "" + +#: i18n/states.php:133 +msgid "Anhui / 安徽" +msgstr "" + +#: i18n/states.php:134 +msgid "Fujian / 福建" +msgstr "" + +#: i18n/states.php:135 +msgid "Jiangxi / 江西" +msgstr "" + +#: i18n/states.php:136 +msgid "Shandong / 山东" +msgstr "" + +#: i18n/states.php:137 +msgid "Henan / 河南" +msgstr "" + +#: i18n/states.php:138 +msgid "Hubei / 湖北" +msgstr "" + +#: i18n/states.php:139 +msgid "Hunan / 湖南" +msgstr "" + +#: i18n/states.php:140 +msgid "Guangdong / 广东" +msgstr "" + +#: i18n/states.php:141 +msgid "Guangxi Zhuang / 广西壮族" +msgstr "" + +#: i18n/states.php:142 +msgid "Hainan / 海南" +msgstr "" + +#: i18n/states.php:143 +msgid "Chongqing / 重庆" +msgstr "" + +#: i18n/states.php:144 +msgid "Sichuan / 四川" +msgstr "" + +#: i18n/states.php:145 +msgid "Guizhou / 贵州" +msgstr "" + +#: i18n/states.php:146 +msgid "Shaanxi / 陕西" +msgstr "" + +#: i18n/states.php:147 +msgid "Gansu / 甘肃" +msgstr "" + +#: i18n/states.php:148 +msgid "Qinghai / 青海" +msgstr "" + +#: i18n/states.php:149 +msgid "Ningxia Hui / 宁夏" +msgstr "" + +#: i18n/states.php:150 +msgid "Macau / 澳门" +msgstr "" + +#: i18n/states.php:151 +msgid "Tibet / 西藏" +msgstr "" + +#: i18n/states.php:152 +msgid "Xinjiang / 新疆" +msgstr "" + +#: i18n/states.php:156 +msgid "Hong Kong Island" +msgstr "" + +#: i18n/states.php:157 +msgid "Kowloon" +msgstr "" + +#: i18n/states.php:158 +msgid "New Territories" +msgstr "" + +#: i18n/states.php:162 +msgid "Bács-Kiskun" +msgstr "" + +#: i18n/states.php:163 +msgid "Békés" +msgstr "" + +#: i18n/states.php:164 +msgid "Baranya" +msgstr "" + +#: i18n/states.php:165 +msgid "Borsod-Abaúj-Zemplén" +msgstr "" + +#: i18n/states.php:166 +msgid "Budapest" +msgstr "" + +#: i18n/states.php:167 +msgid "Csongrád" +msgstr "" + +#: i18n/states.php:168 +msgid "Fejér" +msgstr "" + +#: i18n/states.php:169 +msgid "Győr-Moson-Sopron" +msgstr "" + +#: i18n/states.php:170 +msgid "Hajdú-Bihar" +msgstr "" + +#: i18n/states.php:171 +msgid "Heves" +msgstr "" + +#: i18n/states.php:172 +msgid "Jász-Nagykun-Szolnok" +msgstr "" + +#: i18n/states.php:173 +msgid "Komárom-Esztergom" +msgstr "" + +#: i18n/states.php:174 +msgid "Nógrád" +msgstr "" + +#: i18n/states.php:175 +msgid "Pest" +msgstr "" + +#: i18n/states.php:176 +msgid "Somogy" +msgstr "" + +#: i18n/states.php:177 +msgid "Szabolcs-Szatmár-Bereg" +msgstr "" + +#: i18n/states.php:178 +msgid "Tolna" +msgstr "" + +#: i18n/states.php:179 +msgid "Vas" +msgstr "" + +#: i18n/states.php:180 +msgid "Veszprém" +msgstr "" + +#: i18n/states.php:181 +msgid "Zala" +msgstr "" + +#: i18n/states.php:186 +msgid "Daerah Istimewa Aceh" +msgstr "" + +#: i18n/states.php:187 +msgid "Sumatera Utara" +msgstr "" + +#: i18n/states.php:188 +msgid "Sumatera Barat" +msgstr "" + +#: i18n/states.php:189 +msgid "Riau" +msgstr "" + +#: i18n/states.php:190 +msgid "Kepulauan Riau" +msgstr "" + +#: i18n/states.php:191 +msgid "Jambi" +msgstr "" + +#: i18n/states.php:192 +msgid "Sumatera Selatan" +msgstr "" + +#: i18n/states.php:193 +msgid "Bangka Belitung" +msgstr "" + +#: i18n/states.php:194 +msgid "Bengkulu" +msgstr "" + +#: i18n/states.php:195 +msgid "Lampung" +msgstr "" + +#: i18n/states.php:196 +msgid "DKI Jakarta" +msgstr "" + +#: i18n/states.php:197 +msgid "Jawa Barat" +msgstr "" + +#: i18n/states.php:198 +msgid "Banten" +msgstr "" + +#: i18n/states.php:199 +msgid "Jawa Tengah" +msgstr "" + +#: i18n/states.php:200 +msgid "Jawa Timur" +msgstr "" + +#: i18n/states.php:201 +msgid "Daerah Istimewa Yogyakarta" +msgstr "" + +#: i18n/states.php:202 +msgid "Bali" +msgstr "" + +#: i18n/states.php:203 +msgid "Nusa Tenggara Barat" +msgstr "" + +#: i18n/states.php:204 +msgid "Nusa Tenggara Timur" +msgstr "" + +#: i18n/states.php:205 +msgid "Kalimantan Barat" +msgstr "" + +#: i18n/states.php:206 +msgid "Kalimantan Tengah" +msgstr "" + +#: i18n/states.php:207 +msgid "Kalimantan Timur" +msgstr "" + +#: i18n/states.php:208 +msgid "Kalimantan Selatan" +msgstr "" + +#: i18n/states.php:209 +msgid "Kalimantan Utara" +msgstr "" + +#: i18n/states.php:210 +msgid "Sulawesi Utara" +msgstr "" + +#: i18n/states.php:211 +msgid "Sulawesi Tengah" +msgstr "" + +#: i18n/states.php:212 +msgid "Sulawesi Tenggara" +msgstr "" + +#: i18n/states.php:213 +msgid "Sulawesi Barat" +msgstr "" + +#: i18n/states.php:214 +msgid "Sulawesi Selatan" +msgstr "" + +#: i18n/states.php:215 +msgid "Gorontalo" +msgstr "" + +#: i18n/states.php:216 +msgid "Maluku" +msgstr "" + +#: i18n/states.php:217 +msgid "Maluku Utara" +msgstr "" + +#: i18n/states.php:218 +msgid "Papua" +msgstr "" + +#: i18n/states.php:219 +msgid "Papua Barat" +msgstr "" + +#: i18n/states.php:223 +msgid "Khuzestan" +msgstr "" + +#: i18n/states.php:224 +msgid "Tehran" +msgstr "" + +#: i18n/states.php:225 +msgid "Ilaam" +msgstr "" + +#: i18n/states.php:226 +msgid "Bushehr" +msgstr "" + +#: i18n/states.php:227 +msgid "Ardabil" +msgstr "" + +#: i18n/states.php:228 +msgid "Isfahan" +msgstr "" + +#: i18n/states.php:229 +msgid "Yazd" +msgstr "" + +#: i18n/states.php:230 +msgid "Kermanshah" +msgstr "" + +#: i18n/states.php:231 +msgid "Kerman" +msgstr "" + +#: i18n/states.php:232 +msgid "Hamadan" +msgstr "" + +#: i18n/states.php:233 +msgid "Ghazvin" +msgstr "" + +#: i18n/states.php:234 +msgid "Zanjan" +msgstr "" + +#: i18n/states.php:235 +msgid "Luristan" +msgstr "" + +#: i18n/states.php:236 +msgid "Alborz" +msgstr "" + +#: i18n/states.php:237 +msgid "East Azerbaijan" +msgstr "" + +#: i18n/states.php:238 +msgid "West Azerbaijan" +msgstr "" + +#: i18n/states.php:239 +msgid "Chaharmahal and Bakhtiari" +msgstr "" + +#: i18n/states.php:240 +msgid "South Khorasan" +msgstr "" + +#: i18n/states.php:241 +msgid "Razavi Khorasan" +msgstr "" + +#: i18n/states.php:242 +msgid "North Khorasan" +msgstr "" + +#: i18n/states.php:243 +msgid "Semnan" +msgstr "" + +#: i18n/states.php:244 +msgid "Fars" +msgstr "" + +#: i18n/states.php:245 +msgid "Qom" +msgstr "" + +#: i18n/states.php:246 +msgid "Kurdistan" +msgstr "" + +#: i18n/states.php:247 +msgid "Kohgiluyeh and BoyerAhmad" +msgstr "" + +#: i18n/states.php:248 +msgid "Golestan" +msgstr "" + +#: i18n/states.php:249 +msgid "Gilan" +msgstr "" + +#: i18n/states.php:250 +msgid "Mazandaran" +msgstr "" + +#: i18n/states.php:251 +msgid "Markazi" +msgstr "" + +#: i18n/states.php:252 +msgid "Hormozgan" +msgstr "" + +#: i18n/states.php:253 +msgid "Sistan and Baluchestan" +msgstr "" + +#: i18n/states.php:259 +msgid "Johor" +msgstr "" + +#: i18n/states.php:260 +msgid "Kedah" +msgstr "" + +#: i18n/states.php:261 +msgid "Kelantan" +msgstr "" + +#: i18n/states.php:262 +msgid "Melaka" +msgstr "" + +#: i18n/states.php:263 +msgid "Negeri Sembilan" +msgstr "" + +#: i18n/states.php:264 +msgid "Pahang" +msgstr "" + +#: i18n/states.php:265 +msgid "Perak" +msgstr "" + +#: i18n/states.php:266 +msgid "Perlis" +msgstr "" + +#: i18n/states.php:267 +msgid "Pulau Pinang" +msgstr "" + +#: i18n/states.php:268 +msgid "Sabah" +msgstr "" + +#: i18n/states.php:269 +msgid "Sarawak" +msgstr "" + +#: i18n/states.php:270 +msgid "Selangor" +msgstr "" + +#: i18n/states.php:271 +msgid "Terengganu" +msgstr "" + +#: i18n/states.php:272 +msgid "W.P. Kuala Lumpur" +msgstr "" + +#: i18n/states.php:273 +msgid "W.P. Labuan" +msgstr "" + +#: i18n/states.php:274 +msgid "W.P. Putrajaya" +msgstr "" + +#: i18n/states.php:279 +msgid "Jalisco" +msgstr "" + +#: i18n/states.php:280 +msgid "Nuevo León" +msgstr "" + +#: i18n/states.php:281 +msgid "Aguascalientes" +msgstr "" + +#: i18n/states.php:282 +msgid "Baja California Norte" +msgstr "" + +#: i18n/states.php:283 +msgid "Baja California Sur" +msgstr "" + +#: i18n/states.php:284 +msgid "Campeche" +msgstr "" + +#: i18n/states.php:285 +msgid "Chiapas" +msgstr "" + +#: i18n/states.php:286 +msgid "Chihuahua" +msgstr "" + +#: i18n/states.php:287 +msgid "Coahuila" +msgstr "" + +#: i18n/states.php:288 +msgid "Colima" +msgstr "" + +#: i18n/states.php:289 +msgid "Durango" +msgstr "" + +#: i18n/states.php:290 +msgid "Guanajuato" +msgstr "" + +#: i18n/states.php:291 +msgid "Guerrero" +msgstr "" + +#: i18n/states.php:292 +msgid "Hidalgo" +msgstr "" + +#: i18n/states.php:293 +msgid "Edo. de México" +msgstr "" + +#: i18n/states.php:294 +msgid "Michoacán" +msgstr "" + +#: i18n/states.php:295 +msgid "Morelos" +msgstr "" + +#: i18n/states.php:296 +msgid "Nayarit" +msgstr "" + +#: i18n/states.php:297 +msgid "Oaxaca" +msgstr "" + +#: i18n/states.php:298 +msgid "Puebla" +msgstr "" + +#: i18n/states.php:299 +msgid "Querétaro" +msgstr "" + +#: i18n/states.php:300 +msgid "Quintana Roo" +msgstr "" + +#: i18n/states.php:301 +msgid "San Luis Potosí" +msgstr "" + +#: i18n/states.php:302 +msgid "Sinaloa" +msgstr "" + +#: i18n/states.php:303 +msgid "Sonora" +msgstr "" + +#: i18n/states.php:304 +msgid "Tabasco" +msgstr "" + +#: i18n/states.php:305 +msgid "Tamaulipas" +msgstr "" + +#: i18n/states.php:306 +msgid "Tlaxcala" +msgstr "" + +#: i18n/states.php:307 +msgid "Veracruz" +msgstr "" + +#: i18n/states.php:308 +msgid "Yucatán" +msgstr "" + +#: i18n/states.php:309 +msgid "Zacatecas" +msgstr "" + +#: i18n/states.php:314 +msgid "Drenthe" +msgstr "" + +#: i18n/states.php:315 +msgid "Flevoland" +msgstr "" + +#: i18n/states.php:316 +msgid "Friesland" +msgstr "" + +#: i18n/states.php:317 +msgid "Gelderland" +msgstr "" + +#: i18n/states.php:318 +msgid "Groningen" +msgstr "" + +#: i18n/states.php:319 +msgid "Limburg" +msgstr "" + +#: i18n/states.php:320 +msgid "North Brabant" +msgstr "" + +#: i18n/states.php:321 +msgid "North Holland" +msgstr "" + +#: i18n/states.php:322 +msgid "Overijssel" +msgstr "" + +#: i18n/states.php:323 +msgid "South Holland" +msgstr "" + +#: i18n/states.php:324 +msgid "Utrecht" +msgstr "" + +#: i18n/states.php:325 +msgid "Zeeland" +msgstr "" + +#: i18n/states.php:328 +msgid "Bonaire" +msgstr "" + +#: i18n/states.php:329 +msgid "Saba" +msgstr "" + +#: i18n/states.php:330 +msgid "Sint Eustatius" +msgstr "" + +#: i18n/states.php:334 +msgid "Auckland" +msgstr "" + +#: i18n/states.php:335 +msgid "Bay of Plenty" +msgstr "" + +#: i18n/states.php:336 +msgid "Canterbury" +msgstr "" + +#: i18n/states.php:337 +msgid "Hawke’s Bay" +msgstr "" + +#: i18n/states.php:338 +msgid "Manawatu-Wanganui" +msgstr "" + +#: i18n/states.php:339 +msgid "Marlborough" +msgstr "" + +#: i18n/states.php:340 +msgid "Nelson" +msgstr "" + +#: i18n/states.php:341 +msgid "Northland" +msgstr "" + +#: i18n/states.php:342 +msgid "Otago" +msgstr "" + +#: i18n/states.php:343 +msgid "Southland" +msgstr "" + +#: i18n/states.php:344 +msgid "Taranaki" +msgstr "" + +#: i18n/states.php:345 +msgid "Tasman" +msgstr "" + +#: i18n/states.php:346 +msgid "Waikato" +msgstr "" + +#: i18n/states.php:347 +msgid "Wairarapa" +msgstr "" + +#: i18n/states.php:348 +msgid "Wellington" +msgstr "" + +#: i18n/states.php:349 +msgid "West Coast" +msgstr "" + +#: i18n/states.php:353 +msgid "El Callao" +msgstr "" + +#: i18n/states.php:354 +msgid "Municipalidad Metropolitana de Lima" +msgstr "" + +#: i18n/states.php:356 +msgid "Ancash" +msgstr "" + +#: i18n/states.php:357 +msgid "Apurímac" +msgstr "" + +#: i18n/states.php:358 +msgid "Arequipa" +msgstr "" + +#: i18n/states.php:359 +msgid "Ayacucho" +msgstr "" + +#: i18n/states.php:360 +msgid "Cajamarca" +msgstr "" + +#: i18n/states.php:361 +msgid "Cusco" +msgstr "" + +#: i18n/states.php:362 +msgid "Huancavelica" +msgstr "" + +#: i18n/states.php:363 +msgid "Huánuco" +msgstr "" + +#: i18n/states.php:364 +msgid "Ica" +msgstr "" + +#: i18n/states.php:365 +msgid "Junín" +msgstr "" + +#: i18n/states.php:366 +msgid "La Libertad" +msgstr "" + +#: i18n/states.php:367 +msgid "Lambayeque" +msgstr "" + +#: i18n/states.php:368 +msgid "Lima" +msgstr "" + +#: i18n/states.php:369 +msgid "Loreto" +msgstr "" + +#: i18n/states.php:370 +msgid "Madre de Dios" +msgstr "" + +#: i18n/states.php:371 +msgid "Moquegua" +msgstr "" + +#: i18n/states.php:372 +msgid "Pasco" +msgstr "" + +#: i18n/states.php:373 +msgid "Piura" +msgstr "" + +#: i18n/states.php:374 +msgid "Puno" +msgstr "" + +#: i18n/states.php:375 +msgid "San Martín" +msgstr "" + +#: i18n/states.php:376 +msgid "Tacna" +msgstr "" + +#: i18n/states.php:377 +msgid "Tumbes" +msgstr "" + +#: i18n/states.php:378 +msgid "Ucayali" +msgstr "" + +#: i18n/states.php:382 +msgid "Eastern Cape" +msgstr "" + +#: i18n/states.php:383 +msgid "Free State" +msgstr "" + +#: i18n/states.php:384 +msgid "Gauteng" +msgstr "" + +#: i18n/states.php:385 +msgid "KwaZulu-Natal" +msgstr "" + +#: i18n/states.php:386 +msgid "Limpopo" +msgstr "" + +#: i18n/states.php:387 +msgid "Mpumalanga" +msgstr "" + +#: i18n/states.php:388 +msgid "Northern Cape" +msgstr "" + +#: i18n/states.php:389 +msgid "North West" +msgstr "" + +#: i18n/states.php:390 +msgid "Western Cape" +msgstr "" + +#: includes/admin/adjustments/adjustment-functions.php:43 +#: includes/admin/adjustments/adjustment-functions.php:99 +#: includes/admin/admin-pages.php:88 +#: includes/admin/reporting/reports.php:2184 +#: includes/admin/settings/display-settings.php:40 +#: includes/post-types.php:129 +#: includes/reports/reports-functions.php:336 +#: includes/upgrades/functions.php:146 +msgid "Discounts" +msgstr "" + +#: includes/admin/adjustments/adjustment-functions.php:44 +#: includes/admin/payments/payments-history.php:166 +#: includes/post-types.php:40 +#: includes/post-types.php:89 +#: includes/post-types.php:119 +msgid "Add New" +msgstr "" + +#: includes/admin/adjustments/adjustment-functions.php:49 +#: includes/admin/admin-actions.php:207 +#: includes/admin/customers/customers.php:27 +#: includes/admin/payments/payments-history.php:27 +#: includes/admin/settings/display-settings.php:152 +#: includes/admin/tools.php:42 +msgid "Secondary menu" +msgstr "" + +#: includes/admin/admin-bar.php:57 +msgid "Notifications" +msgstr "" + +#: includes/admin/admin-bar.php:70 +#: includes/admin/admin-pages.php:98 +msgid "Store Reports" +msgstr "" + +#: includes/admin/admin-bar.php:81 +msgid "Store Settings" +msgstr "" + +#. translators: plural downlaods label +#: includes/admin/admin-bar.php:93 +msgid "All %1$s" +msgstr "" + +#: includes/admin/admin-bar.php:99 +#: includes/gateways/functions.php:29 +msgid "Live" +msgstr "" + +#: includes/admin/admin-bar.php:100 +#: src/Admin/Settings/Tabs/Gateways.php:173 +msgid "Test Mode" +msgstr "" + +#. translators: %s: store status ("Test Mode" or "Live Mode") +#: includes/admin/admin-bar.php:111 +msgid "Store Status: %s" +msgstr "" + +#: includes/admin/admin-bar.php:131 +#: includes/admin/admin-deprecated-functions.php:267 +#: includes/admin/plugins.php:38 +#: src/Admin/Promos/Notices/Lite.php:42 +#: src/Admin/Settings/Tabs/Gateways.php:305 +#: src/Lite/Admin/Menu.php:36 +#: src/Lite/Admin/Menu.php:37 +msgid "Upgrade to Pro" +msgstr "" + +#. translators: %s: Whether this is a development domain (1 for true, 0 for false) +#: includes/admin/admin-bar.php:146 +msgid "Development Domain %s" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:30 +#: src/Admin/Settings/Tabs/Gateways.php:124 +msgid "Banned Emails" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:32 +msgid "Emails placed in the box below will not be allowed to make purchases." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:38 +msgid "Enter emails and/or domains (starting with \"@\") and/or TLDs (starting with \".\") to disallow, one per line." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:43 +#: includes/admin/tools.php:355 +msgid "Save" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:68 +#: includes/admin/payments/actions.php:72 +#: includes/admin/payments/actions.php:77 +#: includes/admin/payments/actions.php:276 +#: includes/admin/payments/actions.php:306 +#: includes/emails/actions.php:60 +msgid "You do not have permission to edit this payment record" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:68 +#: includes/admin/admin-deprecated-functions.php:816 +#: includes/admin/customers/customer-actions.php:315 +#: includes/admin/customers/customer-actions.php:371 +#: includes/admin/customers/customer-actions.php:563 +#: includes/admin/customers/customer-actions.php:620 +#: includes/admin/discounts/discount-actions.php:32 +#: includes/admin/discounts/discount-actions.php:179 +#: includes/admin/discounts/discount-actions.php:184 +#: includes/admin/discounts/discount-actions.php:193 +#: includes/admin/discounts/discount-actions.php:333 +#: includes/admin/discounts/discount-actions.php:338 +#: includes/admin/discounts/discount-actions.php:343 +#: includes/admin/discounts/discount-actions.php:372 +#: includes/admin/discounts/discount-actions.php:377 +#: includes/admin/discounts/discount-actions.php:405 +#: includes/admin/discounts/edit-discount.php:17 +#: includes/admin/discounts/edit-discount.php:28 +#: includes/admin/downloads/dashboard-columns.php:190 +#: includes/admin/import/class-batch-import-downloads.php:65 +#: includes/admin/import/class-batch-import-payments.php:77 +#: includes/admin/import/class-batch-import.php:199 +#: includes/admin/payments/actions.php:72 +#: includes/admin/payments/actions.php:77 +#: includes/admin/payments/actions.php:210 +#: includes/admin/payments/actions.php:244 +#: includes/admin/payments/actions.php:276 +#: includes/admin/payments/actions.php:306 +#: includes/admin/payments/add-order.php:63 +#: includes/admin/payments/add-order.php:67 +#: includes/admin/payments/view-order-details.php:23 +#: includes/admin/payments/view-order-details.php:31 +#: includes/admin/payments/view-order-details.php:42 +#: includes/admin/payments/view-order-details.php:60 +#: includes/admin/payments/view-refund.php:23 +#: includes/admin/payments/view-refund.php:32 +#: includes/admin/reporting/class-api-requests-logs-list-table.php:106 +#: includes/admin/reporting/class-export.php:188 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:109 +#: includes/admin/reporting/export/class-batch-export.php:146 +#: includes/admin/reporting/export/export-actions.php:24 +#: includes/admin/tools.php:1161 +#: includes/admin/tools.php:1167 +#: includes/admin/tools/class-edd-tools-recount-all-stats.php:78 +#: includes/admin/tools/class-edd-tools-recount-customer-stats.php:114 +#: includes/admin/tools/class-edd-tools-recount-download-stats.php:117 +#: includes/admin/tools/class-edd-tools-recount-single-customer-stats.php:114 +#: includes/admin/tools/class-edd-tools-recount-store-earnings.php:157 +#: includes/admin/tools/class-edd-tools-reset-stats.php:121 +#: includes/admin/upgrades/classes/class-file-download-log-migration.php:134 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:239 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:310 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:407 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:518 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:593 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:711 +#: includes/admin/upgrades/v3/class-base.php:117 +#: includes/api/class-edd-api.php:2030 +#: includes/api/class-edd-api.php:2034 +#: includes/api/class-edd-api.php:2046 +#: includes/api/class-edd-api.php:2048 +#: includes/blocks/includes/forms/recaptcha.php:180 +#: includes/cart/class-edd-cart.php:1452 +#: includes/cart/class-edd-cart.php:1466 +#: includes/class-edd-license-handler.php:476 +#: includes/deprecated-functions.php:488 +#: includes/deprecated-functions.php:505 +#: includes/deprecated-functions.php:513 +#: includes/deprecated-functions.php:915 +#: includes/download-functions.php:1520 +#: includes/EDD_SL_Plugin_Updater.php:486 +#: includes/emails/actions.php:60 +#: includes/emails/template.php:241 +#: includes/error-tracking.php:60 +#: includes/gateways/functions.php:320 +#: includes/gateways/manual.php:33 +#: includes/gateways/paypal-standard.php:170 +#: includes/gateways/paypal/admin/connect.php:568 +#: includes/gateways/paypal/admin/connect.php:572 +#: includes/gateways/paypal/admin/connect.php:609 +#: includes/gateways/paypal/admin/connect.php:613 +#: includes/gateways/paypal/admin/connect.php:761 +#: includes/gateways/paypal/checkout-actions.php:114 +#: includes/gateways/stripe/includes/admin/upgrade-functions.php:110 +#: includes/process-download.php:286 +#: includes/process-download.php:914 +#: includes/query-filters.php:51 +#: includes/user-functions.php:912 +#: includes/user-functions.php:937 +#: includes/user-functions.php:989 +#: includes/users/lost-password.php:264 +#: src/Extensions/Updater.php:480 +msgid "Error" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:92 +msgid "Popular" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:93 +msgid "New" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:94 +#: includes/admin/class-list-table.php:138 +msgid "All" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:162 +msgid "Apps and Integrations for Easy Digital Downloads" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:173 +#: includes/admin/admin-deprecated-functions.php:198 +msgid "Browse All Integrations" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:176 +msgid "These add functionality to your Easy Digital Downloads powered store." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:238 +msgid "These extensions could not be retrieved from the server. Please try again later." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:263 +#: src/Admin/Extensions/Menu.php:35 +msgid "EDD Extensions" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:263 +#: includes/admin/settings/contextual-help.php:123 +#: includes/admin/settings/register-settings.php:726 +#: src/Admin/Extensions/Menu.php:36 +msgid "Extensions" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:301 +msgid "System Information" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:304 +msgid "Use the system information below to help troubleshoot problems." +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:315 +msgid "Download System Info File" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:316 +#: includes/admin/tools.php:1262 +msgid "Copy to Clipboard" +msgstr "" + +#: includes/admin/admin-deprecated-functions.php:816 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:239 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:310 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:407 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:518 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:593 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:711 +#: includes/gateways/stripe/includes/admin/upgrade-functions.php:110 +msgid "You do not have permission to do shop upgrades" +msgstr "" + +#. translators: 1. link to EDD site; 2. link to wordpress.org to review +#: includes/admin/admin-footer.php:29 +msgid "Thank you for using Easy Digital Downloads! Please rate us on WordPress.org" +msgstr "" + +#: includes/admin/admin-pages.php:86 +#: includes/admin/customers/class-customer-table.php:242 +#: includes/admin/payments/payments-history.php:159 +#: includes/admin/settings/display-settings.php:37 +#: includes/orders/functions/types.php:85 +#: includes/upgrades/functions.php:150 +msgid "Orders" +msgstr "" + +#: includes/admin/admin-pages.php:87 +#: includes/admin/customers/customers.php:77 +#: includes/admin/reporting/reports.php:2459 +#: includes/admin/settings/display-settings.php:43 +msgid "Customers" +msgstr "" + +#: includes/admin/admin-pages.php:89 +#: includes/admin/reporting/contextual-help.php:61 +#: includes/admin/reporting/reports.php:178 +#: includes/admin/settings/display-settings.php:34 +msgid "Reports" +msgstr "" + +#: includes/admin/admin-pages.php:90 +msgid "EDD Settings" +msgstr "" + +#: includes/admin/admin-pages.php:90 +#: includes/admin/plugins.php:47 +#: includes/admin/settings/display-settings.php:31 +#: includes/admin/settings/display-settings.php:361 +#: includes/blocks/build/buy-button/index.js:1 +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/build/confirmation/index.js:1 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/pro/currency-selector/index.js:1 +#: includes/blocks/build/receipt/index.js:1 +#: includes/blocks/build/register/index.js:1 +msgid "Settings" +msgstr "" + +#: includes/admin/admin-pages.php:91 +msgid "EDD Tools" +msgstr "" + +#: includes/admin/admin-pages.php:91 +#: includes/admin/customers/customers.php:1140 +#: includes/admin/settings/display-settings.php:46 +#: includes/admin/tools.php:39 +#: src/Admin/Onboarding/Wizard.php:246 +msgid "Tools" +msgstr "" + +#: includes/admin/admin-pages.php:94 +msgid "EDD Upgrades" +msgstr "" + +#: includes/admin/class-api-keys-table.php:37 +#: includes/admin/reporting/export/class-batch-export-api-requests.php:43 +msgid "API Key" +msgstr "" + +#: includes/admin/class-api-keys-table.php:38 +#: includes/admin/tools.php:108 +msgid "API Keys" +msgstr "" + +#: includes/admin/class-api-keys-table.php:127 +msgid "View Log" +msgstr "" + +#: includes/admin/class-api-keys-table.php:134 +msgid "Reissue" +msgstr "" + +#: includes/admin/class-api-keys-table.php:140 +msgid "Revoke" +msgstr "" + +#: includes/admin/class-api-keys-table.php:156 +#: includes/blocks/views/checkout/purchase-form/register.php:7 +#: includes/checkout/template.php:508 +#: includes/checkout/template.php:514 +#: includes/emails/tags.php:179 +#: templates/shortcode-register.php:20 +msgid "Username" +msgstr "" + +#: includes/admin/class-api-keys-table.php:157 +msgid "Public Key" +msgstr "" + +#: includes/admin/class-api-keys-table.php:158 +msgid "Token" +msgstr "" + +#: includes/admin/class-api-keys-table.php:159 +msgid "Secret Key" +msgstr "" + +#: includes/admin/class-api-keys-table.php:187 +msgid "Generate New API Keys" +msgstr "" + +#. translators: %s: URL to the settings page +#: includes/admin/class-edd-notices.php:273 +msgid "No checkout page is configured. Set one in Settings." +msgstr "" + +#. translators: %s: Uploads directory +#: includes/admin/class-edd-notices.php:329 +msgid "The files in %s are not currently protected." +msgstr "" + +#: includes/admin/class-edd-notices.php:330 +msgid "To protect them, you must add this NGINX redirect rule." +msgstr "" + +#. translators: %s: Dismiss notice URL +#: includes/admin/class-edd-notices.php:332 +#: includes/admin/class-edd-notices.php:351 +msgid "If you have already done this, or it does not apply to your site, you may permenently %s." +msgstr "" + +#. translators: %s: Dismiss notice URL +#: includes/admin/class-edd-notices.php:332 +#: includes/admin/class-edd-notices.php:351 +msgid "dismiss this notice" +msgstr "" + +#. translators: the upload directory +#: includes/admin/class-edd-notices.php:349 +#: src/Admin/SiteHealth/Direct.php:220 +msgid "The .htaccess file is missing from: %s" +msgstr "" + +#. translators: the upload directory +#: includes/admin/class-edd-notices.php:350 +#: src/Admin/SiteHealth/Direct.php:225 +msgid "First, please resave the Misc settings tab a few times. If this warning continues to appear, create a file called \".htaccess\" in the %s directory, and copy the following into it:" +msgstr "" + +#. translators: 1. link to the recount tool; 2. link to the plugins screen. +#: includes/admin/class-edd-notices.php:373 +msgid "Easy Digital Downloads 2.5 contains a built in recount tool. Please deactivate the Easy Digital Downloads - Recount Earnings plugin" +msgstr "" + +#: includes/admin/class-edd-notices.php:407 +msgid "Review Tax Rates" +msgstr "" + +#. translators: Link to review existing tax rates. +#: includes/admin/class-edd-notices.php:414 +msgid "A default tax rate was detected." +msgstr "" + +#. translators: Link to review existing tax rates. +#: includes/admin/class-edd-notices.php:414 +msgid "This setting is no longer used in this version of Easy Digital Downloads. Please confirm your regional tax rates are properly configured and update tax settings to remove this notice." +msgstr "" + +#: includes/admin/class-edd-notices.php:436 +msgid "Settings updated." +msgstr "" + +#. translators: %s: Next order number +#: includes/admin/class-edd-notices.php:449 +msgid "The sequential starting number could not be updated because it could conflict with existing orders. The next assigned order number is %s." +msgstr "" + +#: includes/admin/class-edd-notices.php:481 +msgid "Easy Digital Downloads is migrating orders. Sales and earnings data for your store will be updated when all orders have been migrated." +msgstr "" + +#: includes/admin/class-edd-notices.php:510 +msgid "Discount code added." +msgstr "" + +#: includes/admin/class-edd-notices.php:516 +msgid "There was a problem adding that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:523 +msgid "A discount with that code already exists, please use a different code." +msgstr "" + +#: includes/admin/class-edd-notices.php:530 +msgid "Discount code updated." +msgstr "" + +#: includes/admin/class-edd-notices.php:536 +msgid "No changes were made to that discount code." +msgstr "" + +#: includes/admin/class-edd-notices.php:542 +msgid "There was a problem updating that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:549 +msgid "The discount code could not be added because one or more of the required fields was empty, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:556 +msgid "The discount code entered is invalid; only alphanumeric characters are allowed, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:563 +msgid "The discount amount must be a valid percentage or numeric flat amount. Please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:570 +msgid "Discount code deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:576 +msgid "There was a problem deleting that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:583 +msgid "Discount code activated." +msgstr "" + +#: includes/admin/class-edd-notices.php:589 +msgid "There was a problem activating that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:596 +msgid "Discount code deactivated." +msgstr "" + +#: includes/admin/class-edd-notices.php:602 +msgid "There was a problem deactivating that discount code, please try again." +msgstr "" + +#: includes/admin/class-edd-notices.php:615 +msgid "The reports have been refreshed." +msgstr "" + +#: includes/admin/class-edd-notices.php:627 +msgid "The settings have been imported." +msgstr "" + +#: includes/admin/class-edd-notices.php:633 +msgid "API keys successfully generated." +msgstr "" + +#: includes/admin/class-edd-notices.php:639 +msgid "The specified user already has API keys." +msgstr "" + +#: includes/admin/class-edd-notices.php:646 +msgid "API keys successfully regenerated." +msgstr "" + +#: includes/admin/class-edd-notices.php:652 +msgid "API keys successfully revoked." +msgstr "" + +#: includes/admin/class-edd-notices.php:659 +msgid "The test email was sent successfully." +msgstr "" + +#: includes/admin/class-edd-notices.php:667 +msgid "The test email summary was sent successfully." +msgstr "" + +#: includes/admin/class-edd-notices.php:676 +msgid "Your extensions could not be refreshed because you have not verified your license key." +msgstr "" + +#: includes/admin/class-edd-notices.php:690 +msgid "The note has been added successfully." +msgstr "" + +#: includes/admin/class-edd-notices.php:696 +msgid "The order has been updated successfully." +msgstr "" + +#: includes/admin/class-edd-notices.php:702 +msgid "Order successfully created." +msgstr "" + +#: includes/admin/class-edd-notices.php:708 +msgid "The order has been moved to the trash." +msgstr "" + +#: includes/admin/class-edd-notices.php:714 +msgid "The order has been restored." +msgstr "" + +#: includes/admin/class-edd-notices.php:720 +msgid "The order has been deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:726 +msgid "The purchase receipt has been resent." +msgstr "" + +#: includes/admin/class-edd-notices.php:732 +msgid "Failed to send purchase receipt." +msgstr "" + +#: includes/admin/class-edd-notices.php:738 +msgid "The order note has been deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:750 +msgid "Customer successfully deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:756 +msgid "User successfully verified." +msgstr "" + +#: includes/admin/class-edd-notices.php:762 +msgid "Customer email added." +msgstr "" + +#: includes/admin/class-edd-notices.php:768 +msgid "Customer email deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:774 +msgid "Failed to delete customer email." +msgstr "" + +#: includes/admin/class-edd-notices.php:781 +msgid "Primary email updated for customer." +msgstr "" + +#: includes/admin/class-edd-notices.php:787 +msgid "Failed to set primary email." +msgstr "" + +#: includes/admin/class-edd-notices.php:794 +msgid "Customer address deleted." +msgstr "" + +#: includes/admin/class-edd-notices.php:800 +msgid "Failed to delete customer address." +msgstr "" + +#. Translators: 1. opening strong tag, do not translate; 2. closing strong tag, do not translate +#: includes/admin/class-edd-notices.php:813 +msgid "Congratulations! You are now running %1$sEasy Digital Downloads (Pro)%2$s." +msgstr "" + +#: includes/admin/class-edd-notices.php:858 +msgid "Easy Digital Downloads debug logging is enabled. Please only leave it enabled for as long as it is needed for troubleshooting." +msgstr "" + +#: includes/admin/class-edd-notices.php:861 +msgid "View Debug Log" +msgstr "" + +#: includes/admin/class-edd-notices.php:862 +msgid "Delete Log File and Disable Logging" +msgstr "" + +#: includes/admin/class-edd-notices.php:878 +#: includes/admin/customers/customer-actions.php:612 +#: includes/admin/emails/email-summary/class-edd-email-summary-admin.php:55 +#: includes/admin/tools/class-edd-tools-recount-all-stats.php:78 +#: includes/admin/tools/class-edd-tools-recount-download-stats.php:117 +#: includes/gateways/paypal/admin/connect.php:97 +#: includes/gateways/paypal/admin/connect.php:207 +#: includes/gateways/paypal/admin/connect.php:261 +#: includes/gateways/paypal/admin/connect.php:379 +#: includes/gateways/paypal/admin/connect.php:568 +#: includes/gateways/paypal/admin/connect.php:572 +#: includes/gateways/paypal/admin/connect.php:609 +#: includes/gateways/paypal/admin/connect.php:613 +#: includes/gateways/paypal/admin/connect.php:675 +#: includes/gateways/paypal/admin/connect.php:709 +#: includes/gateways/paypal/admin/connect.php:732 +#: includes/gateways/paypal/admin/connect.php:761 +#: src/Admin/Promos/PromoHandler.php:139 +#: src/Admin/Promos/PromoHandler.php:146 +#: src/Admin/Promos/PromoHandler.php:151 +msgid "You do not have permission to perform this action." +msgstr "" + +#: includes/admin/class-edd-notices.php:883 +msgid "The debug log has been cleared and logging has been disabled." +msgstr "" + +#: includes/admin/class-sections.php:100 +#: includes/admin/discounts/contextual-help.php:55 +#: includes/admin/settings/contextual-help.php:61 +#: includes/admin/settings/display-settings.php:196 +#: includes/admin/settings/register-settings.php:719 +#: includes/admin/settings/register-settings.php:770 +#: includes/admin/settings/register-settings.php:776 +#: includes/admin/settings/register-settings.php:782 +#: includes/admin/settings/register-settings.php:785 +#: includes/admin/settings/register-settings.php:789 +#: includes/admin/settings/register-settings.php:802 +#: includes/admin/tools.php:107 +msgid "General" +msgstr "" + +#: includes/admin/class-sections.php:283 +msgid "Invalid section" +msgstr "" + +#: includes/admin/class-sections.php:312 +msgid "Empty" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:71 +#: includes/admin/customers/class-customer-email-addresses-table.php:78 +#: includes/admin/customers/customers.php:867 +#: includes/admin/customers/customers.php:1027 +msgid "Primary" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:137 +#: includes/admin/customers/class-customer-email-addresses-table.php:131 +#: includes/admin/customers/class-customer-table.php:127 +#: includes/misc-functions.php:1460 +#: includes/payments/functions.php:535 +msgid "Pending" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:142 +#: includes/admin/customers/class-customer-email-addresses-table.php:136 +#: includes/admin/customers/class-customer-table.php:132 +#: includes/admin/discounts/edit-discount.php:263 +#: includes/class-edd-discount.php:684 +#: includes/misc-functions.php:1455 +msgid "Active" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:172 +#: includes/admin/customers/class-customer-email-addresses-table.php:166 +msgid "View" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:182 +#: includes/admin/customers/class-customer-addresses-table.php:318 +#: includes/admin/customers/class-customer-email-addresses-table.php:178 +#: includes/admin/customers/class-customer-email-addresses-table.php:307 +#: includes/admin/customers/class-customer-table.php:187 +#: includes/admin/customers/class-customer-table.php:272 +#: includes/admin/customers/customers.php:885 +#: includes/admin/customers/customers.php:1030 +#: includes/admin/customers/customers.php:1095 +#: includes/admin/discounts/class-discount-codes-table.php:230 +#: includes/admin/discounts/class-discount-codes-table.php:316 +#: includes/gateways/stripe/includes/template-functions.php:494 +#: src/Admin/PassHandler/Handler.php:74 +#: src/Admin/Settings/Tabs/Privacy.php:138 +#: src/Licensing/Traits/Controls.php:83 +msgid "Delete" +msgstr "" + +#. translators: customer address id +#: includes/admin/customers/class-customer-addresses-table.php:248 +msgid "Address ID: %s" +msgstr "" + +#. translators: customer name or address id +#. translators: customer email +#. translators: customer name or email +#. translators: discount name +#. translators: the order number +#. translators: product name +#: includes/admin/customers/class-customer-addresses-table.php:259 +#: includes/admin/customers/class-customer-email-addresses-table.php:250 +#: includes/admin/customers/class-customer-table.php:217 +#: includes/admin/discounts/add-discount.php:85 +#: includes/admin/discounts/add-discount.php:122 +#: includes/admin/discounts/class-discount-codes-table.php:281 +#: includes/admin/discounts/edit-discount.php:126 +#: includes/admin/discounts/edit-discount.php:163 +#: includes/admin/payments/class-payments-table.php:516 +#: includes/admin/payments/class-refund-items-table.php:447 +msgid "Select %s" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:283 +#: includes/admin/customers/customers.php:965 +#: includes/admin/customers/customers.php:985 +#: includes/admin/payments/orders.php:129 +#: includes/admin/payments/orders.php:158 +#: includes/admin/reporting/class-export-payments.php:61 +#: includes/admin/reporting/export/class-batch-export-payments.php:46 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:47 +#: includes/blocks/views/checkout/purchase-form/address.php:32 +msgid "Address" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:284 +#: includes/admin/customers/customers.php:967 +#: includes/admin/customers/customers.php:1003 +#: includes/admin/views/tmpl-tax-rates-table-add.php:26 +#: includes/admin/views/tmpl-tax-rates-table-meta.php:17 +#: includes/admin/views/tmpl-tax-rates-table-row.php:27 +msgid "Region" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:285 +#: includes/admin/customers/customers.php:969 +#: includes/admin/customers/customers.php:1017 +#: includes/admin/reporting/class-export-payments.php:65 +#: includes/admin/reporting/export/class-batch-export-payments.php:50 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:51 +#: includes/admin/tools.php:800 +#: includes/admin/views/tmpl-tax-rates-table-add.php:17 +#: includes/admin/views/tmpl-tax-rates-table-meta.php:16 +#: includes/admin/views/tmpl-tax-rates-table-row.php:18 +#: includes/blocks/views/checkout/purchase-form/address.php:8 +#: includes/gateways/stripe/includes/template-functions.php:563 +#: includes/gateways/stripe/includes/template-functions.php:572 +#: templates/shortcode-profile-editor.php:168 +msgid "Country" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:286 +#: includes/admin/customers/class-customer-email-addresses-table.php:277 +#: includes/admin/customers/customer-functions.php:134 +#: includes/admin/payments/class-payments-table.php:405 +#: includes/admin/payments/orders.php:117 +#: includes/admin/payments/orders.php:152 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:111 +#: includes/admin/reporting/class-sales-logs-list-table.php:98 +msgid "Customer" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:287 +#: includes/admin/customers/class-customer-email-addresses-table.php:278 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:17 +#: includes/blocks/build/pro/currency-selector/index.js:1 +msgid "Type" +msgstr "" + +#: includes/admin/customers/class-customer-addresses-table.php:288 +#: includes/admin/customers/class-customer-email-addresses-table.php:279 +#: includes/admin/customers/class-customer-table.php:244 +#: includes/admin/customers/customers.php:713 +#: includes/admin/customers/customers.php:754 +#: includes/admin/downloads/dashboard-columns.php:37 +#: includes/admin/payments/class-payments-table.php:408 +#: includes/admin/payments/orders.php:1073 +#: includes/admin/reporting/class-api-requests-logs-list-table.php:53 +#: includes/admin/reporting/class-export-download-history.php:59 +#: includes/admin/reporting/class-export-payments.php:76 +#: includes/admin/reporting/class-export.php:75 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:116 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:112 +#: includes/admin/reporting/class-sales-logs-list-table.php:101 +#: includes/admin/reporting/export/class-batch-export-api-requests.php:46 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:43 +#: includes/admin/reporting/export/class-batch-export-payments.php:62 +#: includes/admin/reporting/export/class-batch-export-sales-and-earnings.php:59 +#: includes/admin/reporting/export/class-batch-export-sales.php:58 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:62 +#: includes/admin/tools.php:665 +#: includes/blocks/views/orders/totals.php:38 +#: includes/privacy-functions.php:946 +#: includes/reports/reports-functions.php:316 +#: templates/history-purchases.php:52 +#: templates/shortcode-receipt.php:69 +msgid "Date" +msgstr "" + +#: includes/admin/customers/class-customer-email-addresses-table.php:79 +msgid "Secondary" +msgstr "" + +#: includes/admin/customers/class-customer-email-addresses-table.php:243 +msgid "Primary email addresses cannot be deleted." +msgstr "" + +#: includes/admin/customers/class-customer-email-addresses-table.php:276 +#: includes/admin/customers/class-customer-table.php:241 +#: includes/admin/customers/customers.php:853 +#: includes/admin/payments/orders.php:123 +#: includes/admin/payments/orders.php:280 +#: includes/admin/reporting/class-export-customers.php:63 +#: includes/admin/reporting/class-export-customers.php:74 +#: includes/admin/reporting/class-export-payments.php:58 +#: includes/admin/reporting/export/class-batch-export-customers.php:54 +#: includes/admin/reporting/export/class-batch-export-payments.php:43 +#: includes/admin/reporting/export/class-batch-export-sales.php:50 +#: includes/admin/reporting/export/class-batch-export-taxed-customers.php:43 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:43 +#: includes/admin/tools.php:556 +#: includes/blocks/views/forms/registration.php:24 +#: includes/emails/tags.php:185 +#: templates/shortcode-register.php:25 +#: includes/blocks/build/register/index.js:1 +msgid "Email" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:185 +#: includes/admin/discounts/class-discount-codes-table.php:208 +#: includes/admin/payments/class-payments-table.php:543 +msgid "Edit" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:186 +#: includes/admin/payments/orders.php:141 +#: includes/admin/tools.php:110 +#: includes/upgrades/functions.php:166 +msgid "Logs" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:240 +#: includes/admin/discounts/add-discount.php:31 +#: includes/admin/discounts/class-discount-codes-table.php:72 +#: includes/admin/discounts/edit-discount.php:72 +#: includes/admin/downloads/dashboard-columns.php:31 +#: includes/admin/reporting/class-export-customers.php:71 +#: includes/admin/reporting/export/class-batch-export-customers.php:53 +#: includes/admin/reporting/export/class-batch-export-downloads.php:43 +#: includes/admin/reporting/export/class-batch-export-sales.php:51 +#: includes/admin/reporting/export/class-batch-export-taxed-customers.php:42 +#: includes/admin/tools.php:565 +#: includes/privacy-functions.php:594 +#: includes/reports/data/discounts/class-top-five-discounts-list-table.php:75 +#: includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php:159 +#: includes/reports/data/downloads/class-top-selling-downloads-list-table.php:55 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:57 +#: templates/shortcode-receipt.php:204 +#: includes/blocks/build/terms/index.js:1 +msgid "Name" +msgstr "" + +#: includes/admin/customers/class-customer-table.php:243 +msgid "Spent" +msgstr "" + +#: includes/admin/customers/customer-actions.php:35 +#: includes/admin/customers/customer-actions.php:198 +#: includes/admin/customers/customer-actions.php:502 +msgid "You do not have permission to edit this customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:44 +#: includes/admin/customers/customer-actions.php:428 +#: includes/admin/customers/customer-actions.php:513 +msgid "Cheatin' eh?!" +msgstr "" + +#: includes/admin/customers/customer-actions.php:62 +msgid "Please enter a valid email address." +msgstr "" + +#. translators: %d: user ID +#: includes/admin/customers/customer-actions.php:70 +msgid "The User ID %d is already associated with a different customer." +msgstr "" + +#. translators: %d: user ID +#: includes/admin/customers/customer-actions.php:77 +msgid "The User ID %d does not exist. Please assign an existing user." +msgstr "" + +#. translators: %s: user login or email address +#: includes/admin/customers/customer-actions.php:104 +msgid "Failed to attach user. The login or email address %s was not found." +msgstr "" + +#: includes/admin/customers/customer-actions.php:208 +msgid "Email address is missing." +msgstr "" + +#: includes/admin/customers/customer-actions.php:210 +msgid "Customer ID is required." +msgstr "" + +#: includes/admin/customers/customer-actions.php:212 +msgid "An error has occured. Please try again." +msgstr "" + +#: includes/admin/customers/customer-actions.php:218 +#: includes/user-functions.php:937 +msgid "Nonce verification failed." +msgstr "" + +#: includes/admin/customers/customer-actions.php:224 +msgid "Invalid email address." +msgstr "" + +#: includes/admin/customers/customer-actions.php:239 +msgid "Email already associated with this customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:245 +msgid "Email address is already associated with another customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:261 +msgid "Email successfully added to customer." +msgstr "" + +#. translators: 1. email address; 2. username +#: includes/admin/customers/customer-actions.php:268 +msgid "Email address %1$s added by %2$s" +msgstr "" + +#. translators: 1. email address; 2. username +#: includes/admin/customers/customer-actions.php:273 +msgid "Email address %1$s set as primary by %2$s" +msgstr "" + +#: includes/admin/customers/customer-actions.php:315 +#: includes/admin/customers/customer-actions.php:371 +#: includes/admin/customers/customer-actions.php:563 +#: includes/admin/customers/customer-actions.php:620 +#: includes/admin/import/import-functions.php:25 +#: includes/admin/import/import-functions.php:106 +#: includes/admin/reporting/export/export-actions.php:24 +#: includes/api/class-edd-api.php:2030 +#: includes/class-edd-license-handler.php:476 +msgid "Nonce verification failed" +msgstr "" + +#. translators: 1. email address; 2. username +#. translators: 1. email address; 2. user who removed email. +#: includes/admin/customers/customer-actions.php:331 +#: includes/shortcodes.php:991 +msgid "Email address %1$s removed by %2$s" +msgstr "" + +#: includes/admin/customers/customer-actions.php:386 +msgid "Email address %s set as primary by %s" +msgstr "" + +#: includes/admin/customers/customer-actions.php:415 +msgid "You do not have permission to delete this customer." +msgstr "" + +#: includes/admin/customers/customer-actions.php:432 +msgid "Please confirm you want to delete this customer" +msgstr "" + +#: includes/admin/customers/customer-actions.php:478 +msgid "Error deleting customer" +msgstr "" + +#: includes/admin/customers/customer-actions.php:483 +msgid "Invalid Customer ID" +msgstr "" + +#: includes/admin/customers/customer-actions.php:532 +msgid "Failed to disconnect user from customer" +msgstr "" + +#: includes/admin/customers/customer-functions.php:43 +msgctxt "Customer Details tab title" +msgid "Profile" +msgstr "" + +#: includes/admin/customers/customer-functions.php:44 +msgctxt "Customer Emails tab title" +msgid "Emails" +msgstr "" + +#: includes/admin/customers/customer-functions.php:45 +msgctxt "Customer Addresses tab title" +msgid "Addresses" +msgstr "" + +#: includes/admin/customers/customer-functions.php:46 +msgctxt "Customer Notes tab title" +msgid "Notes" +msgstr "" + +#: includes/admin/customers/customer-functions.php:47 +msgctxt "Customer Tools tab title" +msgid "Tools" +msgstr "" + +#: includes/admin/customers/customer-functions.php:63 +msgctxt "Delete Customer tab title" +msgid "Delete" +msgstr "" + +#. translators: link to send an email +#: includes/admin/customers/customer-functions.php:110 +#: templates/account-pending.php:8 +msgid "Your account is pending verification. Please click the link in your email to activate your account. No email? Click here to send a new activation code." +msgstr "" + +#: includes/admin/customers/customer-functions.php:111 +msgid "Account Pending Verification" +msgstr "" + +#: includes/admin/customers/customer-functions.php:156 +#: includes/admin/reporting/class-sales-logs-list-table.php:64 +msgid "Unnamed Customer" +msgstr "" + +#: includes/admin/customers/customers.php:78 +msgid "Email Addresses" +msgstr "" + +#: includes/admin/customers/customers.php:79 +msgid "Physical Addresses" +msgstr "" + +#. translators: the active screen, eg "Search Customers" or "Search Customer Email Addresses" +#: includes/admin/customers/customers.php:260 +#: includes/class-edd-html-elements.php:48 +msgid "Search %s" +msgstr "" + +#: includes/admin/customers/customers.php:292 +msgid "You are not permitted to view this data." +msgstr "" + +#: includes/admin/customers/customers.php:297 +#: includes/admin/customers/customers.php:305 +msgid "Invalid Customer ID Provided." +msgstr "" + +#: includes/admin/customers/customers.php:311 +msgid "Customer Details" +msgstr "" + +#: includes/admin/customers/customers.php:466 +msgid "Edit Profile" +msgstr "" + +#: includes/admin/customers/customers.php:472 +#: includes/admin/thickbox.php:183 +#: includes/gateways/stripe/includes/admin/admin-filters.php:105 +#: includes/gateways/stripe/includes/template-functions.php:650 +#: includes/gateways/stripe/includes/template-functions.php:693 +#: includes/scripts.php:545 +msgid "Cancel" +msgstr "" + +#: includes/admin/customers/customers.php:473 +#: includes/gateways/stripe/includes/template-functions.php:478 +msgid "Update" +msgstr "" + +#: includes/admin/customers/customers.php:483 +msgid "Customer Address" +msgstr "" + +#: includes/admin/customers/customers.php:495 +msgid "Address 1" +msgstr "" + +#: includes/admin/customers/customers.php:496 +msgid "Address 2" +msgstr "" + +#: includes/admin/customers/customers.php:497 +#: includes/admin/customers/customers.php:966 +#: includes/admin/customers/customers.php:996 +#: includes/admin/reporting/class-export-payments.php:63 +#: includes/admin/reporting/export/class-batch-export-payments.php:48 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:49 +#: includes/admin/tools.php:772 +#: includes/blocks/views/checkout/purchase-form/address.php:54 +#: includes/blocks/views/checkout/purchase-form/address.php:60 +#: includes/checkout/template.php:393 +#: includes/gateways/stripe/includes/template-functions.php:538 +#: templates/shortcode-profile-editor.php:158 +msgid "City" +msgstr "" + +#: includes/admin/customers/customers.php:520 +#: includes/admin/tools.php:781 +#: templates/shortcode-profile-editor.php:177 +msgid "State / Province" +msgstr "" + +#: includes/admin/customers/customers.php:524 +#: includes/admin/customers/customers.php:968 +#: includes/admin/customers/customers.php:1010 +msgid "Postal Code" +msgstr "" + +#: includes/admin/customers/customers.php:531 +#: includes/admin/reporting/export/class-batch-export-payments.php:45 +msgid "Customer Name" +msgstr "" + +#: includes/admin/customers/customers.php:538 +#: includes/admin/payments/orders.php:356 +msgid "Customer Email" +msgstr "" + +#: includes/admin/customers/customers.php:544 +msgid "Customer Since" +msgstr "" + +#. translators: The date. +#: includes/admin/customers/customers.php:550 +#: includes/admin/payments/orders.php:233 +msgid "Customer since %s" +msgstr "" + +#. translators: %s: user id +#: includes/admin/customers/customers.php:565 +msgid "User %s missing" +msgstr "" + +#: includes/admin/customers/customers.php:573 +msgid "Not a registered user" +msgstr "" + +#. translators: the customer's lifetime number of sales +#: includes/admin/customers/customers.php:602 +msgid "%s Completed Sale" +msgid_plural "%s Completed Sales" +msgstr[0] "" +msgstr[1] "" + +#. translators: the customer's lifetime value +#: includes/admin/customers/customers.php:622 +msgid "%s Lifetime Value" +msgstr "" + +#: includes/admin/customers/customers.php:641 +msgid "Agreements" +msgstr "" + +#: includes/admin/customers/customers.php:647 +#: includes/admin/customers/customers.php:658 +msgid " — Agreed to Terms" +msgstr "" + +#: includes/admin/customers/customers.php:651 +#: includes/admin/customers/customers.php:679 +msgid "Previous Agreement Dates" +msgstr "" + +#: includes/admin/customers/customers.php:661 +#: includes/admin/customers/customers.php:690 +msgid "Estimated Privacy Policy Date" +msgstr "" + +#: includes/admin/customers/customers.php:661 +msgid "This customer made a purchase prior to agreement dates being logged, this is the date of their last purchase. If your site was displaying the agreement checkbox at that time, this is our best estimate as to when they last agreed to your terms." +msgstr "" + +#: includes/admin/customers/customers.php:665 +msgid "No terms agreement found." +msgstr "" + +#: includes/admin/customers/customers.php:675 +#: includes/admin/customers/customers.php:687 +msgid " — Agreed to Privacy Policy" +msgstr "" + +#: includes/admin/customers/customers.php:690 +msgid "This customer made a purchase prior to privacy policy dates being logged, this is the date of their last purchase. If your site was displaying the privacy policy checkbox at that time, this is our best estimate as to when they last agreed to your privacy policy." +msgstr "" + +#: includes/admin/customers/customers.php:694 +msgid "No privacy policy agreement found." +msgstr "" + +#: includes/admin/customers/customers.php:706 +#: includes/admin/dashboard-widgets.php:222 +msgid "Recent Orders" +msgstr "" + +#: includes/admin/customers/customers.php:710 +#: includes/admin/customers/customers.php:751 +#: includes/admin/payments/class-payments-table.php:404 +#: includes/admin/reporting/reports.php:1178 +msgid "Number" +msgstr "" + +#: includes/admin/customers/customers.php:711 +#: includes/admin/customers/customers.php:752 +#: includes/admin/payments/class-payments-table.php:406 +#: includes/admin/payments/orders.php:898 +#: includes/admin/payments/orders.php:904 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:111 +#: includes/admin/reporting/class-gateways-reports-table.php:74 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:59 +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:76 +msgid "Gateway" +msgstr "" + +#: includes/admin/customers/customers.php:712 +#: includes/admin/customers/customers.php:753 +#: includes/admin/payments/class-payments-table.php:407 +#: includes/admin/payments/class-refund-items-table.php:90 +#: includes/admin/tools.php:722 +#: includes/admin/views/tmpl-order-refund.php:39 +#: includes/admin/views/tmpl-order-total.php:16 +#: includes/blocks/views/checkout/cart/cart-total.php:8 +#: includes/blocks/views/orders/totals.php:63 +#: includes/gateways/stripe/includes/payment-methods/payment-request/functions.php:150 +#: includes/gateways/stripe/includes/payment-methods/payment-request/functions.php:197 +#: templates/checkout_cart.php:129 +#: templates/shortcode-receipt.php:163 +msgid "Total" +msgstr "" + +#: includes/admin/customers/customers.php:742 +msgid "No orders found" +msgstr "" + +#: includes/admin/customers/customers.php:747 +msgid "Recent Refunds" +msgstr "" + +#: includes/admin/customers/customers.php:776 +msgid "No refunds found" +msgstr "" + +#. translators: %s: plural downloads label +#: includes/admin/customers/customers.php:784 +msgid "Purchased %s" +msgstr "" + +#. translators: %s: plural downloads label +#: includes/admin/customers/customers.php:809 +#: includes/blocks/build/buy-button/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +msgid "No %s Found" +msgstr "" + +#: includes/admin/customers/customers.php:844 +msgid "Customer Emails" +msgstr "" + +#: includes/admin/customers/customers.php:845 +msgid "This customer can use any of the emails listed here when making new purchases." +msgstr "" + +#: includes/admin/customers/customers.php:854 +msgid "Date Added" +msgstr "" + +#: includes/admin/customers/customers.php:881 +#: includes/admin/customers/customers.php:927 +msgid "Make Primary" +msgstr "" + +#: includes/admin/customers/customers.php:910 +msgid "No emails found." +msgstr "" + +#: includes/admin/customers/customers.php:919 +#: includes/admin/customers/customers.php:921 +#: includes/admin/payments/orders.php:329 +#: includes/blocks/views/orders/guest.php:10 +#: includes/gateways/stripe/includes/payment-methods/buy-now/template.php:107 +#: includes/privacy-functions.php:773 +msgid "Email Address" +msgstr "" + +#: includes/admin/customers/customers.php:931 +msgid "Add Email" +msgstr "" + +#: includes/admin/customers/customers.php:958 +#: includes/upgrades/functions.php:154 +msgid "Customer Addresses" +msgstr "" + +#: includes/admin/customers/customers.php:970 +#: includes/admin/customers/customers.php:1024 +msgid "First Used" +msgstr "" + +#: includes/admin/customers/customers.php:1064 +#: includes/admin/payments/orders.php:135 +#: includes/admin/reporting/export/class-batch-export-downloads.php:56 +msgid "Notes" +msgstr "" + +#: includes/admin/customers/customers.php:1101 +msgid "Are you sure you want to delete this customer?" +msgstr "" + +#: includes/admin/customers/customers.php:1106 +msgid "Delete all associated payments and records?" +msgstr "" + +#: includes/admin/customers/customers.php:1116 +msgid "Delete Customer" +msgstr "" + +#: includes/admin/customers/customers.php:1143 +#: includes/admin/tools.php:169 +msgid "Recount Customer Stats" +msgstr "" + +#: includes/admin/customers/customers.php:1144 +msgid "Use this tool to recalculate the purchase count and total value of the customer." +msgstr "" + +#: includes/admin/customers/customers.php:1151 +#: includes/admin/tools.php:142 +msgid "Recount Stats" +msgstr "" + +#: includes/admin/customers/customers.php:1187 +msgid "This customer's user account is pending verification." +msgstr "" + +#: includes/admin/customers/customers.php:1189 +msgid "Verify account." +msgstr "" + +#: includes/admin/dashboard-widgets.php:24 +msgid "Easy Digital Downloads Sales Summary" +msgstr "" + +#: includes/admin/dashboard-widgets.php:45 +msgid "Easy Digital Downloads is performing a database migration via WP-CLI." +msgstr "" + +#: includes/admin/dashboard-widgets.php:46 +#: includes/admin/dashboard-widgets.php:63 +msgid "This summary will be available when that has completed." +msgstr "" + +#: includes/admin/dashboard-widgets.php:62 +msgid "Easy Digital Downloads needs to upgrade the database." +msgstr "" + +#: includes/admin/dashboard-widgets.php:65 +msgid "Begin the upgrade." +msgstr "" + +#: includes/admin/dashboard-widgets.php:134 +msgid "Current Month" +msgstr "" + +#: includes/admin/dashboard-widgets.php:134 +#: includes/admin/dashboard-widgets.php:150 +#: includes/admin/dashboard-widgets.php:170 +#: includes/admin/dashboard-widgets.php:193 +#: includes/admin/reporting/reports.php:247 +#: includes/admin/reporting/reports.php:272 +#: includes/admin/reporting/reports.php:345 +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:80 +msgid "Net" +msgstr "" + +#: includes/admin/dashboard-widgets.php:139 +#: includes/admin/dashboard-widgets.php:155 +#: includes/admin/dashboard-widgets.php:176 +#: includes/admin/reporting/class-download-reports-table.php:104 +#: includes/admin/reporting/export/class-batch-export-downloads.php:58 +#: includes/admin/reporting/export/class-batch-export-sales-and-earnings.php:61 +#: includes/admin/reporting/graphing.php:299 +#: includes/admin/reporting/graphing.php:598 +#: includes/admin/reporting/reports.php:254 +#: includes/admin/reporting/reports.php:353 +#: includes/admin/reporting/reports.php:714 +#: includes/admin/reporting/reports.php:903 +#: includes/admin/reporting/reports.php:1322 +#: includes/admin/reporting/reports.php:1508 +#: includes/admin/reporting/reports.php:1626 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:61 +#: includes/blocks/build/downloads/index.js:1 +msgid "Earnings" +msgstr "" + +#: includes/admin/dashboard-widgets.php:142 +#: includes/admin/dashboard-widgets.php:159 +#: includes/admin/dashboard-widgets.php:182 +#: includes/admin/dashboard-widgets.php:203 +msgid "Sale" +msgid_plural "Sales" +msgstr[0] "" +msgstr[1] "" + +#: includes/admin/dashboard-widgets.php:150 +#: includes/class-edd-stats.php:84 +#: includes/reports/reports-functions.php:501 +msgid "Last Month" +msgstr "" + +#: includes/admin/dashboard-widgets.php:170 +#: includes/class-edd-stats.php:79 +#: includes/reports/reports-functions.php:495 +msgid "Today" +msgstr "" + +#: includes/admin/dashboard-widgets.php:193 +msgid "All Time" +msgstr "" + +#: includes/admin/dashboard-widgets.php:199 +#: includes/admin/reporting/class-categories-reports-table.php:78 +#: includes/admin/tools.php:1019 +#: includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php:161 +msgid "Total Earnings" +msgstr "" + +#: includes/admin/dashboard-widgets.php:239 +msgid "No Name" +msgstr "" + +#. translators: 1. customer name; 2. number of items purchased; 3. order total +#: includes/admin/dashboard-widgets.php:244 +msgid "%1$s purchased %2$s item for %3$s" +msgid_plural "%1$s purchased %2$s items for %3$s" +msgstr[0] "" +msgstr[1] "" + +#: includes/admin/dashboard-widgets.php:268 +msgid "View All Orders" +msgstr "" + +#: includes/admin/dashboard-widgets.php:300 +msgid "%s " +msgid_plural "%s " +msgstr[0] "" +msgstr[1] "" + +#: includes/admin/discounts/add-discount.php:16 +#: includes/post-types.php:120 +msgid "Add New Discount" +msgstr "" + +#: includes/admin/discounts/add-discount.php:34 +#: includes/admin/discounts/edit-discount.php:75 +msgid "Summer Sale" +msgstr "" + +#: includes/admin/discounts/add-discount.php:35 +#: includes/admin/discounts/edit-discount.php:76 +msgid "The name of this discount. Customers will see this on checkout." +msgstr "" + +#: includes/admin/discounts/add-discount.php:43 +#: includes/admin/discounts/class-discount-codes-table.php:73 +#: includes/admin/discounts/edit-discount.php:84 +#: includes/reports/data/discounts/class-top-five-discounts-list-table.php:76 +msgid "Code" +msgstr "" + +#: includes/admin/discounts/add-discount.php:46 +#: includes/admin/discounts/edit-discount.php:87 +msgid "10PERCENT" +msgstr "" + +#: includes/admin/discounts/add-discount.php:47 +#: includes/admin/discounts/edit-discount.php:88 +msgid "The code customers will enter to apply this discount. Only alphanumeric characters are allowed." +msgstr "" + +#: includes/admin/discounts/add-discount.php:57 +#: includes/admin/discounts/class-discount-codes-table.php:74 +#: includes/admin/discounts/edit-discount.php:98 +#: includes/admin/payments/orders.php:812 +#: includes/admin/payments/refunds.php:225 +#: includes/admin/reporting/class-export-payments.php:70 +#: includes/admin/reporting/export/class-batch-export-payments.php:56 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:56 +#: includes/admin/reporting/reports.php:1166 +#: includes/admin/views/tmpl-order-adjustment-discount.php:41 +#: includes/admin/views/tmpl-order-adjustment.php:54 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:80 +#: includes/admin/views/tmpl-order-form-add-order-item.php:225 +#: includes/admin/views/tmpl-order-item.php:78 +#: includes/admin/views/tmpl-order-subtotal.php:18 +#: includes/admin/views/tmpl-order-tax.php:29 +#: includes/admin/views/tmpl-order-total.php:23 +#: includes/reports/data/discounts/class-top-five-discounts-list-table.php:78 +#: templates/history-purchases.php:53 +msgid "Amount" +msgstr "" + +#: includes/admin/discounts/add-discount.php:61 +#: includes/admin/discounts/edit-discount.php:102 +msgid "10.00" +msgstr "" + +#: includes/admin/discounts/add-discount.php:62 +#: includes/admin/discounts/edit-discount.php:103 +msgid "Amount Type" +msgstr "" + +#: includes/admin/discounts/add-discount.php:68 +#: includes/admin/discounts/edit-discount.php:109 +msgid "The amount as a percentage or flat rate. Cannot be left blank." +msgstr "" + +#: includes/admin/discounts/add-discount.php:76 +#: includes/admin/discounts/edit-discount.php:117 +msgid "%s Requirements" +msgstr "" + +#: includes/admin/discounts/add-discount.php:90 +#: includes/admin/discounts/edit-discount.php:131 +msgid "Cart must contain all selected %s" +msgstr "" + +#: includes/admin/discounts/add-discount.php:91 +#: includes/admin/discounts/edit-discount.php:132 +msgid "Cart needs one or more of the selected %s" +msgstr "" + +#: includes/admin/discounts/add-discount.php:97 +#: includes/admin/discounts/edit-discount.php:138 +msgid "Apply discount to entire purchase." +msgstr "" + +#: includes/admin/discounts/add-discount.php:101 +#: includes/admin/discounts/edit-discount.php:142 +msgid "Apply discount only to selected %s." +msgstr "" + +#: includes/admin/discounts/add-discount.php:105 +#: includes/admin/discounts/edit-discount.php:146 +msgid "%s this discount can only be applied to. Leave blank for any." +msgstr "" + +#: includes/admin/discounts/add-discount.php:113 +#: includes/admin/discounts/edit-discount.php:154 +msgid "Excluded %s" +msgstr "" + +#: includes/admin/discounts/add-discount.php:124 +#: includes/admin/discounts/edit-discount.php:165 +msgid "%s this discount cannot be applied to. Leave blank for none." +msgstr "" + +#: includes/admin/discounts/add-discount.php:132 +#: includes/admin/discounts/edit-discount.php:173 +msgid "Start date" +msgstr "" + +#: includes/admin/discounts/add-discount.php:138 +#: includes/admin/discounts/edit-discount.php:179 +msgid "Start Date Hour" +msgstr "" + +#: includes/admin/discounts/add-discount.php:144 +#: includes/admin/discounts/edit-discount.php:185 +msgid "Start Date Minute" +msgstr "" + +#: includes/admin/discounts/add-discount.php:149 +#: includes/admin/discounts/edit-discount.php:190 +msgid "Pick the date and time this discount will start on. Leave blank for no start date." +msgstr "" + +#: includes/admin/discounts/add-discount.php:157 +#: includes/admin/discounts/edit-discount.php:198 +msgid "Expiration date" +msgstr "" + +#: includes/admin/discounts/add-discount.php:163 +#: includes/admin/discounts/edit-discount.php:204 +msgid "Expiration Date Hour" +msgstr "" + +#: includes/admin/discounts/add-discount.php:169 +#: includes/admin/discounts/edit-discount.php:210 +msgid "Expiration Date Minute" +msgstr "" + +#: includes/admin/discounts/add-discount.php:174 +#: includes/admin/discounts/edit-discount.php:215 +msgid "Pick the date and time this discount will expire on. Leave blank to never expire." +msgstr "" + +#: includes/admin/discounts/add-discount.php:182 +#: includes/admin/discounts/edit-discount.php:223 +msgid "Minimum Amount" +msgstr "" + +#: includes/admin/discounts/add-discount.php:185 +#: includes/admin/discounts/edit-discount.php:226 +msgid "No minimum" +msgstr "" + +#: includes/admin/discounts/add-discount.php:186 +#: includes/admin/discounts/edit-discount.php:227 +msgid "The minimum subtotal of item prices in a cart before this discount may be applied." +msgstr "" + +#: includes/admin/discounts/add-discount.php:194 +#: includes/admin/discounts/edit-discount.php:235 +msgid "Max Uses" +msgstr "" + +#: includes/admin/discounts/add-discount.php:197 +#: includes/admin/discounts/edit-discount.php:238 +#: includes/class-edd-cli.php:442 +msgid "Unlimited" +msgstr "" + +#: includes/admin/discounts/add-discount.php:198 +#: includes/admin/discounts/edit-discount.php:239 +msgid "The maximum number of times this discount can be used." +msgstr "" + +#: includes/admin/discounts/add-discount.php:206 +#: includes/admin/discounts/edit-discount.php:247 +msgid "Use Once Per Customer" +msgstr "" + +#: includes/admin/discounts/add-discount.php:210 +#: includes/admin/discounts/edit-discount.php:251 +msgid "Prevent customers from using this discount more than once." +msgstr "" + +#: includes/admin/discounts/add-discount.php:233 +msgid "Add Discount Code" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:75 +#: includes/reports/data/discounts/class-top-five-discounts-list-table.php:77 +msgid "Uses" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:76 +msgid "Start Date" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:77 +msgid "End Date" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:215 +#: includes/admin/discounts/class-discount-codes-table.php:315 +#: includes/admin/views/tmpl-tax-rates-table-bulk-actions.php:20 +#: includes/admin/views/tmpl-tax-rates-table-row.php:49 +#: src/Admin/PassHandler/Handler.php:101 +#: src/Licensing/Traits/Controls.php:104 +#: src/Pro/Admin/Extensions/Buttons.php:23 +msgid "Deactivate" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:222 +#: includes/admin/discounts/class-discount-codes-table.php:314 +#: includes/admin/views/tmpl-tax-rates-table-bulk-actions.php:19 +#: includes/admin/views/tmpl-tax-rates-table-row.php:51 +#: src/Licensing/Traits/Controls.php:111 +#: src/Pro/Admin/Extensions/Buttons.php:20 +msgid "Activate" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:239 +msgid "View Orders" +msgstr "" + +#: includes/admin/discounts/class-discount-codes-table.php:303 +#: includes/reports/data/discounts/class-top-five-discounts-list-table.php:179 +msgid "No discounts found." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:44 +#: includes/admin/downloads/contextual-help.php:49 +#: includes/admin/payments/contextual-help.php:56 +#: includes/admin/reporting/contextual-help.php:50 +#: includes/admin/settings/contextual-help.php:50 +msgid "For more information:" +msgstr "" + +#: includes/admin/discounts/contextual-help.php:45 +#: includes/admin/downloads/contextual-help.php:50 +#: includes/admin/payments/contextual-help.php:57 +#: includes/admin/reporting/contextual-help.php:51 +#: includes/admin/settings/contextual-help.php:51 +msgid "Visit the documentation on the Easy Digital Downloads website." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:47 +#: includes/admin/downloads/contextual-help.php:52 +#: includes/admin/payments/contextual-help.php:59 +#: includes/admin/reporting/contextual-help.php:53 +msgid "Need more from your Easy Digital Downloads store? Upgrade Now!" +msgstr "" + +#: includes/admin/discounts/contextual-help.php:57 +msgid "Discount codes allow you to offer buyers special discounts by having them enter predefined codes during checkout." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:58 +msgid "Discount codes that are set to \"inactive\" cannot be redeemed." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:59 +msgid "Discount codes can be setup to only be used only one time by each customer. If a customer attempts to use a code a second time, they will be given an error." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:60 +msgid "Discount codes that have already been used cannot be deleted for data integrity and reporting purposes." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:65 +msgid "Adding Discounts" +msgstr "" + +#: includes/admin/discounts/contextual-help.php:67 +msgid "You can create any number of discount codes easily from this page." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:68 +msgid "Discount codes have several options:" +msgstr "" + +#: includes/admin/discounts/contextual-help.php:70 +msgid "Name - this is the name given to the discount. Used primarily for administrative purposes." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:71 +msgid "Code - this is the unique code that customers will enter during checkout to redeem the code." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:72 +msgid "Type - this is the type of discount this code awards." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:73 +msgid "Amount - this is the discount amount provided by this code. For percentage based discounts, enter a number such as 70 for 70%. Do not enter a percent sign." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:74 +msgid "Requirements - this allows you to select the product(s) that are required to be purchased in order for a discount to be applied." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:75 +msgid "Condition - this lets you set whether all selected products must be in the cart, or just a minimum of one." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:76 +msgid "Apply discount only to selected Downloads? - If this box is checked, only the prices of the required products will be discounted. If left unchecked, the discount will apply to all products in the cart." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:77 +msgid "Start Date - this is the date that this code becomes available. If a customer attempts to redeem the code prior to this date, they will be given an error. This is optional." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:78 +msgid "Expiration Date - this is the end date for the discount. After this date, the code will no longer be able to be used. This is optional." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:79 +msgid "Minimum Amount - this is the minimum purchase amount required to use this code. If a customer has less than this amount in their cart, they will be given an error. This is optional." +msgstr "" + +#: includes/admin/discounts/contextual-help.php:80 +msgid "Max Uses - this is the maximum number of times this discount can be redeemed. Once this number is reached, no more customers will be allowed to use it." +msgstr "" + +#: includes/admin/discounts/discount-actions.php:32 +#: includes/admin/discounts/discount-actions.php:410 +msgid "You do not have permission to create discount codes" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:179 +#: includes/admin/discounts/discount-actions.php:377 +msgid "You do not have permission to edit discount codes" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:184 +#: includes/admin/discounts/discount-actions.php:343 +msgid "No discount ID supplied" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:193 +msgid "Invalid discount" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:333 +#: includes/admin/discounts/discount-actions.php:372 +#: includes/admin/discounts/discount-actions.php:405 +msgid "Trying to cheat or something?" +msgstr "" + +#: includes/admin/discounts/discount-actions.php:338 +msgid "You do not have permission to delete discount codes" +msgstr "" + +#: includes/admin/discounts/discount-codes.php:56 +#: includes/class-edd-html-elements.php:444 +#: includes/post-types.php:125 +msgid "Search Discounts" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:17 +#: includes/admin/discounts/edit-discount.php:28 +msgid "Something went wrong." +msgstr "" + +#: includes/admin/discounts/edit-discount.php:58 +#: includes/post-types.php:121 +msgid "Edit Discount" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:259 +#: includes/admin/payments/class-payments-table.php:409 +#: includes/admin/payments/orders.php:1015 +#: includes/admin/reporting/class-export-payments.php:78 +#: includes/admin/reporting/export/class-batch-export-downloads.php:48 +#: includes/admin/reporting/export/class-batch-export-payments.php:66 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:67 +#: includes/admin/tools.php:694 +#: includes/privacy-functions.php:789 +msgid "Status" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:264 +#: includes/class-edd-discount.php:681 +#: includes/misc-functions.php:1456 +msgid "Inactive" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:266 +msgid "The status of this discount code." +msgstr "" + +#: includes/admin/discounts/edit-discount.php:274 +msgid "Discount Notes" +msgstr "" + +#: includes/admin/discounts/edit-discount.php:294 +msgid "Update Discount Code" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:60 +msgid "%s Settings" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:62 +msgid "File Download Limit - Define how many times customers are allowed to download their purchased files. Leave at 0 for unlimited. Resending the purchase receipt will permit the customer one additional download if their limit has already been reached." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:64 +msgid "Accounting Options - If enabled, define an individual SKU or product number for this download." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:66 +msgid "Button Options - Disable the automatic output of the purchase button. If disabled, no button will be added to the download page unless the [purchase_link] shortcode is used." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:71 +msgid "%s Prices" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:73 +msgid "Enable variable pricing - By enabling variable pricing, multiple download options and prices can be configured." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:75 +msgid "Enable multi-option purchases - By enabling multi-option purchases customers can add multiple variable price items to their cart at once." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:80 +msgid "%s Files" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:82 +msgid "Product Type Options - Choose a default product type or a bundle. Bundled products automatically include access to other download's files when purchased." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:84 +msgid "File Downloads - Define download file names and their respective file URL. Multiple files can be assigned to a single price, or variable prices." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:90 +msgid "%s Instructions" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:91 +msgid "Special instructions for this %s. These will be added to the sales receipt, and may be used by some extensions or themes." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:100 +#: includes/admin/downloads/metabox.php:1150 +msgid "Purchase Shortcode" +msgstr "" + +#: includes/admin/downloads/contextual-help.php:102 +msgid "Purchase Shortcode - If the automatic output of the purchase button has been disabled via the Download Configuration box, a shortcode can be used to output the button or link." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:105 +msgid "The ID of a specific download to purchase." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:106 +msgid "Whether to show the price on the purchase button. 1 to show the price, 0 to disable it." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:107 +msgid "The text to be displayed on the button or link." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:108 +msgid "button | text - The style of the purchase link." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:110 +msgid "One or more custom CSS classes you want applied to the button." +msgstr "" + +#: includes/admin/downloads/contextual-help.php:113 +msgid "For more information, see using Shortcodes on the WordPress.org Codex or Easy Digital Downloads Documentation" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:34 +#: includes/admin/downloads/dashboard-columns.php:327 +#: includes/admin/downloads/metabox.php:343 +#: includes/admin/downloads/metabox.php:476 +#: includes/admin/reporting/export/class-batch-export-downloads.php:51 +#: includes/emails/tags.php:215 +#: includes/reports/data/downloads/class-top-selling-downloads-list-table.php:56 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:59 +#: templates/shortcode-receipt.php:211 +#: includes/blocks/build/downloads/index.js:1 +msgid "Price" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:35 +msgid "Net Sales" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:36 +#: includes/emails/email-summary/edd-email-summary-template.php:249 +msgid "Net Revenue" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:190 +msgid "You do not have permission to view this data." +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:244 +#: includes/class-edd-html-elements.php:520 +msgctxt "plural: Example: \"All Categories\"" +msgid "All %s" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:269 +#: includes/admin/downloads/dashboard-columns.php:271 +msgctxt "plural: Example: \"Search Download Categories\"" +msgid "Search %s" +msgstr "" + +#: includes/admin/downloads/dashboard-columns.php:325 +msgid "%s Configuration" +msgstr "" + +#: includes/admin/downloads/metabox.php:29 +msgid "%1$s Prices" +msgstr "" + +#: includes/admin/downloads/metabox.php:32 +msgid "%1$s Files" +msgstr "" + +#: includes/admin/downloads/metabox.php:35 +msgid "%1$s Settings" +msgstr "" + +#: includes/admin/downloads/metabox.php:38 +msgid "%1$s Instructions" +msgstr "" + +#: includes/admin/downloads/metabox.php:42 +msgid "%1$s Stats" +msgstr "" + +#: includes/admin/downloads/metabox.php:330 +msgid "Pricing Options:" +msgstr "" + +#: includes/admin/downloads/metabox.php:337 +msgid "Enable variable pricing" +msgstr "" + +#: includes/admin/downloads/metabox.php:376 +msgid "Enable multi-option purchase mode. Allows multiple price options to be added to your cart at once" +msgstr "" + +#: includes/admin/downloads/metabox.php:407 +#: src/Admin/Onboarding/Steps/Products.php:105 +msgid "Add New Price" +msgstr "" + +#: includes/admin/downloads/metabox.php:443 +msgid "Click and drag to re-order price options" +msgstr "" + +#: includes/admin/downloads/metabox.php:444 +msgid "Price ID: %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:450 +#: includes/scripts.php:537 +msgid "Show advanced settings" +msgstr "" + +#: includes/admin/downloads/metabox.php:453 +#: includes/admin/downloads/metabox.php:661 +#: includes/admin/downloads/metabox.php:723 +#: includes/admin/downloads/metabox.php:839 +#: includes/admin/views/tmpl-tax-rates-table-row.php:47 +#: includes/blocks/views/checkout/cart/cart-fees.php:15 +#: includes/blocks/views/checkout/cart/cart-item.php:55 +#: includes/scripts.php:532 +#: templates/checkout_cart.php:57 +#: templates/checkout_cart.php:75 +#: templates/shortcode-profile-editor.php:129 +msgid "Remove" +msgstr "" + +#: includes/admin/downloads/metabox.php:453 +msgid "Remove price option %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:463 +#: includes/admin/downloads/metabox.php:469 +msgid "Option Name" +msgstr "" + +#: includes/admin/downloads/metabox.php:506 +#: includes/gateways/stripe/includes/template-functions.php:457 +#: includes/blocks/build/downloads/index.js:1 +msgid "Default" +msgstr "" + +#: includes/admin/downloads/metabox.php:508 +msgid "Set ID %s as default price" +msgstr "" + +#: includes/admin/downloads/metabox.php:544 +msgid "Product Type Options:" +msgstr "" + +#: includes/admin/downloads/metabox.php:558 +msgid "Select a product type" +msgstr "" + +#: includes/admin/downloads/metabox.php:559 +msgid "Product Type: Sell this item as a single product, or use the Bundle type to sell a collection of products." +msgstr "" + +#: includes/admin/downloads/metabox.php:599 +msgid "Bundled %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:607 +#: includes/admin/downloads/metabox.php:678 +msgid "Click and drag to re-order bundled %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:611 +#: includes/admin/downloads/metabox.php:682 +msgid "Select %s:" +msgstr "" + +#: includes/admin/downloads/metabox.php:631 +#: includes/admin/downloads/metabox.php:698 +msgid "Price assignment:" +msgstr "" + +#: includes/admin/downloads/metabox.php:661 +msgid "Remove bundle option %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:672 +msgid "Bundled %s:" +msgstr "" + +#: includes/admin/downloads/metabox.php:723 +msgid "Remove bundle option 1" +msgstr "" + +#: includes/admin/downloads/metabox.php:734 +#: includes/admin/downloads/metabox.php:795 +msgid "Add New File" +msgstr "" + +#: includes/admin/downloads/metabox.php:833 +msgid "Click and drag to re-order files" +msgstr "" + +#: includes/admin/downloads/metabox.php:834 +msgid "%1$s file ID: %2$s" +msgstr "" + +#: includes/admin/downloads/metabox.php:839 +msgid "Remove file %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:846 +msgid "File Name" +msgstr "" + +#: includes/admin/downloads/metabox.php:854 +msgid "My Neat File" +msgstr "" + +#: includes/admin/downloads/metabox.php:861 +msgid "File URL" +msgstr "" + +#: includes/admin/downloads/metabox.php:867 +msgid "Enter, upload, choose from Media Library" +msgstr "" + +#: includes/admin/downloads/metabox.php:872 +#: includes/admin/downloads/metabox.php:874 +msgid "Select Files" +msgstr "" + +#: includes/admin/downloads/metabox.php:872 +msgid "Select" +msgstr "" + +#: includes/admin/downloads/metabox.php:882 +msgid "Price Assignment" +msgstr "" + +#: includes/admin/downloads/metabox.php:882 +msgid "Price Assignment: With variable pricing enabled, you can choose to allow certain price variations access to specific files, or allow all price variations to access a file." +msgstr "" + +#: includes/admin/downloads/metabox.php:929 +msgid "Insert into %s" +msgstr "" + +#: includes/admin/downloads/metabox.php:960 +#: includes/admin/reporting/reports.php:995 +#: includes/admin/reporting/reports.php:1158 +#: includes/admin/reporting/reports.php:1348 +#: includes/admin/settings/register-settings.php:772 +#: includes/orders/functions/types.php:93 +#: src/Admin/Settings/Tabs/Gateways.php:133 +#: src/Admin/Settings/Tabs/Gateways.php:136 +msgid "Refunds" +msgstr "" + +#: includes/admin/downloads/metabox.php:961 +msgid "Refundable: Allow or disallow refunds for this specific product. When allowed, the refund window will be used on all future purchases.
    Refund Window: Limit the number of days this product can be refunded after purchasing." +msgstr "" + +#: includes/admin/downloads/metabox.php:966 +msgid "Refund Status" +msgstr "" + +#. translators: Default refund status +#: includes/admin/downloads/metabox.php:977 +msgctxt "Download refund status" +msgid "Default (%1$s)" +msgstr "" + +#: includes/admin/downloads/metabox.php:992 +#: src/Admin/Settings/Tabs/Gateways.php:149 +msgid "Refund Window" +msgstr "" + +#: includes/admin/downloads/metabox.php:995 +msgctxt "refund window interval" +msgid "Days" +msgstr "" + +#: includes/admin/downloads/metabox.php:998 +#: includes/admin/downloads/metabox.php:1031 +msgid "Leave blank to use global setting. Enter 0 for unlimited" +msgstr "" + +#: includes/admin/downloads/metabox.php:1026 +#: includes/admin/reporting/export/class-batch-export-downloads.php:53 +#: includes/admin/tools.php:1000 +#: src/Admin/Settings/Tabs/Misc.php:140 +msgid "File Download Limit" +msgstr "" + +#: includes/admin/downloads/metabox.php:1026 +msgid "File Download Limit: Limit the number of times a customer who purchased this product can access their download links." +msgstr "" + +#: includes/admin/downloads/metabox.php:1071 +msgid "Taxability" +msgstr "" + +#: includes/admin/downloads/metabox.php:1071 +msgid "Taxability: When taxes are enabled, all products are taxable by default. Check this box to mark this product as non-taxable." +msgstr "" + +#: includes/admin/downloads/metabox.php:1082 +msgid "This product is non-taxable" +msgstr "" + +#: includes/admin/downloads/metabox.php:1107 +msgid "Item Quantities" +msgstr "" + +#: includes/admin/downloads/metabox.php:1107 +msgid "Item Quantities: if disabled, customers will not be provided an option to change the number they wish to purchase." +msgstr "" + +#: includes/admin/downloads/metabox.php:1120 +msgid "Disable quantity input for this product" +msgstr "" + +#: includes/admin/downloads/metabox.php:1142 +#: includes/admin/thickbox.php:77 +#: includes/blocks/includes/downloads/downloads.php:135 +#: includes/checkout/template.php:992 +#: includes/gateways/stripe/includes/payment-methods/buy-now/template.php:215 +#: includes/shortcodes.php:49 +#: includes/template-functions.php:88 +#: src/Admin/Settings/Tabs/Misc.php:86 +msgid "Purchase" +msgstr "" + +#: includes/admin/downloads/metabox.php:1150 +msgid "Purchase Shortcode: Use this shortcode to output a purchase link for this product in the location of your choosing." +msgstr "" + +#: includes/admin/downloads/metabox.php:1174 +msgid "Accounting Options" +msgstr "" + +#: includes/admin/downloads/metabox.php:1174 +msgid "SKU: If an SKU is entered for this product, it will be shown on the purchase receipt and exported purchase histories." +msgstr "" + +#: includes/admin/downloads/metabox.php:1177 +msgid "Enter an SKU for this product." +msgstr "" + +#: includes/admin/downloads/metabox.php:1206 +msgid "Purchase button behavior: Add to Cart buttons follow a traditional eCommerce flow. A Buy Now button bypasses most of the process, taking the customer directly from button click to payment, greatly speeding up the process of buying the product." +msgstr "" + +#: includes/admin/downloads/metabox.php:1207 +msgid "Purchase button behavior: Add to Cart buttons follow a traditional eCommerce flow. Buy Now buttons are only available for stores that have a single supported gateway active and that do not use taxes." +msgstr "" + +#: includes/admin/downloads/metabox.php:1211 +msgid "Button Options" +msgstr "" + +#: includes/admin/downloads/metabox.php:1211 +msgid "Hide purchase button: By default, the purchase buttons will be displayed at the bottom of the download, when disabled you will need to use the Purchase link shortcode below to output the ability to buy the product where you prefer." +msgstr "" + +#: includes/admin/downloads/metabox.php:1223 +msgid "Hide purchase button" +msgstr "" + +#: includes/admin/downloads/metabox.php:1229 +msgid "Purchase button behavior" +msgstr "" + +#: includes/admin/downloads/metabox.php:1237 +#: includes/admin/thickbox.php:126 +#: src/Admin/Settings/Tabs/Misc.php:100 +msgid "Add to Cart" +msgstr "" + +#: includes/admin/downloads/metabox.php:1238 +#: includes/blocks/includes/downloads/downloads.php:161 +#: includes/gateways/stripe/includes/payment-methods/buy-now/template.php:40 +#: includes/shortcodes.php:47 +#: includes/template-functions.php:88 +#: src/Admin/Settings/Tabs/Misc.php:182 +#: includes/blocks/build/buy-button/index.js:1 +msgid "Buy Now" +msgstr "" + +#: includes/admin/downloads/metabox.php:1284 +msgid "Download Instructions" +msgstr "" + +#: includes/admin/downloads/metabox.php:1287 +msgid "Special instructions for this %s. These will be added to the purchase receipt, and may be used by some extensions or themes." +msgstr "" + +#: includes/admin/downloads/metabox.php:1325 +msgid "Net Sales:" +msgstr "" + +#: includes/admin/downloads/metabox.php:1330 +msgid "Net Revenue:" +msgstr "" + +#: includes/admin/downloads/metabox.php:1347 +msgid "View File Download Log" +msgstr "" + +#: includes/admin/emails/email-summary/class-edd-email-summary-admin.php:64 +msgid "The test Email Summary was sent successfully!" +msgstr "" + +#: includes/admin/emails/email-summary/class-edd-email-summary-admin.php:72 +msgid "There was an unknown problem while sending test Email Summary!" +msgstr "" + +#: includes/admin/import/class-batch-import-downloads.php:65 +#: includes/admin/import/class-batch-import-payments.php:77 +#: includes/admin/import/class-batch-import.php:199 +msgid "You do not have permission to import data." +msgstr "" + +#: includes/admin/import/class-batch-import-payments.php:295 +#: includes/admin/reporting/export/class-batch-export-payments.php:53 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:54 +msgid "Products (Raw)" +msgstr "" + +#: includes/admin/import/class-batch-import-payments.php:642 +msgid "payments" +msgstr "" + +#: includes/admin/import/import-functions.php:29 +#: includes/admin/import/import-functions.php:110 +msgid "Missing import parameters. Import class must be specified." +msgstr "" + +#: includes/admin/import/import-functions.php:41 +#: includes/admin/import/import-functions.php:138 +msgid "Invalid importer class supplied" +msgstr "" + +#: includes/admin/import/import-functions.php:50 +#: includes/admin/import/import-functions.php:148 +msgid "You do not have permission to import data" +msgstr "" + +#: includes/admin/import/import-functions.php:54 +msgid "Missing import file. Please provide an import file." +msgstr "" + +#: includes/admin/import/import-functions.php:58 +#: includes/admin/import/import-functions.php:131 +msgid "The file you uploaded does not appear to be a CSV file." +msgstr "" + +#: includes/admin/import/import-functions.php:62 +#: includes/admin/import/import-functions.php:114 +msgid "Something went wrong during the upload process, please try again." +msgstr "" + +#: includes/admin/import/import-functions.php:174 +msgid "No data found for import parameters" +msgstr "" + +#: includes/admin/import/import-functions.php:182 +msgid "Import complete! View imported %s." +msgstr "" + +#: includes/admin/notes/note-functions.php:44 +msgid "No notes." +msgstr "" + +#. translators: user ID +#: includes/admin/notes/note-functions.php:81 +msgid "User ID #%s" +msgstr "" + +#: includes/admin/notes/note-functions.php:103 +msgctxt "Delete note" +msgid "×" +msgstr "" + +#: includes/admin/notes/note-functions.php:138 +msgid "Note" +msgstr "" + +#: includes/admin/notes/note-functions.php:148 +msgid "Add Note" +msgstr "" + +#: includes/admin/payments/actions.php:123 +msgid "New Customers require a name and email address" +msgstr "" + +#: includes/admin/payments/actions.php:142 +msgid "Error creating new customer" +msgstr "" + +#: includes/admin/payments/actions.php:145 +msgid "A customer with the email address %s already exists. Please go back and assign this payment to them." +msgstr "" + +#: includes/admin/payments/actions.php:210 +msgid "Error updating order." +msgstr "" + +#: includes/admin/payments/actions.php:244 +msgid "You do not have permission to edit this order." +msgstr "" + +#: includes/admin/payments/actions.php:382 +#: includes/admin/payments/actions.php:479 +msgid "You must be logged in to perform this action." +msgstr "" + +#: includes/admin/payments/actions.php:392 +#: includes/admin/payments/actions.php:484 +msgid "Your account does not have permission to perform this action." +msgstr "" + +#: includes/admin/payments/actions.php:403 +msgid "Invalid order ID" +msgstr "" + +#: includes/admin/payments/actions.php:413 +msgid "Invalid order" +msgstr "" + +#: includes/admin/payments/actions.php:422 +msgid "Order is already refunded" +msgstr "" + +#: includes/admin/payments/actions.php:431 +msgid "Cannot refund an order that is already refunded." +msgstr "" + +#: includes/admin/payments/actions.php:442 +msgid "View Refund" +msgstr "" + +#: includes/admin/payments/actions.php:488 +msgid "Missing form data or order ID." +msgstr "" + +#: includes/admin/payments/actions.php:497 +msgid "Nonce validation failed when submitting refund." +msgstr "" + +#: includes/admin/payments/actions.php:544 +msgid "Refund successfully processed." +msgstr "" + +#: includes/admin/payments/actions.php:555 +msgid "Unable to process refund." +msgstr "" + +#: includes/admin/payments/add-order.php:58 +msgid "New Order" +msgstr "" + +#: includes/admin/payments/add-order.php:63 +#: includes/admin/payments/view-order-details.php:60 +msgid "Please select an existing customer or create a new customer." +msgstr "" + +#: includes/admin/payments/add-order.php:67 +msgid "Please add an item to this order." +msgstr "" + +#: includes/admin/payments/add-order.php:135 +msgid "Add Download" +msgstr "" + +#: includes/admin/payments/add-order.php:143 +#: includes/admin/views/tmpl-order-form-add-order-discount.php:65 +msgid "Add Discount" +msgstr "" + +#: includes/admin/payments/add-order.php:151 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:167 +msgid "Add Adjustment" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:149 +msgid "All modes" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:160 +msgid "All gateways" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:193 +#: includes/admin/reporting/class-base-logs-list-table.php:477 +#: includes/admin/reporting/views/export-api-requests.php:17 +#: includes/admin/reporting/views/export-download-history.php:28 +#: includes/admin/reporting/views/export-orders.php:17 +#: includes/admin/reporting/views/export-sales-earnings.php:17 +#: includes/admin/reporting/views/export-sales.php:17 +#: includes/admin/reporting/views/export-taxed-customers.php:16 +#: includes/admin/reporting/views/export-taxed-orders.php:17 +#: includes/reports/reports-functions.php:1341 +msgctxt "date filter" +msgid "From" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:200 +#: includes/admin/reporting/class-base-logs-list-table.php:484 +#: includes/admin/reporting/views/export-api-requests.php:28 +#: includes/admin/reporting/views/export-download-history.php:39 +#: includes/admin/reporting/views/export-orders.php:28 +#: includes/admin/reporting/views/export-sales-earnings.php:28 +#: includes/admin/reporting/views/export-sales.php:28 +#: includes/admin/reporting/views/export-taxed-customers.php:27 +#: includes/admin/reporting/views/export-taxed-orders.php:28 +#: includes/reports/reports-functions.php:1351 +msgctxt "date filter" +msgid "To" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:222 +msgid "More" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:226 +msgid "Total is" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:229 +msgid "equal to" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:230 +msgid "greater than" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:231 +msgid "less than" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:248 +#: includes/admin/payments/class-refund-items-table.php:77 +#: includes/admin/reporting/class-export-download-history.php:62 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:47 +#: includes/blocks/includes/orders/orders.php:549 +#: includes/emails/email-summary/edd-email-summary-template.php:340 +msgid "Product" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:261 +msgid "Country & Region" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:285 +msgid "Extras" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:296 +#: includes/admin/reporting/class-base-logs-list-table.php:502 +#: includes/admin/tools/logs.php:235 +#: includes/reports/reports-functions.php:1755 +msgid "Filter" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:300 +#: includes/admin/reporting/class-base-logs-list-table.php:506 +msgid "Clear" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:318 +#: includes/admin/payments/contextual-help.php:96 +#: includes/admin/reporting/class-base-logs-list-table.php:521 +msgid "Search" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:351 +msgid "Search orders..." +msgstr "" + +#: includes/admin/payments/class-payments-table.php:367 +msgid "Easy Digital Downloads needs to upgrade the database. Orders will be available when that has completed." +msgstr "" + +#. Translators: %s is for the status of 'Trash', telling the user no items were found in the trash. +#: includes/admin/payments/class-payments-table.php:380 +msgid "No orders found in %s." +msgstr "" + +#. Translators: %s is for the currently viewed order status filter +#: includes/admin/payments/class-payments-table.php:384 +msgid "No %s orders found." +msgstr "" + +#: includes/admin/payments/class-payments-table.php:390 +msgid "No orders found." +msgstr "" + +#: includes/admin/payments/class-payments-table.php:551 +#: includes/emails/tags.php:779 +msgid "View Receipt" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:560 +#: includes/admin/payments/orders.php:378 +msgid "Resend Receipt" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:569 +msgid "Trash" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:575 +#: includes/admin/payments/class-payments-table.php:698 +msgid "Restore" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:581 +msgid "Delete Permanently" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:682 +msgid "Mark Completed" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:683 +msgid "Mark Pending" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:684 +msgid "Mark Processing" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:685 +msgid "Mark Revoked" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:686 +msgid "Mark Failed" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:687 +msgid "Mark Abandoned" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:688 +msgid "Mark Preapproved" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:689 +msgid "Mark Cancelled" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:690 +msgid "Resend Receipts" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:702 +msgid "Delete permanently" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:705 +#: includes/admin/payments/orders.php:1051 +#: includes/admin/payments/refunds.php:301 +msgid "Move to Trash" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:1098 +#: includes/admin/payments/class-payments-table.php:1101 +msgid "Confirmation Required" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:1099 +msgid "You are about to permanently delete orders from your store. Once deleted, these orders are not recoverable. Are you sure you want to continue?" +msgstr "" + +#: includes/admin/payments/class-payments-table.php:1102 +msgid "You are about to permanently delete this order from your store. Once deleted, this order is not recoverable. Are you sure you want to continue?" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:78 +#: includes/admin/payments/orders.php:808 +#: includes/admin/payments/refunds.php:223 +#: includes/admin/views/tmpl-order-form-add-order-item.php:175 +#: includes/admin/views/tmpl-order-item.php:67 +msgid "Unit Price" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:79 +#: includes/admin/payments/orders.php:810 +#: includes/admin/payments/refunds.php:224 +#: includes/admin/reporting/export/class-batch-export-sales.php:53 +#: includes/admin/views/tmpl-order-form-add-order-item.php:110 +#: includes/admin/views/tmpl-order-item.php:73 +#: includes/emails/tags.php:320 +#: includes/emails/tags.php:455 +#: templates/shortcode-receipt.php:209 +msgid "Quantity" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:80 +#: includes/admin/tools.php:704 +#: includes/admin/views/tmpl-order-subtotal.php:16 +#: includes/blocks/views/checkout/cart/cart-subtotal.php:10 +#: includes/blocks/views/orders/totals.php:43 +#: includes/emails/tags.php:203 +#: templates/checkout_cart.php:102 +#: templates/shortcode-receipt.php:76 +msgid "Subtotal" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:86 +#: includes/admin/reporting/class-export-payments.php:71 +#: includes/admin/reporting/export/class-batch-export-payments.php:57 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:57 +#: includes/admin/tools.php:713 +#: includes/admin/views/tmpl-order-form-add-order-item.php:196 +#: includes/admin/views/tmpl-order-tax.php:26 +#: includes/blocks/views/checkout/cart/cart-taxes.php:9 +#: includes/blocks/views/orders/totals.php:56 +#: includes/emails/tags.php:209 +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:79 +#: templates/checkout_cart.php:120 +#: templates/shortcode-receipt.php:134 +msgid "Tax" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:157 +#: includes/admin/views/tmpl-order-adjustment.php:45 +msgid "Order Fee" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:159 +#: includes/admin/views/tmpl-order-adjustment.php:43 +#: includes/ajax-functions.php:1079 +msgid "Order Credit" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:278 +msgid "Amount to refund, excluding tax" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:280 +msgid "Amount of tax to refund" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:315 +msgctxt "Maximum input amount" +msgid "Max:" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:343 +msgid "Quantity to refund" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:481 +msgid "No items found." +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:656 +#: includes/admin/payments/view-order-details.php:131 +msgid "Submit Refund" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:688 +msgid "Refund Subtotal:" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:711 +msgid "Refund Tax Total:" +msgstr "" + +#: includes/admin/payments/class-refund-items-table.php:727 +msgid "Refund Total:" +msgstr "" + +#: includes/admin/payments/contextual-help.php:67 +#: includes/admin/reporting/reports.php:209 +msgid "Overview" +msgstr "" + +#: includes/admin/payments/contextual-help.php:69 +msgid "This screen provides access to all of the orders and refunds in your store." +msgstr "" + +#: includes/admin/payments/contextual-help.php:70 +msgid "Orders can be searched by email address, user name, or filtered by status, mode, date range, gateway, and more!" +msgstr "" + +#: includes/admin/payments/contextual-help.php:71 +msgid "To maintain accurate reporting and accounting, we strongly advise against deleting any completed order data." +msgstr "" + +#: includes/admin/payments/contextual-help.php:76 +msgid "— Orders" +msgstr "" + +#: includes/admin/payments/contextual-help.php:78 +msgid "Orders are placed by customers when they buy things from your store." +msgstr "" + +#: includes/admin/payments/contextual-help.php:79 +msgid "Every order contains a snapshot of your store at the time the order was placed, and is made up of many different pieces of information." +msgstr "" + +#: includes/admin/payments/contextual-help.php:80 +msgid "Things like products, discounts, taxes, fees, and customer email address, are all examples of information that is saved with each order." +msgstr "" + +#: includes/admin/payments/contextual-help.php:81 +msgid "Both full and partial refunds are supported." +msgstr "" + +#: includes/admin/payments/contextual-help.php:86 +msgid "— Refunds" +msgstr "" + +#: includes/admin/payments/contextual-help.php:88 +msgid "Refunds are created when a customer would like money back from a completed order." +msgstr "" + +#: includes/admin/payments/contextual-help.php:89 +msgid "Every refund refers back to the original order, and only contains the items and adjustments that were refunded." +msgstr "" + +#: includes/admin/payments/contextual-help.php:90 +msgid "Refunds could be entire orders, or single products." +msgstr "" + +#: includes/admin/payments/contextual-help.php:91 +msgid "Once an item is refunded, it cannot be undone; it can only be repurchased." +msgstr "" + +#: includes/admin/payments/contextual-help.php:98 +msgid "The order history can be searched in several different ways." +msgstr "" + +#: includes/admin/payments/contextual-help.php:99 +msgid "You can enter:" +msgstr "" + +#: includes/admin/payments/contextual-help.php:101 +msgid "The specific order ID" +msgstr "" + +#: includes/admin/payments/contextual-help.php:102 +msgid "The 32-character order key" +msgstr "" + +#: includes/admin/payments/contextual-help.php:103 +msgid "The customer's email address" +msgstr "" + +#: includes/admin/payments/contextual-help.php:105 +msgid "The customer's name or ID prefixed by %s" +msgstr "" + +#: includes/admin/payments/contextual-help.php:109 +msgid "A user's ID prefixed by %s" +msgstr "" + +#: includes/admin/payments/contextual-help.php:113 +msgid "The %s ID prefixed by %s" +msgstr "" + +#: includes/admin/payments/contextual-help.php:118 +msgid "The Discount Code prefixed by %s" +msgstr "" + +#: includes/admin/payments/contextual-help.php:122 +msgid "A transaction ID prefixed by %s" +msgstr "" + +#: includes/admin/payments/contextual-help.php:130 +#: templates/history-purchases.php:54 +msgid "Details" +msgstr "" + +#: includes/admin/payments/contextual-help.php:132 +msgid "Each order can be further inspected by clicking the corresponding View Order Details link. This will provide more information including:" +msgstr "" + +#: includes/admin/payments/contextual-help.php:135 +msgid "The file associated with the purchase." +msgstr "" + +#: includes/admin/payments/contextual-help.php:136 +msgid "The exact date and time the order was completed." +msgstr "" + +#: includes/admin/payments/contextual-help.php:137 +msgid "If a coupon or discount was used during the checkout process." +msgstr "" + +#: includes/admin/payments/contextual-help.php:138 +msgid "The buyer's name." +msgstr "" + +#: includes/admin/payments/contextual-help.php:139 +#: includes/emails/tags.php:186 +msgid "The buyer's email address." +msgstr "" + +#: includes/admin/payments/contextual-help.php:140 +msgid "Any customer-specific notes related to the order." +msgstr "" + +#: includes/admin/payments/contextual-help.php:141 +msgid "The name of the order gateway used to complete the order." +msgstr "" + +#: includes/admin/payments/contextual-help.php:142 +msgid "A unique key used to identify the order." +msgstr "" + +#: includes/admin/payments/orders.php:28 +msgid "Create Order" +msgstr "" + +#: includes/admin/payments/orders.php:29 +msgid "Save Order" +msgstr "" + +#: includes/admin/payments/orders.php:43 +msgid "Send Purchase Receipt" +msgstr "" + +#: includes/admin/payments/orders.php:45 +msgid "Checking this box will email the purchase receipt to the selected customer." +msgstr "" + +#: includes/admin/payments/orders.php:191 +msgid "Assign" +msgstr "" + +#: includes/admin/payments/orders.php:192 +msgid "Switch Customer" +msgstr "" + +#: includes/admin/payments/orders.php:202 +msgid "Assign to an existing customer" +msgstr "" + +#: includes/admin/payments/orders.php:211 +#: includes/admin/payments/orders.php:212 +msgid "Search for a customer" +msgstr "" + +#: includes/admin/payments/orders.php:242 +msgid "View customer record" +msgstr "" + +#: includes/admin/payments/orders.php:248 +msgid "create a new customer" +msgstr "" + +#: includes/admin/payments/orders.php:255 +msgid "← Use an existing customer" +msgstr "" + +#: includes/admin/payments/orders.php:260 +#: includes/admin/reporting/class-export-customers.php:61 +#: includes/admin/reporting/class-export-payments.php:59 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:45 +#: includes/admin/tools.php:575 +#: includes/emails/tags.php:167 +#: templates/shortcode-profile-editor.php:44 +msgid "First Name" +msgstr "" + +#: includes/admin/payments/orders.php:270 +#: includes/admin/reporting/class-export-customers.php:62 +#: includes/admin/reporting/class-export-payments.php:60 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:46 +#: includes/admin/tools.php:585 +#: templates/shortcode-profile-editor.php:49 +msgid "Last Name" +msgstr "" + +#. translators: email type +#: includes/admin/payments/orders.php:324 +msgid "Send a new copy of the purchase receipt to the %s email address. If download URLs were included in the original receipt, new ones will be included." +msgstr "" + +#: includes/admin/payments/orders.php:325 +msgid "selected" +msgstr "" + +#: includes/admin/payments/orders.php:325 +msgid "customer" +msgstr "" + +#: includes/admin/payments/orders.php:329 +msgid "Send email receipt to" +msgstr "" + +#: includes/admin/payments/orders.php:353 +msgid "Customer Primary" +msgstr "" + +#: includes/admin/payments/orders.php:355 +msgid "Order Email" +msgstr "" + +#: includes/admin/payments/orders.php:414 +#: includes/blocks/views/checkout/purchase-form/address.php:3 +#: includes/emails/tags.php:191 +#: includes/privacy-functions.php:779 +msgid "Billing Address" +msgstr "" + +#: includes/admin/payments/orders.php:417 +msgid "Existing Address:" +msgstr "" + +#: includes/admin/payments/orders.php:422 +msgid "Line 1:" +msgstr "" + +#: includes/admin/payments/orders.php:429 +msgid "Line 2:" +msgstr "" + +#: includes/admin/payments/orders.php:436 +msgctxt "Address City" +msgid "City:" +msgstr "" + +#: includes/admin/payments/orders.php:443 +msgctxt "Zip / Postal code of address" +msgid "Zip / Postal Code:" +msgstr "" + +#: includes/admin/payments/orders.php:450 +msgctxt "Address country" +msgid "Country:" +msgstr "" + +#: includes/admin/payments/orders.php:462 +msgid "Search Countries" +msgstr "" + +#: includes/admin/payments/orders.php:472 +msgctxt "Region of address" +msgid "Region:" +msgstr "" + +#: includes/admin/payments/orders.php:484 +msgid "Search Regions" +msgstr "" + +#: includes/admin/payments/orders.php:569 +msgid "File Download Log for Order" +msgstr "" + +#: includes/admin/payments/orders.php:570 +msgid "Customer Download Log" +msgstr "" + +#: includes/admin/payments/orders.php:571 +#: includes/privacy-functions.php:807 +msgid "Customer Orders" +msgstr "" + +#: includes/admin/payments/orders.php:775 +#: includes/admin/views/tmpl-order-copy-download-link.php:35 +#: includes/gateways/stripe/includes/utils/modal.php:69 +#: src/Admin/Pointers.php:41 +msgid "Close" +msgstr "" + +#: includes/admin/payments/orders.php:840 +msgid "Order Details" +msgstr "" + +#: includes/admin/payments/orders.php:888 +msgid "Order Extras" +msgstr "" + +#: includes/admin/payments/orders.php:926 +msgid "Key" +msgstr "" + +#: includes/admin/payments/orders.php:936 +#: includes/admin/payments/orders.php:944 +msgid "IP" +msgstr "" + +#: includes/admin/payments/orders.php:951 +#: includes/admin/payments/orders.php:959 +#: includes/admin/reporting/class-export-payments.php:74 +#: includes/admin/reporting/export/class-batch-export-payments.php:60 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:60 +#: includes/admin/tools.php:732 +msgid "Transaction ID" +msgstr "" + +#: includes/admin/payments/orders.php:973 +msgid "Unlimited Downloads" +msgstr "" + +#: includes/admin/payments/orders.php:974 +msgid "Checking this box will override all other file download limits for this purchase, granting the customer unlimited downloads of all files included on the purchase." +msgstr "" + +#: includes/admin/payments/orders.php:1006 +msgid "Order Attributes" +msgstr "" + +#: includes/admin/payments/orders.php:1018 +msgid "Pending: order is still processing or was abandoned by customer. Successful orders will be marked as Complete automatically once processing is finalized." +msgstr "" + +#: includes/admin/payments/orders.php:1019 +msgid "Complete: all processing is completed for this purchase." +msgstr "" + +#: includes/admin/payments/orders.php:1020 +msgid "Revoked: access to purchased items is disabled, perhaps due to policy violation or fraud." +msgstr "" + +#: includes/admin/payments/orders.php:1021 +msgid "Refunded: the purchase amount is returned to the customer and access to items is disabled." +msgstr "" + +#: includes/admin/payments/orders.php:1022 +msgid "Abandoned: the purchase attempt was not completed by the customer." +msgstr "" + +#: includes/admin/payments/orders.php:1023 +msgid "Failed: customer clicked Cancel before completing the purchase." +msgstr "" + +#: includes/admin/payments/orders.php:1061 +msgid "Recover" +msgstr "" + +#: includes/admin/payments/orders.php:1062 +msgid "Pending and abandoned payments can be resumed by the customer, using this custom URL. Payments can be resumed only when they do not have a transaction ID from the gateway." +msgstr "" + +#: includes/admin/payments/orders.php:1084 +msgid "Time" +msgstr "" + +#: includes/admin/payments/orders.php:1089 +msgid "Hour" +msgstr "" + +#: includes/admin/payments/orders.php:1095 +msgid "Minute" +msgstr "" + +#: includes/admin/payments/payments-history.php:220 +msgid "Edit Order" +msgstr "" + +#: includes/admin/payments/payments-history.php:225 +msgid "Add New Order" +msgstr "" + +#: includes/admin/payments/refunds.php:41 +msgid "View Original Order" +msgstr "" + +#: includes/admin/payments/refunds.php:46 +msgid "A refund is a read-only record to help balance your store's books." +msgstr "" + +#: includes/admin/payments/refunds.php:47 +msgid "You are viewing a refund record." +msgstr "" + +#: includes/admin/payments/refunds.php:246 +msgid "Refund Notes" +msgstr "" + +#: includes/admin/payments/refunds.php:290 +msgid "Refund Attributes" +msgstr "" + +#: includes/admin/payments/refunds.php:308 +msgid "Original Order" +msgstr "" + +#: includes/admin/payments/refunds.php:342 +msgid "Related Refunds" +msgstr "" + +#: includes/admin/payments/view-order-details.php:23 +msgid "Order ID not supplied. Please try again" +msgstr "" + +#: includes/admin/payments/view-order-details.php:31 +msgid "The specified ID does not belong to an order. Please try again" +msgstr "" + +#: includes/admin/payments/view-order-details.php:42 +msgid "The specified ID is for a refund, not an order. Please access the refund directly." +msgstr "" + +#. translators: the order +#: includes/admin/payments/view-order-details.php:55 +#: includes/blocks/views/orders/orders.php:19 +msgid "Order: %s" +msgstr "" + +#: includes/admin/payments/view-order-details.php:135 +#: includes/admin/views/tmpl-order-item.php:54 +msgid "Copy %s Links" +msgstr "" + +#: includes/admin/payments/view-refund.php:23 +msgid "Refund ID not supplied. Please try again." +msgstr "" + +#: includes/admin/payments/view-refund.php:32 +msgid "The specified ID does not belong to an refund. Please try again." +msgstr "" + +#. translators: %s Refund number, linked to Refund record. +#: includes/admin/payments/view-refund.php:44 +#: includes/admin/views/tmpl-order-refund.php:25 +msgid "Refund: %s" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:78 +msgid "Hey, I noticed you've made quite a few sales with Easy Digital Downloads! Are you enjoying Easy Digital Downloads?" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:80 +#: includes/gateways/stripe/includes/admin/admin-actions.php:146 +msgid "Yes" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:81 +msgid "Not Really" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:85 +msgid "We're sorry to hear you aren't enjoying Easy Digital Downloads. We would love a chance to improve. Could you take a minute and let us know what we can do better?" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:87 +msgid "Give Feedback" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:88 +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:96 +msgid "No thanks" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:92 +msgid "That's awesome! Could you please do me a BIG favor and give it a 5-star rating on WordPress to help us spread the word and boost our motivation?" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:93 +msgid "~ Chris Klosowski
    President of Easy Digital Downloads" +msgstr "" + +#: includes/admin/promos/notices/class-five-star-review-dashboard.php:95 +msgid "Ok, you deserve it!" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:48 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:109 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:107 +#: includes/admin/reporting/export/class-batch-export-api-requests.php:39 +msgid "Log ID" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:49 +msgid "Request Details" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:50 +#: includes/admin/reporting/export/class-batch-export-api-requests.php:44 +msgid "API Version" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:51 +msgid "Request IP" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:52 +#: includes/admin/reporting/export/class-batch-export-api-requests.php:45 +msgid "Request Speed" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:97 +msgid "View Request" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:103 +msgid "API Request:" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:109 +msgid "API User:" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:111 +msgid "API Key:" +msgstr "" + +#: includes/admin/reporting/class-api-requests-logs-list-table.php:113 +msgid "Request Date:" +msgstr "" + +#: includes/admin/reporting/class-base-logs-list-table.php:497 +msgid "Customer ID: %d" +msgstr "" + +#: includes/admin/reporting/class-base-logs-list-table.php:555 +msgid "Search logs..." +msgstr "" + +#: includes/admin/reporting/class-categories-reports-table.php:76 +msgid "Category" +msgstr "" + +#: includes/admin/reporting/class-categories-reports-table.php:77 +#: includes/admin/reporting/class-gateways-reports-table.php:77 +#: includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php:160 +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:80 +msgid "Total Sales" +msgstr "" + +#: includes/admin/reporting/class-categories-reports-table.php:79 +msgid "Monthly Sales Avg" +msgstr "" + +#: includes/admin/reporting/class-categories-reports-table.php:80 +msgid "Monthly Earnings Avg" +msgstr "" + +#: includes/admin/reporting/class-categories-reports-table.php:206 +msgid "Less than 1" +msgstr "" + +#: includes/admin/reporting/class-categories-reports-table.php:308 +msgid "No sales for dates provided." +msgstr "" + +#: includes/admin/reporting/class-categories-reports-table.php:349 +msgid "No earnings for dates provided." +msgstr "" + +#: includes/admin/reporting/class-download-reports-table.php:88 +msgid "View Detailed Report" +msgstr "" + +#: includes/admin/reporting/class-download-reports-table.php:103 +#: includes/admin/reporting/export/class-batch-export-downloads.php:57 +#: includes/admin/reporting/export/class-batch-export-sales-and-earnings.php:60 +#: includes/admin/reporting/graphing.php:300 +#: includes/admin/reporting/graphing.php:599 +#: includes/admin/reporting/reports.php:232 +#: includes/admin/reporting/reports.php:365 +#: includes/admin/reporting/reports.php:664 +#: includes/admin/reporting/reports.php:915 +#: includes/admin/reporting/reports.php:1298 +#: includes/admin/reporting/reports.php:1458 +#: includes/admin/reporting/reports.php:1638 +#: includes/reports/data/downloads/class-top-selling-downloads-list-table.php:57 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:60 +#: includes/blocks/build/downloads/index.js:1 +msgid "Sales" +msgstr "" + +#: includes/admin/reporting/class-download-reports-table.php:105 +msgid "Monthly Average Sales" +msgstr "" + +#: includes/admin/reporting/class-download-reports-table.php:106 +msgid "Monthly Average Earnings" +msgstr "" + +#: includes/admin/reporting/class-download-reports-table.php:107 +msgid "Detailed Report" +msgstr "" + +#: includes/admin/reporting/class-export-customers.php:64 +msgid "Date Purchased" +msgstr "" + +#: includes/admin/reporting/class-export-customers.php:77 +msgid "Total Purchases" +msgstr "" + +#: includes/admin/reporting/class-export-customers.php:78 +msgid "Total Purchased" +msgstr "" + +#: includes/admin/reporting/class-export-download-history.php:60 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:44 +msgid "Downloaded by" +msgstr "" + +#: includes/admin/reporting/class-export-download-history.php:61 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:114 +#: includes/admin/reporting/export/class-batch-export-api-requests.php:41 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:45 +#: includes/admin/reporting/export/class-batch-export-payments.php:64 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:65 +#: includes/admin/tools.php:615 +#: includes/emails/tags.php:263 +#: includes/privacy-functions.php:783 +#: includes/privacy-functions.php:876 +#: includes/privacy-functions.php:954 +msgid "IP Address" +msgstr "" + +#: includes/admin/reporting/class-export-download-history.php:63 +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:113 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:48 +msgid "File" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:56 +#: includes/admin/reporting/class-export.php:74 +#: includes/admin/reporting/export/class-batch-export-customers.php:51 +#: includes/admin/reporting/export/class-batch-export-downloads.php:41 +#: includes/admin/reporting/export/class-batch-export-taxed-customers.php:41 +#: templates/history-purchases.php:51 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "ID" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:57 +#: includes/admin/tools.php:655 +msgid "Payment Number" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:62 +#: includes/admin/reporting/export/class-batch-export-payments.php:47 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:48 +msgid "Address (Line 2)" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:64 +#: includes/admin/reporting/export/class-batch-export-payments.php:49 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:50 +#: includes/gateways/stripe/includes/template-functions.php:585 +msgid "State" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:66 +#: includes/admin/reporting/export/class-batch-export-payments.php:51 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:52 +#: includes/admin/tools.php:790 +#: includes/blocks/views/checkout/purchase-form/address.php:105 +#: includes/checkout/template.php:403 +#: includes/gateways/stripe/includes/template-functions.php:839 +#: templates/shortcode-profile-editor.php:163 +msgid "Zip / Postal Code" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:67 +#: includes/blocks/views/orders/receipt-items.php:20 +#: includes/reports/reports-functions.php:320 +#: src/Admin/Onboarding/Wizard.php:252 +#: templates/shortcode-receipt.php:200 +msgid "Products" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:68 +#: includes/admin/reporting/export/class-batch-export-payments.php:54 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:55 +msgid "SKUs" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:69 +#: includes/admin/reporting/export/class-batch-export-payments.php:55 +#: includes/admin/reporting/export/class-batch-export-sales.php:55 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:64 +#: includes/admin/settings/register-settings.php:765 +#: src/Admin/Settings/Tabs/General.php:171 +#: src/Admin/Settings/Tabs/General.php:179 +msgid "Currency" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:72 +#: includes/admin/reporting/export/class-batch-export-payments.php:58 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:58 +msgid "Discount Code" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:73 +#: includes/admin/reporting/export/class-batch-export-payments.php:59 +#: includes/admin/tools.php:645 +#: includes/blocks/views/orders/totals.php:32 +#: includes/emails/tags.php:233 +#: templates/shortcode-receipt.php:63 +msgid "Payment Method" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:75 +#: includes/admin/reporting/export/class-batch-export-payments.php:61 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:61 +#: includes/admin/tools.php:674 +msgid "Purchase Key" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:77 +#: includes/admin/reporting/export/class-batch-export-payments.php:63 +#: includes/admin/reporting/export/class-batch-export-sales.php:48 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:63 +#: includes/admin/tools.php:742 +msgid "User" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:185 +#: includes/admin/reporting/export/class-batch-export-payments.php:128 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:129 +msgid "none" +msgstr "" + +#: includes/admin/reporting/class-export-payments.php:190 +#: includes/admin/reporting/export/class-batch-export-payments.php:216 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:212 +msgid "guest" +msgstr "" + +#: includes/admin/reporting/class-export.php:188 +#: includes/admin/reporting/export/class-batch-export.php:146 +#: includes/admin/tools/class-edd-tools-recount-customer-stats.php:114 +#: includes/admin/tools/class-edd-tools-recount-store-earnings.php:157 +#: includes/admin/tools/class-edd-tools-reset-stats.php:121 +msgid "You do not have permission to export data." +msgstr "" + +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:112 +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:108 +#: includes/admin/reporting/class-sales-logs-list-table.php:97 +#: includes/admin/reporting/export/class-batch-export-payments.php:42 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:42 +msgid "Order Number" +msgstr "" + +#: includes/admin/reporting/class-file-downloads-logs-list-table.php:115 +#: includes/admin/reporting/export/class-batch-export-file-downloads.php:46 +msgid "User Agent" +msgstr "" + +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:74 +msgid "View Log Message" +msgstr "" + +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:88 +msgid "Log data:" +msgstr "" + +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:110 +msgid "Error Message" +msgstr "" + +#: includes/admin/reporting/class-gateway-error-logs-list-table.php:137 +#: includes/gateways/manual.php:73 +#: includes/gateways/paypal-standard.php:193 +msgid "Payment Error" +msgstr "" + +#: includes/admin/reporting/class-gateways-reports-table.php:75 +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:77 +msgid "Complete Sales" +msgstr "" + +#: includes/admin/reporting/class-gateways-reports-table.php:76 +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:78 +msgid "Pending / Failed Sales" +msgstr "" + +#: includes/admin/reporting/class-sales-logs-list-table.php:100 +#: includes/admin/reporting/export/class-batch-export-sales.php:54 +msgid "Item Amount" +msgstr "" + +#: includes/admin/reporting/contextual-help.php:62 +msgid "This screen provides you with reports for your earnings, downloads, customers and taxes." +msgstr "" + +#: includes/admin/reporting/contextual-help.php:67 +#: includes/admin/reporting/reports.php:2672 +#: includes/admin/reporting/views/export-sales-earnings.php:61 +#: includes/admin/tools.php:1065 +msgid "Export" +msgstr "" + +#: includes/admin/reporting/contextual-help.php:69 +msgid "This screen allows you to export your reports into a CSV format." +msgstr "" + +#: includes/admin/reporting/contextual-help.php:70 +msgid "Sales and Earnings - This report exports all of the sales and earnings that you have made in the current year. It includes your sales and earnings for each product as well a graphs of sales and earnings so you can compare them for each month." +msgstr "" + +#: includes/admin/reporting/contextual-help.php:71 +msgid "Payment History - This report exports all of the payments you have received on your EDD store in a CSV format. It includes the contact details of the customer, the products they have purchased as well as any discount codes they have used and the final price they have paid." +msgstr "" + +#: includes/admin/reporting/contextual-help.php:72 +msgid "Customers - This report exports all of your customers in a CSV format. It exports the customer's name and email address and the amount of products they have purchased as well as the final price of their total purchases." +msgstr "" + +#: includes/admin/reporting/contextual-help.php:73 +msgid "Download History - This report exports all of the downloads you have received in the current month into a CSV. It exports the date the file was downloaded, the customer it was downloaded by, their IP address, the name of the product and the file they downloaded." +msgstr "" + +#: includes/admin/reporting/contextual-help.php:79 +msgid "Search File Downloads" +msgstr "" + +#: includes/admin/reporting/contextual-help.php:81 +msgid "The file download log can be searched in several different ways:" +msgstr "" + +#: includes/admin/reporting/contextual-help.php:83 +msgid "You can enter the customer's email address" +msgstr "" + +#: includes/admin/reporting/contextual-help.php:84 +msgid "You can enter the customer's IP address" +msgstr "" + +#: includes/admin/reporting/contextual-help.php:85 +msgid "You can enter the download file's name" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-api-requests.php:40 +msgid "API Request" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-api-requests.php:42 +msgid "API User" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-customers.php:52 +msgid "User ID" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-customers.php:55 +#: includes/admin/reporting/export/class-batch-export-taxed-customers.php:44 +msgid "Number of Purchases" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-customers.php:56 +#: includes/admin/reporting/export/class-batch-export-taxed-customers.php:45 +msgid "Customer Value" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-customers.php:57 +msgid "Payment IDs" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-customers.php:58 +#: includes/admin/reporting/export/class-batch-export-downloads.php:44 +#: includes/privacy-functions.php:598 +msgid "Date Created" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:42 +#: includes/blocks/build/terms/index.js:1 +msgid "Slug" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:45 +msgid "Author" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:46 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:153 +msgid "Description" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:47 +#: includes/blocks/build/downloads/index.js:1 +msgid "Excerpt" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:49 +#: includes/post-types.php:259 +msgid "Categories" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:50 +#: includes/post-types.php:294 +msgid "Tags" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:52 +#: includes/blocks/includes/orders/orders.php:574 +#: templates/history-downloads.php:46 +msgid "Files" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:54 +msgid "Featured Image" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-downloads.php:55 +#: includes/emails/tags.php:324 +#: includes/emails/tags.php:459 +#: templates/shortcode-receipt.php:206 +msgid "SKU" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-earnings-report.php:66 +msgid "Monthly Sales Activity" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-earnings-report.php:67 +msgid "Gross Activity" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-earnings-report.php:76 +msgid "Net Activity" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-earnings-report.php:152 +msgid "Order Count" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-earnings-report.php:153 +msgid "Gross Amount" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:41 +#: includes/admin/reporting/export/class-batch-export-sales.php:56 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:41 +#: includes/privacy-functions.php:868 +msgid "Order ID" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:44 +#: includes/admin/reporting/export/class-batch-export-sales.php:49 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:44 +#: includes/admin/tools.php:595 +#: includes/privacy-functions.php:586 +#: includes/privacy-functions.php:872 +msgid "Customer ID" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:52 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:53 +msgid "Products (Verbose)" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:65 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:66 +#: includes/admin/tools.php:625 +msgid "Mode (Live|Test)" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:67 +#: includes/admin/reporting/export/class-batch-export-taxed-orders.php:68 +msgid "Country Name" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-payments.php:68 +msgid "State Name" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-sales.php:47 +msgid "Product ID" +msgstr "" + +#: includes/admin/reporting/export/class-batch-export-sales.php:57 +msgid "Price ID" +msgstr "" + +#: includes/admin/reporting/export/export-functions.php:51 +msgid "Export location or file not writable" +msgstr "" + +#: includes/admin/reporting/export/export-functions.php:78 +msgid "No data found for export parameters" +msgstr "" + +#: includes/admin/reporting/export/export-functions.php:85 +msgid "Batch Processing Complete" +msgstr "" + +#: includes/admin/reporting/graphing.php:311 +msgid "Earnings Over Time" +msgstr "" + +#: includes/admin/reporting/graphing.php:328 +#: includes/admin/reporting/graphing.php:614 +msgid "Total earnings for period shown: " +msgstr "" + +#: includes/admin/reporting/graphing.php:336 +#: includes/admin/reporting/graphing.php:615 +msgid "Total sales for period shown: " +msgstr "" + +#: includes/admin/reporting/graphing.php:342 +msgid "Estimated monthly earnings: " +msgstr "" + +#: includes/admin/reporting/graphing.php:350 +msgid "Estimated monthly sales: " +msgstr "" + +#: includes/admin/reporting/graphing.php:357 +msgid "Excludes sales tax." +msgstr "" + +#: includes/admin/reporting/graphing.php:605 +msgid "Earnings Over Time for %s" +msgstr "" + +#: includes/admin/reporting/graphing.php:616 +msgid "Average monthly earnings: %s" +msgstr "" + +#: includes/admin/reporting/graphing.php:617 +msgid "Average monthly sales: %s" +msgstr "" + +#: includes/admin/reporting/graphing.php:670 +msgid "Invalid date format. Please enter a date in the format: YYYY-mm-dd." +msgstr "" + +#: includes/admin/reporting/graphing.php:671 +msgid "Invalid Date Error" +msgstr "" + +#: includes/admin/reporting/graphing.php:749 +msgid "Clicking this will clear the reports cache" +msgstr "" + +#: includes/admin/reporting/graphing.php:749 +msgid "Refresh Reports" +msgstr "" + +#: includes/admin/reporting/logs.php:20 +msgid "The logs tab has been moved to the Tools screen." +msgstr "" + +#: includes/admin/reporting/reports.php:279 +#: includes/admin/reporting/reports.php:1375 +msgid "Average Order Value" +msgstr "" + +#: includes/admin/reporting/reports.php:303 +#: includes/admin/reporting/reports.php:2479 +#: includes/admin/reporting/reports.php:2553 +#: includes/admin/reporting/reports.php:2626 +#: includes/emails/email-summary/edd-email-summary-template.php:279 +msgid "New Customers" +msgstr "" + +#: includes/admin/reporting/reports.php:322 +#: includes/admin/reporting/reports.php:1079 +msgid "Total Refund Amount" +msgstr "" + +#: includes/admin/reporting/reports.php:345 +#: includes/admin/reporting/reports.php:732 +#: includes/admin/reporting/reports.php:1526 +msgid "Sales and Earnings" +msgstr "" + +#: includes/admin/reporting/reports.php:444 +msgid "Sales / Earnings" +msgstr "" + +#: includes/admin/reporting/reports.php:518 +msgid "Most Valuable %s" +msgstr "" + +#: includes/admin/reporting/reports.php:555 +msgid "Average Sales / Earnings" +msgstr "" + +#: includes/admin/reporting/reports.php:570 +msgid "Net\t" +msgstr "" + +#: includes/admin/reporting/reports.php:607 +msgid "Earnings By Taxonomy" +msgstr "" + +#: includes/admin/reporting/reports.php:619 +msgid "Top Selling %s" +msgstr "" + +#: includes/admin/reporting/reports.php:631 +msgid "Sales by Variation" +msgstr "" + +#: includes/admin/reporting/reports.php:681 +msgid "Earnings by Variation" +msgstr "" + +#: includes/admin/reporting/reports.php:1020 +msgid "Number of Refunds" +msgstr "" + +#: includes/admin/reporting/reports.php:1039 +msgid "Number of Fully Refunded Orders" +msgstr "" + +#: includes/admin/reporting/reports.php:1059 +msgid "Number of Fully Refunded Items" +msgstr "" + +#: includes/admin/reporting/reports.php:1099 +msgid "Average Refund Amount" +msgstr "" + +#: includes/admin/reporting/reports.php:1120 +msgid "Average Time to Refund" +msgstr "" + +#: includes/admin/reporting/reports.php:1138 +msgid "Refund Rate" +msgstr "" + +#: includes/admin/reporting/reports.php:1286 +msgid "Payment Gateways" +msgstr "" + +#: includes/admin/reporting/reports.php:1412 +msgid "Gateway Stats" +msgstr "" + +#: includes/admin/reporting/reports.php:1426 +msgid "Gateway Sales" +msgstr "" + +#: includes/admin/reporting/reports.php:1475 +msgid "Gateway Earnings" +msgstr "" + +#: includes/admin/reporting/reports.php:1750 +#: includes/admin/settings/contextual-help.php:99 +#: includes/admin/settings/register-settings.php:724 +#: src/Admin/Settings/Tabs/Taxes.php:36 +msgid "Taxes" +msgstr "" + +#: includes/admin/reporting/reports.php:1761 +msgid "Total Tax Collected" +msgstr "" + +#: includes/admin/reporting/reports.php:1796 +msgid "Total Tax Collected for " +msgstr "" + +#: includes/admin/reporting/reports.php:1826 +msgid "Tax Collected by Location" +msgstr "" + +#: includes/admin/reporting/reports.php:1905 +#: includes/admin/reporting/reports.php:2089 +#: includes/admin/settings/register-settings.php:804 +#: includes/admin/tools/logs.php:172 +#: includes/privacy-functions.php:546 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:58 +msgid "File Downloads" +msgstr "" + +#: includes/admin/reporting/reports.php:1917 +#: includes/admin/reporting/reports.php:2007 +msgid "Number of File Downloads" +msgstr "" + +#: includes/admin/reporting/reports.php:1941 +msgid "Average per Customer" +msgstr "" + +#: includes/admin/reporting/reports.php:1959 +msgid "Average per Order" +msgstr "" + +#: includes/admin/reporting/reports.php:1977 +msgid "Most Downloaded Product" +msgstr "" + +#: includes/admin/reporting/reports.php:1995 +msgid "Top Five Most Downloaded Products" +msgstr "" + +#: includes/admin/reporting/reports.php:2196 +msgid "Number of Discounts Used" +msgstr "" + +#: includes/admin/reporting/reports.php:2213 +msgid "Discount Ratio" +msgstr "" + +#: includes/admin/reporting/reports.php:2231 +msgid "Customer Savings" +msgstr "" + +#: includes/admin/reporting/reports.php:2252 +msgid "Average Discount Amount" +msgstr "" + +#: includes/admin/reporting/reports.php:2270 +msgid "Most Popular Discount" +msgstr "" + +#: includes/admin/reporting/reports.php:2295 +msgid "Discount Usage Count" +msgstr "" + +#: includes/admin/reporting/reports.php:2314 +msgid "Top Five Discounts" +msgstr "" + +#: includes/admin/reporting/reports.php:2327 +#: includes/admin/reporting/reports.php:2403 +msgid "Discount Usage" +msgstr "" + +#: includes/admin/reporting/reports.php:2498 +msgid "Average Revenue per Customer" +msgstr "" + +#: includes/admin/reporting/reports.php:2513 +msgid "Average Orders per Customer" +msgstr "" + +#: includes/admin/reporting/reports.php:2529 +msgid "Top Five Customers — All Time" +msgstr "" + +#: includes/admin/reporting/reports.php:2541 +msgid "Most Valuable Customers" +msgstr "" + +#: includes/admin/reporting/reports.php:2783 +#: includes/user-functions.php:941 +#: includes/user-functions.php:945 +msgid "Notice" +msgstr "" + +#: includes/admin/reporting/reports.php:2784 +msgid "Tax reports are only generated for taxes associated with a location. The legacy default tax rate is unable to be reported on." +msgstr "" + +#: includes/admin/reporting/views/export-api-requests.php:2 +msgid "Export API Request Logs" +msgstr "" + +#: includes/admin/reporting/views/export-api-requests.php:4 +msgid "Download a CSV of API request logs." +msgstr "" + +#: includes/admin/reporting/views/export-api-requests.php:8 +msgid "Export API Request Log Dates" +msgstr "" + +#: includes/admin/reporting/views/export-api-requests.php:10 +#: includes/admin/reporting/views/export-download-history.php:21 +#: includes/admin/reporting/views/export-orders.php:10 +#: includes/admin/reporting/views/export-sales-earnings.php:10 +#: includes/admin/reporting/views/export-sales.php:10 +#: includes/admin/reporting/views/export-taxed-customers.php:9 +#: includes/admin/reporting/views/export-taxed-orders.php:10 +msgid "Set start date" +msgstr "" + +#: includes/admin/reporting/views/export-api-requests.php:21 +#: includes/admin/reporting/views/export-download-history.php:32 +#: includes/admin/reporting/views/export-orders.php:21 +#: includes/admin/reporting/views/export-sales-earnings.php:21 +#: includes/admin/reporting/views/export-sales.php:21 +#: includes/admin/reporting/views/export-taxed-customers.php:20 +#: includes/admin/reporting/views/export-taxed-orders.php:21 +msgid "Set end date" +msgstr "" + +#: includes/admin/reporting/views/export-api-requests.php:35 +#: includes/admin/reporting/views/export-customers.php:60 +#: includes/admin/reporting/views/export-download-history.php:46 +#: includes/admin/reporting/views/export-downloads.php:23 +#: includes/admin/reporting/views/export-earnings-report.php:29 +#: includes/admin/reporting/views/export-orders.php:49 +#: includes/admin/reporting/views/export-sales.php:46 +#: includes/admin/reporting/views/export-taxed-customers.php:34 +#: includes/admin/reporting/views/export-taxed-orders.php:69 +msgid "Generate CSV" +msgstr "" + +#: includes/admin/reporting/views/export-customers.php:2 +msgid "Export Customers" +msgstr "" + +#: includes/admin/reporting/views/export-customers.php:4 +msgid "Download a CSV of customers. Select a taxonomy to see all the customers who purchased %s in that taxonomy." +msgstr "" + +#: includes/admin/reporting/views/export-customers.php:32 +#: includes/blocks/build/terms/index.js:1 +msgid "Select Taxonomy" +msgstr "" + +#: includes/admin/reporting/views/export-customers.php:41 +msgid "All Taxonomies" +msgstr "" + +#: includes/admin/reporting/views/export-customers.php:45 +#: includes/admin/reporting/views/export-download-history.php:6 +#: includes/admin/reporting/views/export-downloads.php:10 +#: includes/admin/reporting/views/export-sales-earnings.php:34 +#: includes/admin/reporting/views/export-sales.php:34 +msgid "Select Download" +msgstr "" + +#. translators: the plural post type label +#: includes/admin/reporting/views/export-customers.php:53 +#: includes/admin/reporting/views/export-download-history.php:13 +#: includes/admin/reporting/views/export-downloads.php:17 +#: includes/admin/reporting/views/export-sales-earnings.php:42 +#: includes/reports/reports-functions.php:1441 +msgid "All %s" +msgstr "" + +#: includes/admin/reporting/views/export-download-history.php:2 +msgid "Export File Download Logs" +msgstr "" + +#: includes/admin/reporting/views/export-download-history.php:4 +msgid "Download a CSV of file download logs." +msgstr "" + +#: includes/admin/reporting/views/export-download-history.php:19 +msgid "Export File Download Log Dates" +msgstr "" + +#. translators: the singular post type label +#: includes/admin/reporting/views/export-downloads.php:4 +msgid "Export %s Products" +msgstr "" + +#. translators: the plural post type label +#: includes/admin/reporting/views/export-downloads.php:8 +msgid "Download a CSV of product %1$s." +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:2 +msgid "Export Earnings Report" +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:4 +msgid "Download a CSV giving a detailed look into earnings over time." +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:8 +msgid "Export Earnings Start" +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:10 +msgid "Select start month" +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:12 +msgid "Select start year" +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:16 +msgctxt "Date one to date two" +msgid "— to —" +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:20 +msgid "Export Earnings End" +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:22 +msgid "Select end month" +msgstr "" + +#: includes/admin/reporting/views/export-earnings-report.php:24 +msgid "Select end year" +msgstr "" + +#: includes/admin/reporting/views/export-orders.php:2 +msgid "Export Orders" +msgstr "" + +#: includes/admin/reporting/views/export-orders.php:4 +msgid "Download a CSV of all orders." +msgstr "" + +#: includes/admin/reporting/views/export-orders.php:8 +msgid "Export Order Dates" +msgstr "" + +#: includes/admin/reporting/views/export-orders.php:33 +#: includes/admin/reporting/views/export-taxed-orders.php:33 +msgid "Select Status" +msgstr "" + +#: includes/admin/reporting/views/export-orders.php:39 +#: includes/admin/reporting/views/export-taxed-orders.php:39 +msgid "All Statuses" +msgstr "" + +#: includes/admin/reporting/views/export-sales-earnings.php:2 +msgid "Export Sales and Earnings" +msgstr "" + +#: includes/admin/reporting/views/export-sales-earnings.php:4 +msgid "Download a CSV of all sales or earnings on a day-by-day basis." +msgstr "" + +#: includes/admin/reporting/views/export-sales-earnings.php:8 +msgid "Export Sales and Earnings Dates" +msgstr "" + +#: includes/admin/reporting/views/export-sales-earnings.php:46 +msgid "Select Customer" +msgstr "" + +#: includes/admin/reporting/views/export-sales-earnings.php:54 +msgid "All Customers" +msgstr "" + +#: includes/admin/reporting/views/export-sales.php:2 +msgid "Export Product Sales" +msgstr "" + +#: includes/admin/reporting/views/export-sales.php:4 +msgid "Download a CSV file containing a record of each sale of a product along with the customer information." +msgstr "" + +#: includes/admin/reporting/views/export-sales.php:8 +msgid "Export Sales Dates" +msgstr "" + +#: includes/admin/reporting/views/export-taxed-customers.php:2 +msgid "Export Taxed Customers" +msgstr "" + +#: includes/admin/reporting/views/export-taxed-customers.php:4 +msgid "Download a CSV of all customers that were taxed." +msgstr "" + +#: includes/admin/reporting/views/export-taxed-customers.php:8 +msgid "Export Taxed Customer Dates" +msgstr "" + +#: includes/admin/reporting/views/export-taxed-orders.php:2 +msgid "Export Taxed Orders" +msgstr "" + +#: includes/admin/reporting/views/export-taxed-orders.php:4 +msgid "Download a CSV of all orders, taxed by Country and/or Region." +msgstr "" + +#: includes/admin/reporting/views/export-taxed-orders.php:8 +msgid "Export Taxed Order Dates" +msgstr "" + +#: includes/admin/reporting/views/export-taxed-orders.php:46 +msgid "Select Country" +msgstr "" + +#: includes/admin/reporting/views/export-taxed-orders.php:57 +msgid "Select Region" +msgstr "" + +#: includes/admin/reporting/views/export-taxed-orders.php:62 +#: includes/class-edd-html-elements.php:658 +msgid "All Regions" +msgstr "" + +#: includes/admin/settings/contextual-help.php:53 +msgid "Need more from your Easy Digital Downloads store? Upgrade Now!." +msgstr "" + +#: includes/admin/settings/contextual-help.php:62 +msgid "This screen provides the most basic settings for configuring your store. You can set the currency, page templates, and general store settings." +msgstr "" + +#: includes/admin/settings/contextual-help.php:67 +#: includes/admin/settings/register-settings.php:720 +msgid "Payments" +msgstr "" + +#: includes/admin/settings/contextual-help.php:69 +msgid "This screen provides ways to enable Test Mode, toggle payment gateways on or off, manage accounting settings, and configure gateway-specific settings. Any extra payment gateway extensions you have installed will appear on this page, and can be configured to suit your needs." +msgstr "" + +#: includes/admin/settings/contextual-help.php:70 +msgid "Test Payment - This included gateway is great for testing your store, as it requires no payment, and leads straight to product downloads. However, please remember to turn it off once your site is live!" +msgstr "" + +#: includes/admin/settings/contextual-help.php:71 +msgid "PayPal - A PayPal payment gateway is included as standard with Easy Digital Downloads. To test the PayPal gateway, you need a Sandbox account for PayPal and the site must be placed in Test Mode from the Payments > Gateways tab. Please remember to enter your PayPal account email address in order for payments to get processed." +msgstr "" + +#: includes/admin/settings/contextual-help.php:72 +msgid "Stripe - The Stripe payment gateway is also included with Easy Digital Downloads. To test the Stripe gateway, you must \"Connect with Stripe\" and the site must be placed in Test Mode from the Payments > Gateways tab." +msgstr "" + +#: includes/admin/settings/contextual-help.php:77 +#: includes/admin/settings/register-settings.php:721 +#: src/Admin/Onboarding/Wizard.php:240 +msgid "Emails" +msgstr "" + +#: includes/admin/settings/contextual-help.php:79 +msgid "This screen allows you to customize how emails act throughout your store. You can choose a premade template, set the sender's name, email address, and subject." +msgstr "" + +#: includes/admin/settings/contextual-help.php:80 +msgid "A set of email tag markers has also been provided to allow the creation of personalized emails. A tag consists of a keyword surrounded by curly braces: {tag}." +msgstr "" + +#: includes/admin/settings/contextual-help.php:85 +#: includes/admin/settings/register-settings.php:722 +msgid "Marketing" +msgstr "" + +#: includes/admin/settings/contextual-help.php:87 +msgid "Marketing settings will help you connect with your customers." +msgstr "" + +#: includes/admin/settings/contextual-help.php:88 +msgid "Marketing specific extensions will add their settings here as well." +msgstr "" + +#: includes/admin/settings/contextual-help.php:93 +#: includes/admin/settings/register-settings.php:723 +msgid "Styles" +msgstr "" + +#: includes/admin/settings/contextual-help.php:94 +msgid "This screen allows customization of your store's styles. For complete control, you can completely disable all styles generated by the plugin." +msgstr "" + +#: includes/admin/settings/contextual-help.php:101 +msgid "This screen allows you to configure the tax rules for your store." +msgstr "" + +#: includes/admin/settings/contextual-help.php:102 +msgid "If you do not wish to charge any tax on purchase, simply leave the Enable Taxes option unchecked." +msgstr "" + +#: includes/admin/settings/contextual-help.php:103 +msgid "Default Tax Rate: The default tax rate is the tax rate charged to customers located in your base country / state or province." +msgstr "" + +#: includes/admin/settings/contextual-help.php:104 +msgid "Base Country: This determines the country that is loaded by default on the checkout screen for customers that do not have an address stored in their account." +msgstr "" + +#: includes/admin/settings/contextual-help.php:105 +msgid "Base State / Province: This determines the region that is loaded by default on the checkout screen for customers that do not have an address stored in their account." +msgstr "" + +#: includes/admin/settings/contextual-help.php:106 +msgid "Prices Entered with Tax: if enabled, this means that the price entered on the product edit screens is the total amount the customer will pay after taxes. For example, if enabled and the price of a product is $20, the customer will pay 20$ at checkout. The exact amount charged in tax will be calculated automatically." +msgstr "" + +#: includes/admin/settings/contextual-help.php:107 +msgid "Display Tax Rate on Prices: when enabled, the amount the customer is expected to pay in tax will be displayed below purchase buttons." +msgstr "" + +#: includes/admin/settings/contextual-help.php:108 +msgid "Display During Checkout: This determines whether prices are shown with taxes or without taxes on checkout. If set to Including Tax, a $10 product with a 10% tax will be shown as $11." +msgstr "" + +#: includes/admin/settings/contextual-help.php:109 +msgid "Calculate Tax After Discounts: If enabled, this option will make it so that tax is calculated on the after-discount amount. If a purchase of $20 is made and a 20% discount is applied, tax will be calculated off of $16 instead of $20." +msgstr "" + +#: includes/admin/settings/contextual-help.php:110 +msgid "Additional Tax Rates: This section lets you add tax rates for specific countries and/or states/provinces in those countries." +msgstr "" + +#: includes/admin/settings/contextual-help.php:115 +#: includes/admin/settings/register-settings.php:725 +msgid "Policies" +msgstr "" + +#: includes/admin/settings/contextual-help.php:117 +msgid "This screen provides access to customer privacy policies, terms & agreements, and how to display them on the front of your site." +msgstr "" + +#: includes/admin/settings/contextual-help.php:118 +msgid "You may also override what happens to order records when a customer exercises their right to be forgotten from your site." +msgstr "" + +#: includes/admin/settings/contextual-help.php:124 +msgid "This screen provides access to settings added by most Easy Digital Downloads extensions." +msgstr "" + +#: includes/admin/settings/contextual-help.php:129 +msgid "Miscellaneous" +msgstr "" + +#: includes/admin/settings/contextual-help.php:131 +msgid "This screen provides other miscellaneous options such as configuring your store buttons, file download functionality, and terms of service." +msgstr "" + +#: includes/admin/settings/display-settings.php:28 +#: includes/post-types.php:171 +msgid "Downloads" +msgstr "" + +#: includes/admin/settings/display-settings.php:49 +#: src/Admin/PassHandler/Handler.php:131 +msgid "View Extensions" +msgstr "" + +#: includes/admin/settings/display-settings.php:51 +msgid "Manage Extensions" +msgstr "" + +#. Translators: %1$s number of notifications; %2$s opening span tag; %3$s closing span tag +#: includes/admin/settings/display-settings.php:123 +msgid "%1$s %2$sunread notifications%3$s" +msgstr "" + +#: includes/admin/settings/display-settings.php:394 +msgid "Taxes are currently disabled. Rates listed below will not be applied to purchases until taxes are enabled." +msgstr "" + +#: includes/admin/settings/display-settings.php:413 +msgid "Manage extensions for Easy Digital Downloads which are not included with a pass. Having an active license for your extensions gives you access to updates when they're available." +msgstr "" + +#. translators: 1. opening anchor tag; 2. closing anchor tag +#: includes/admin/settings/display-settings.php:430 +msgid "Have a pass? You're ready to set up EDD (Pro). %1$sActivate Your Pass%2$s" +msgstr "" + +#: includes/admin/settings/register-settings.php:727 +msgid "Licenses" +msgstr "" + +#: includes/admin/settings/register-settings.php:728 +msgid "Misc" +msgstr "" + +#: includes/admin/settings/register-settings.php:764 +msgid "Store" +msgstr "" + +#: includes/admin/settings/register-settings.php:766 +#: src/Admin/Settings/Tabs/General.php:112 +msgid "Pages" +msgstr "" + +#: includes/admin/settings/register-settings.php:767 +#: src/Admin/Settings/Tabs/General.php:219 +msgid "API" +msgstr "" + +#: includes/admin/settings/register-settings.php:771 +#: includes/blocks/includes/checkout/checkout.php:131 +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:92 +#: includes/install.php:285 +#: templates/widget-cart-checkout.php:6 +#: templates/widget-cart-empty.php:7 +msgid "Checkout" +msgstr "" + +#: includes/admin/settings/register-settings.php:773 +msgid "Accounting" +msgstr "" + +#: includes/admin/settings/register-settings.php:777 +msgid "Purchase Receipts" +msgstr "" + +#: includes/admin/settings/register-settings.php:778 +msgid "Sale Notifications" +msgstr "" + +#: includes/admin/settings/register-settings.php:779 +msgid "Summaries" +msgstr "" + +#: includes/admin/settings/register-settings.php:786 +#: includes/blocks/build/pro/currency-selector/index.js:1 +msgid "Buttons" +msgstr "" + +#: includes/admin/settings/register-settings.php:790 +msgid "Rates" +msgstr "" + +#: includes/admin/settings/register-settings.php:793 +#: src/Admin/Settings/Tabs/Privacy.php:36 +msgid "Privacy Policy" +msgstr "" + +#: includes/admin/settings/register-settings.php:794 +#: src/Admin/Settings/Tabs/Privacy.php:71 +msgid "Terms & Agreements" +msgstr "" + +#: includes/admin/settings/register-settings.php:795 +msgid "Export & Erase" +msgstr "" + +#: includes/admin/settings/register-settings.php:798 +msgid "Main" +msgstr "" + +#: includes/admin/settings/register-settings.php:803 +msgid "Purchase Buttons" +msgstr "" + +#: includes/admin/settings/register-settings.php:826 +#: includes/class-edd-discount.php:672 +#: src/Admin/Settings/Tabs/Gateways.php:103 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "None" +msgstr "" + +#: includes/admin/settings/register-settings.php:1182 +#: includes/gateways/functions.php:80 +#: includes/gateways/functions.php:81 +msgid "Store Gateway" +msgstr "" + +#: includes/admin/settings/register-settings.php:1182 +msgid "This is an internal payment gateway which can be used for manually added orders or test purchases. No money is actually processed." +msgstr "" + +#: includes/admin/settings/register-settings.php:1191 +msgid "Configure Gateway" +msgstr "" + +#. translators: 1. opening link tag; do not translate; 2. closing link tag; do not translate +#: includes/admin/settings/register-settings.php:1211 +msgid "Choose how you want to allow your customers to pay you." +msgstr "" + +#. translators: 1. opening link tag; do not translate; 2. closing link tag; do not translate +#: includes/admin/settings/register-settings.php:1211 +msgid "More %1$sPayment Gateways%2$s are available." +msgstr "" + +#: includes/admin/settings/register-settings.php:1240 +msgid "Automatic" +msgstr "" + +#. translators: %s: the setting ID +#: includes/admin/settings/register-settings.php:1472 +msgid "The callback function used for the %s setting is missing." +msgstr "" + +#: includes/admin/settings/register-settings.php:1656 +msgid "Attach File" +msgstr "" + +#: includes/admin/settings/register-settings.php:1656 +msgid "Attach" +msgstr "" + +#: includes/admin/settings/register-settings.php:1774 +msgid "Recapture plugin activated." +msgstr "" + +#. translators: %1$s - opening anchor tag, %2$s - closing anchor tag +#: includes/admin/settings/register-settings.php:1779 +msgid "%1$sAccess your Recapture account%2$s." +msgstr "" + +#: includes/admin/settings/register-settings.php:1789 +msgid "Disconnect Recapture" +msgstr "" + +#. translators: %1$s - opening anchor tag, %2$s - closing anchor tag +#: includes/admin/settings/register-settings.php:1797 +msgid "%1$sComplete your connection to Recapture%2$s" +msgstr "" + +#: includes/admin/settings/register-settings.php:1811 +msgid "We recommend Recapture for recovering lost revenue by automatically sending effective, targeted emails to customers who abandon their shopping cart." +msgstr "" + +#. translators: %1$s - opening anchor tag, %2$s - closing anchor tag +#: includes/admin/settings/register-settings.php:1816 +msgid "%1$sLearn more%2$s (Free trial available)" +msgstr "" + +#: includes/admin/settings/register-settings.php:1825 +msgid "Connect with Recapture" +msgstr "" + +#. translators: Tax rate country code +#: includes/admin/settings/register-settings.php:1858 +msgid "Duplicate tax rates are not allowed. Please deactivate the existing %s tax rate before adding or activating another." +msgstr "" + +#: includes/admin/settings/register-settings.php:1859 +msgid "Please select a country." +msgstr "" + +#: includes/admin/settings/register-settings.php:1860 +msgid "Please enter a tax rate greater than 0." +msgstr "" + +#: includes/admin/settings/register-settings.php:1861 +msgid "Are you sure you want to add a 0% tax rate?" +msgstr "" + +#. translators: singular download label +#: includes/admin/thickbox.php:31 +#: includes/admin/thickbox.php:184 +msgid "Insert %s" +msgstr "" + +#: includes/admin/thickbox.php:58 +msgid "Link" +msgstr "" + +#: includes/admin/thickbox.php:59 +#: includes/template-functions.php:522 +msgid "Button" +msgstr "" + +#: includes/admin/thickbox.php:81 +msgid "You must choose a download" +msgstr "" + +#: includes/admin/thickbox.php:115 +msgid "Choose an existing product" +msgstr "" + +#: includes/admin/thickbox.php:122 +msgid "Behavior" +msgstr "" + +#: includes/admin/thickbox.php:127 +msgid "Direct Link" +msgstr "" + +#: includes/admin/thickbox.php:129 +msgid "How do you want this to work?" +msgstr "" + +#: includes/admin/thickbox.php:136 +msgid "Style" +msgstr "" + +#: includes/admin/thickbox.php:146 +msgid "Choose between a Button or a Link" +msgstr "" + +#: includes/admin/thickbox.php:153 +msgid "Color" +msgstr "" + +#: includes/admin/thickbox.php:163 +msgid "Choose the button color" +msgstr "" + +#: includes/admin/thickbox.php:170 +#: includes/blocks/includes/admin/settings.php:169 +msgid "Text" +msgstr "" + +#: includes/admin/thickbox.php:173 +msgid "View Product" +msgstr "" + +#: includes/admin/thickbox.php:174 +msgid "This is the text inside the button or link" +msgstr "" + +#: includes/admin/tools.php:109 +msgid "Beta Versions" +msgstr "" + +#: includes/admin/tools.php:111 +msgid "System Info" +msgstr "" + +#: includes/admin/tools.php:112 +#: includes/admin/tools.php:1237 +msgid "Debug Log" +msgstr "" + +#: includes/admin/tools.php:113 +msgid "Import/Export" +msgstr "" + +#: includes/admin/tools.php:144 +msgid "Use these tools to recount / reset store stats." +msgstr "" + +#: includes/admin/tools.php:151 +#: includes/scripts.php:200 +msgid "Please select an option" +msgstr "" + +#: includes/admin/tools.php:153 +msgid "Recount Store Earnings and Sales" +msgstr "" + +#. translators: %s: Singular download label, lowercase +#: includes/admin/tools.php:158 +msgid "Recount Earnings and Sales for a %s" +msgstr "" + +#. translators: %s: Plural download label, lowercase +#: includes/admin/tools.php:165 +msgid "Recount Earnings and Sales for All %s" +msgstr "" + +#: includes/admin/tools.php:172 +msgid "Reset Store" +msgstr "" + +#: includes/admin/tools.php:186 +msgid "Submit" +msgstr "" + +#: includes/admin/tools.php:192 +msgid "Recalculates the total store earnings and sales." +msgstr "" + +#. translators: %s: Singular wownload label, lowercase +#: includes/admin/tools.php:196 +msgid "Recalculates the earnings and sales stats for a specific %s." +msgstr "" + +#. translators: %s: Pural download label, lowercase +#: includes/admin/tools.php:202 +msgid "Recalculates the earnings and sales stats for all %s." +msgstr "" + +#: includes/admin/tools.php:205 +msgid "Recalculates the lifetime value and purchase counts for all customers." +msgstr "" + +#: includes/admin/tools.php:207 +msgid "Deletes all payment records, customers, and related log entries." +msgstr "" + +#: includes/admin/tools.php:237 +#: includes/admin/tools.php:246 +msgid "Clear Incomplete Upgrade Notice" +msgstr "" + +#: includes/admin/tools.php:239 +msgid "Sometimes a database upgrade notice may not be cleared after an upgrade is completed due to conflicts with other extensions or other minor issues." +msgstr "" + +#: includes/admin/tools.php:240 +msgid "If you're certain these upgrades have been completed, you can clear these upgrade notices by clicking the button below. If you have any questions about this, please contact the Easy Digital Downloads support team and we'll be happy to help." +msgstr "" + +#. translators: %1$s: API documentation linktag , %2$s: iOS app link tag, %3$s: closing link tag +#: includes/admin/tools.php:295 +msgid "These API keys allow you to use the %1$sEDD REST API%3$s to retrieve store data in JSON or XML for external applications or devices, such as the %2$sEDD mobile app%3$s." +msgstr "" + +#: includes/admin/tools.php:327 +msgid "Enable Beta Versions" +msgstr "" + +#: includes/admin/tools.php:329 +msgid "Checking any of the below checkboxes will opt you in to receive pre-release update notifications. You can opt-out at any time. Pre-release updates do not install automatically, you will still have the opportunity to ignore update notifications." +msgstr "" + +#. translators: %s: Product name +#: includes/admin/tools.php:345 +msgid "Get updates for pre-release versions of %s" +msgstr "" + +#: includes/admin/tools.php:501 +msgid "Import Orders" +msgstr "" + +#: includes/admin/tools.php:503 +msgid "Import a CSV file of orders." +msgstr "" + +#: includes/admin/tools.php:515 +#: includes/admin/tools.php:834 +msgid "Import CSV" +msgstr "" + +#. translators: %1$s opening anchor tag, %2$s closing anchor tag +#: includes/admin/tools.php:528 +msgid "Each column loaded from the CSV needs to be mapped to an order field. Select the column that should be mapped to each field below. Any columns not needed can be ignored. See %1$sthis guide%2$s for assistance with importing payment records." +msgstr "" + +#: includes/admin/tools.php:539 +msgid "Payment Field" +msgstr "" + +#: includes/admin/tools.php:540 +#: includes/admin/tools.php:859 +msgid "CSV Column" +msgstr "" + +#: includes/admin/tools.php:541 +#: includes/admin/tools.php:860 +msgid "Data Preview" +msgstr "" + +#: includes/admin/tools.php:546 +msgid "Currency Code" +msgstr "" + +#: includes/admin/tools.php:550 +#: includes/admin/tools.php:559 +#: includes/admin/tools.php:569 +#: includes/admin/tools.php:579 +#: includes/admin/tools.php:589 +#: includes/admin/tools.php:599 +#: includes/admin/tools.php:609 +#: includes/admin/tools.php:619 +#: includes/admin/tools.php:629 +#: includes/admin/tools.php:639 +#: includes/admin/tools.php:649 +#: includes/admin/tools.php:659 +#: includes/admin/tools.php:668 +#: includes/admin/tools.php:678 +#: includes/admin/tools.php:688 +#: includes/admin/tools.php:698 +#: includes/admin/tools.php:707 +#: includes/admin/tools.php:716 +#: includes/admin/tools.php:726 +#: includes/admin/tools.php:736 +#: includes/admin/tools.php:746 +#: includes/admin/tools.php:756 +#: includes/admin/tools.php:766 +#: includes/admin/tools.php:775 +#: includes/admin/tools.php:784 +#: includes/admin/tools.php:794 +#: includes/admin/tools.php:804 +#: includes/admin/tools.php:869 +#: includes/admin/tools.php:879 +#: includes/admin/tools.php:889 +#: includes/admin/tools.php:899 +#: includes/admin/tools.php:909 +#: includes/admin/tools.php:919 +#: includes/admin/tools.php:928 +#: includes/admin/tools.php:937 +#: includes/admin/tools.php:946 +#: includes/admin/tools.php:956 +#: includes/admin/tools.php:966 +#: includes/admin/tools.php:975 +#: includes/admin/tools.php:985 +#: includes/admin/tools.php:994 +#: includes/admin/tools.php:1004 +#: includes/admin/tools.php:1013 +#: includes/admin/tools.php:1023 +msgid "- Ignore this field -" +msgstr "" + +#: includes/admin/tools.php:553 +#: includes/admin/tools.php:562 +#: includes/admin/tools.php:572 +#: includes/admin/tools.php:582 +#: includes/admin/tools.php:592 +#: includes/admin/tools.php:602 +#: includes/admin/tools.php:612 +#: includes/admin/tools.php:622 +#: includes/admin/tools.php:632 +#: includes/admin/tools.php:642 +#: includes/admin/tools.php:652 +#: includes/admin/tools.php:662 +#: includes/admin/tools.php:671 +#: includes/admin/tools.php:681 +#: includes/admin/tools.php:691 +#: includes/admin/tools.php:701 +#: includes/admin/tools.php:710 +#: includes/admin/tools.php:719 +#: includes/admin/tools.php:729 +#: includes/admin/tools.php:739 +#: includes/admin/tools.php:749 +#: includes/admin/tools.php:759 +#: includes/admin/tools.php:769 +#: includes/admin/tools.php:778 +#: includes/admin/tools.php:787 +#: includes/admin/tools.php:797 +#: includes/admin/tools.php:807 +#: includes/admin/tools.php:872 +#: includes/admin/tools.php:882 +#: includes/admin/tools.php:892 +#: includes/admin/tools.php:902 +#: includes/admin/tools.php:912 +#: includes/admin/tools.php:922 +#: includes/admin/tools.php:931 +#: includes/admin/tools.php:940 +#: includes/admin/tools.php:949 +#: includes/admin/tools.php:959 +#: includes/admin/tools.php:969 +#: includes/admin/tools.php:978 +#: includes/admin/tools.php:988 +#: includes/admin/tools.php:997 +#: includes/admin/tools.php:1007 +#: includes/admin/tools.php:1016 +#: includes/admin/tools.php:1026 +msgid "- select field to preview data -" +msgstr "" + +#: includes/admin/tools.php:605 +msgid "Discount Code(s)" +msgstr "" + +#: includes/admin/tools.php:635 +msgid "Parent Payment ID" +msgstr "" + +#: includes/admin/tools.php:684 +msgid "Purchased Product(s)" +msgstr "" + +#: includes/admin/tools.php:752 +#: includes/gateways/stripe/includes/template-functions.php:510 +msgid "Address Line 1" +msgstr "" + +#: includes/admin/tools.php:762 +#: includes/blocks/views/checkout/purchase-form/address.php:43 +#: includes/gateways/stripe/includes/template-functions.php:524 +msgid "Address Line 2" +msgstr "" + +#: includes/admin/tools.php:812 +#: includes/admin/tools.php:1031 +msgid "Process Import" +msgstr "" + +#: includes/admin/tools.php:820 +msgid "Import Download Products" +msgstr "" + +#: includes/admin/tools.php:822 +msgid "Import a CSV file of products." +msgstr "" + +#. translators: %1$s and %2$s opening and closing anchor tags respectively +#: includes/admin/tools.php:847 +msgid "Each column loaded from the CSV needs to be mapped to a Download product field. Select the column that should be mapped to each field below. Any columns not needed can be ignored. See %1$sthis guide%2$s for assistance with importing Download products." +msgstr "" + +#: includes/admin/tools.php:858 +msgid "Product Field" +msgstr "" + +#: includes/admin/tools.php:865 +msgid "Product Author" +msgstr "" + +#: includes/admin/tools.php:875 +#: includes/reports/reports-functions.php:324 +msgid "Product Categories" +msgstr "" + +#: includes/admin/tools.php:885 +msgid "Product Creation Date" +msgstr "" + +#: includes/admin/tools.php:895 +msgid "Product Description" +msgstr "" + +#: includes/admin/tools.php:905 +msgid "Product Excerpt" +msgstr "" + +#: includes/admin/tools.php:915 +#: src/Admin/Onboarding/Steps/Products.php:42 +msgid "Product Image" +msgstr "" + +#: includes/admin/tools.php:925 +msgid "Product Notes" +msgstr "" + +#: includes/admin/tools.php:934 +msgid "Product Price(s)" +msgstr "" + +#: includes/admin/tools.php:943 +msgid "Product SKU" +msgstr "" + +#: includes/admin/tools.php:952 +msgid "Product Slug" +msgstr "" + +#: includes/admin/tools.php:962 +msgid "Product Status" +msgstr "" + +#: includes/admin/tools.php:972 +msgid "Product Tags" +msgstr "" + +#: includes/admin/tools.php:981 +msgid "Product Title" +msgstr "" + +#: includes/admin/tools.php:991 +msgid "Download Files" +msgstr "" + +#: includes/admin/tools.php:1010 +msgid "Sale Count" +msgstr "" + +#: includes/admin/tools.php:1039 +msgid "Export Settings" +msgstr "" + +#: includes/admin/tools.php:1041 +msgid "Export the Easy Digital Downloads settings for this site as a .json file. This allows you to easily import the configuration into another site." +msgstr "" + +#. translators: %s: Reports page URL +#: includes/admin/tools.php:1047 +msgid "To export shop data (purchases, customers, etc), visit the Reports page." +msgstr "" + +#: includes/admin/tools.php:1072 +msgid "Import Settings" +msgstr "" + +#: includes/admin/tools.php:1074 +msgid "Import the Easy Digital Downloads settings from a .json file. This file can be obtained by exporting the settings on another site using the form above." +msgstr "" + +#: includes/admin/tools.php:1083 +msgid "Import" +msgstr "" + +#: includes/admin/tools.php:1161 +msgid "Please upload a valid .json file" +msgstr "" + +#: includes/admin/tools.php:1167 +msgid "Please upload a file to import" +msgstr "" + +#: includes/admin/tools.php:1230 +msgid "No File" +msgstr "" + +#: includes/admin/tools.php:1233 +msgid "Log is Empty" +msgstr "" + +#. translators: 1. opening anchor tag, do not translate; 2. function name, do not translate; 3. closing anchor tag, do not translate +#: includes/admin/tools.php:1245 +msgid "When debug mode is enabled, specific information will be logged here. (%1$sLearn how to use %2$s in your own code.%3$s)" +msgstr "" + +#: includes/admin/tools.php:1261 +msgid "Download Debug Log File" +msgstr "" + +#: includes/admin/tools.php:1266 +msgid "Clear Log" +msgstr "" + +#: includes/admin/tools.php:1275 +msgid "Log file" +msgstr "" + +#: includes/admin/tools/class-edd-tools-recount-all-stats.php:99 +msgid "Earnings and sales stats successfully recounted." +msgstr "" + +#: includes/admin/tools/class-edd-tools-recount-customer-stats.php:124 +#: includes/admin/tools/class-edd-tools-recount-single-customer-stats.php:124 +msgid "Customer stats successfully recounted." +msgstr "" + +#: includes/admin/tools/class-edd-tools-recount-download-stats.php:129 +msgid "Earnings and sales stats successfully recounted for %s." +msgstr "" + +#: includes/admin/tools/class-edd-tools-recount-single-customer-stats.php:114 +msgid "You do not have permission to modify this data." +msgstr "" + +#: includes/admin/tools/class-edd-tools-recount-store-earnings.php:174 +msgid "Store earnings successfully recounted." +msgstr "" + +#: includes/admin/tools/class-edd-tools-reset-stats.php:145 +msgid "Your store has been successfully reset." +msgstr "" + +#: includes/admin/tools/logs.php:173 +msgid "Payment Errors" +msgstr "" + +#: includes/admin/tools/logs.php:174 +msgid "API Requests" +msgstr "" + +#: includes/admin/tracking.php:145 +#: includes/admin/tracking.php:270 +#: src/Admin/Onboarding/Steps/Tools.php:241 +msgid "Join the EDD Community" +msgstr "" + +#: includes/admin/tracking.php:146 +msgid "Yes, I want to help!" +msgstr "" + +#: includes/admin/tracking.php:275 +msgid "Allow" +msgstr "" + +#: includes/admin/tracking.php:277 +msgid "Do not allow" +msgstr "" + +#: includes/admin/tracking.php:290 +#: src/Admin/Onboarding/Steps/Tools.php:245 +msgid "Help us provide a better experience and faster fixes by sharing some anonymous data about how you use Easy Digital Downloads." +msgstr "" + +#. translators: %1$s Link to tracking information, do not translate. %2$s clsoing link tag, do not translate +#: includes/admin/tracking.php:294 +msgid "%1$sHere is what we track.%2$s" +msgstr "" + +#: includes/admin/upgrades/classes/class-file-download-log-migration.php:133 +#: includes/admin/upgrades/v3/class-base.php:116 +msgid "You do not have permission to run this upgrade." +msgstr "" + +#: includes/admin/upgrades/classes/class-file-download-log-migration.php:147 +msgid "File download logs updated successfully." +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:98 +#: includes/install.php:294 +msgid "Transaction Failed" +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:99 +msgid "Your transaction failed, please try again or contact site support." +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:794 +msgid "Migration complete: You have already completed the update to the file download logs." +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:804 +msgid "Upgrades Complete: You may now safely navigate away from this page." +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:808 +msgid "Important: Do not navigate away from this page until all upgrades complete." +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:856 +msgid "Update file download logs" +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:861 +msgid "This will update the file download logs to remove some PII and make file download counts more accurate." +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:870 +#: includes/admin/upgrades/deprecated-upgrade-functions.php:873 +msgid "Update File Download Logs" +msgstr "" + +#: includes/admin/upgrades/deprecated-upgrade-functions.php:874 +msgid "File download logs have already been updated." +msgstr "" + +#. translators: %s: Resume upgrade link +#: includes/admin/upgrades/upgrade-functions.php:46 +msgid "Easy Digital Downloads needs to complete a database upgrade that was previously started, click here to resume the upgrade." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:89 +msgid "Easy Digital Downloads is performing a database migration via WP-CLI. Sales and earnings data for your store will be updated when all orders have been migrated. This message will be removed when the migration is complete." +msgstr "" + +#. translators: 1. Opening strong tag; do not translate. 2. Closing strong tag; do not translate. +#: includes/admin/upgrades/upgrade-functions.php:98 +msgid "Easy Digital Downloads needs to upgrade the database. %1$sLearn more about this upgrade%2$s." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:107 +msgid "About this upgrade:" +msgstr "" + +#. translators: 1. Opening strong/italics tag; do not translate. 2. Closing strong/italics tag; do not translate. +#: includes/admin/upgrades/upgrade-functions.php:113 +msgid "This is a %1$smandatory%2$s update that will migrate all Easy Digital Downloads data to custom database tables. This upgrade will provide better performance and scalability." +msgstr "" + +#. translators: 1. Opening strong tag; do not translate. 2. Closing strong tag; do not translate. 3. Plural download label +#: includes/admin/upgrades/upgrade-functions.php:123 +msgid "%1$sPlease back up your database before starting this upgrade.%2$s This upgrade routine will make irreversible changes to the database." +msgstr "" + +#. translators: 1. Opening strong tag; do not translate. 2. Closing strong tag; do not translate. 3. Line break; do not translate. 4. CLI command example; do not translate. +#: includes/admin/upgrades/upgrade-functions.php:133 +msgid "%1$sAdvanced User?%2$s This upgrade can also be run via WP-CLI with the following command:%3$s%3$s%4$s" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:142 +msgid "For large sites, this is the recommended method of upgrading." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:155 +msgid "Begin the upgrade" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:166 +msgid "Easy Digital Downloads was unable to create the necessary database tables to complete this update. Your site may not meet the minimum requirements for EDD 3.0." +msgstr "" + +#. translators: 1. opening anchor tag, do not translate; 2. closing anchor tag, do not translate; 3. MySQL database version, do not translate +#: includes/admin/upgrades/upgrade-functions.php:173 +msgid "Please contact your host and ask them to upgrade your environment to meet our %1$sminimum technical requirements%2$s. Your MySQL version is %3$s and needs to be updated." +msgstr "" + +#. translators: 1. opening anchor tag, do not translate; 2. closing anchor tag, do not translate +#: includes/admin/upgrades/upgrade-functions.php:181 +msgid "%1$sContact our support team%2$s for help with next steps." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:306 +msgid "Database Upgrade Complete: All database upgrades have been completed." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:308 +msgid "You may now leave this page." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:313 +msgid "Return to the dashboard" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:320 +msgid "Important: Do not navigate away from this page until all upgrades have completed." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:324 +msgid "Easy Digital Downloads needs to perform upgrades to your WordPress database. Your store data will be migrated to custom database tables to improve performance and efficiency. This process may take a while." +msgstr "" + +#. translators: %s: Plural label for downloads +#: includes/admin/upgrades/upgrade-functions.php:328 +msgid "Sales and earnings data for %s and customers will be updated once orders have finished migrating." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:332 +msgid "Please create a full backup of your website before proceeding." +msgstr "" + +#. translators: %s: WP-CLI command +#: includes/admin/upgrades/upgrade-functions.php:338 +msgid "This migration can also be run via WP-CLI with the following command: %s. This is the recommended method for large sites." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:349 +msgid "I have secured a backup of my website data." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:354 +msgid "Upgrade Easy Digital Downloads" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:379 +#: includes/scripts.php:559 +msgid "Migration complete" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:384 +msgid "Migration pending" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:403 +msgid "The data migration has been successfully completed. You may now leave this page or proceed to remove legacy data below." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:427 +#: includes/admin/upgrades/upgrade-functions.php:498 +#: includes/upgrades/functions.php:174 +msgid "Remove Legacy Data" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:430 +msgid "Important: This removes all legacy data from where it was previously stored in custom post types and post meta. This is an optional step that is not reversible. Please back up your database and ensure your store is operational before completing this step." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:436 +msgid "You can complete this step later by navigating to %sDownloads » Tools%s." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:447 +msgid "I have confirmed my store is operational and I have a backup of my website data." +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:455 +msgid "Permanently Remove Legacy Data" +msgstr "" + +#: includes/admin/upgrades/upgrade-functions.php:466 +msgid "Legacy data has been successfully removed. You may now leave this page." +msgstr "" + +#: includes/admin/upgrades/upgrades.php:29 +msgid "Upgrades" +msgstr "" + +#: includes/admin/upgrades/upgrades.php:92 +#: includes/admin/upgrades/upgrades.php:111 +msgid "The upgrade process has started, please be patient. This could take several minutes. You will be automatically redirected when the upgrade is finished." +msgstr "" + +#: includes/admin/upgrades/upgrades.php:95 +msgid "Step %d of approximately %d running" +msgstr "" + +#: includes/admin/upgrades/v3/class-customer-addresses.php:30 +msgid "Customer addresses migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-customer-email-addresses.php:30 +msgid "Customer email addresses migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-customer-notes.php:32 +msgid "Customer notes migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-discounts.php:30 +msgid "Discounts migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-logs.php:30 +msgid "Logs migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-order-notes.php:30 +msgid "Order notes migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-orders.php:30 +msgid "Orders migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-remove-legacy-data.php:41 +msgid "Legacy data removed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/class-tax-rates.php:30 +msgid "Tax rates migration completed successfully." +msgstr "" + +#: includes/admin/upgrades/v3/upgrade-actions.php:44 +msgid "\"%s\" is not a valid 3.0 upgrade." +msgstr "" + +#: includes/admin/upgrades/v3/upgrade-actions.php:62 +msgid "Error loading migration class." +msgstr "" + +#. Translators: %s - number of notifications +#: includes/admin/views/notifications.php:35 +msgid "(%s) New Notifications" +msgstr "" + +#: includes/admin/views/notifications.php:49 +msgid "Close panel" +msgstr "" + +#: includes/admin/views/notifications.php:85 +msgid "Dismiss" +msgstr "" + +#: includes/admin/views/notifications.php:95 +msgid "You have no new notifications." +msgstr "" + +#: includes/admin/views/notifications.php:101 +msgid "Loading notifications..." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:22 +msgctxt "Apply an adjustment to an order" +msgid "Add Adjustment" +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:30 +msgctxt "Apply a discount to an order" +msgid "Add Discount" +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:39 +#: includes/admin/views/tmpl-order-form-add-order-item.php:253 +msgid "Add %s" +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:43 +msgid "Order items cannot be modified." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:44 +msgid "Issue a refund to adjust the net total for this order." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:49 +msgid "Amazon orders must be refunded at the gateway." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:51 +msgid "The refund window for this Order has passed; however, you have the ability to override this." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:53 +msgid "The refund window for this Order has passed." +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:63 +msgid "Initialize Refund" +msgstr "" + +#: includes/admin/views/tmpl-order-actions.php:68 +msgid "Orders placed through the Amazon gateway must be refunded through Amazon. The order status can then be updated manually." +msgstr "" + +#: includes/admin/views/tmpl-order-adjustment-discount.php:27 +#: includes/discount-functions.php:1365 +msgid "Remove discount" +msgstr "" + +#: includes/admin/views/tmpl-order-adjustment-discount.php:35 +#: includes/admin/views/tmpl-order-form-add-order-discount.php:22 +#: includes/ajax-functions.php:1047 +#: includes/blocks/views/checkout/discount.php:16 +#: includes/blocks/views/orders/discounts.php:7 +#: includes/checkout/template.php:741 +#: includes/discount-functions.php:1329 +#: templates/shortcode-receipt.php:87 +msgid "Discount" +msgid_plural "Discounts" +msgstr[0] "" +msgstr[1] "" + +#: includes/admin/views/tmpl-order-adjustment.php:27 +#: includes/admin/views/tmpl-order-item.php:25 +msgid "Remove %s" +msgstr "" + +#: includes/admin/views/tmpl-order-adjustment.php:41 +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:31 +#: includes/blocks/views/orders/fees.php:8 +#: includes/blocks/views/orders/fees.php:12 +#: templates/shortcode-receipt.php:116 +#: templates/shortcode-receipt.php:120 +msgid "Fee" +msgid_plural "Fees" +msgstr[0] "" +msgstr[1] "" + +#: includes/admin/views/tmpl-order-copy-download-link.php:18 +msgid "%s Links" +msgstr "" + +#: includes/admin/views/tmpl-order-copy-download-link.php:24 +msgid "No file links available" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:46 +#: includes/ajax-functions.php:1080 +#: includes/blocks/views/orders/credits.php:8 +#: includes/blocks/views/orders/credits.php:12 +#: templates/shortcode-receipt.php:143 +#: templates/shortcode-receipt.php:147 +msgid "Credit" +msgid_plural "Credits" +msgstr[0] "" +msgstr[1] "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:54 +msgid "Apply to" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:62 +msgid "Entire order" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:114 +msgid "Apply tax to fee" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:120 +#: includes/admin/views/tmpl-order-form-add-order-item.php:148 +msgid "Tax Rate: %s" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:140 +#: includes/admin/views/tmpl-order-form-add-order-item.php:161 +msgid "No tax rate has been set." +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:141 +#: includes/admin/views/tmpl-order-form-add-order-item.php:162 +msgid "Tax rates are defined by the customer's billing address." +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-adjustment.php:145 +#: includes/admin/views/tmpl-order-form-add-order-item.php:166 +msgid "Set an address" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-discount.php:30 +msgid "Choose a discount" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-discount.php:52 +msgid "This Discount already applied to the Order." +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-item.php:80 +#: includes/admin/views/tmpl-order-form-add-order-item.php:81 +msgid "Search for a download" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-item.php:98 +msgid "This %s already exists in the Order. Please remove it before adding it again." +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-item.php:142 +msgid "Automatically calculate amounts" +msgstr "" + +#: includes/admin/views/tmpl-order-form-add-order-item.php:200 +msgctxt "add order item tax rate" +msgid "(%s)" +msgstr "" + +#: includes/admin/views/tmpl-order-item.php:45 +msgid "Tax:" +msgstr "" + +#: includes/admin/views/tmpl-order-item.php:62 +msgid "Show more details" +msgstr "" + +#: includes/admin/views/tmpl-order-no-items.php:14 +msgid "No order items" +msgstr "" + +#: includes/admin/views/tmpl-order-tax.php:46 +msgid "The tax rate has been updated to %1$s. Existing automatically calculated amounts have not been updated." +msgstr "" + +#: includes/admin/views/tmpl-order-tax.php:58 +msgid "Update Amounts" +msgstr "" + +#: includes/admin/views/tmpl-order-tax.php:63 +msgid "Dismiss this notice." +msgstr "" + +#: includes/admin/views/tmpl-order-total.php:20 +msgid "† Some amounts have been manually adjusted." +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-add.php:29 +msgid "Apply to whole country" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-add.php:36 +#: includes/admin/views/tmpl-tax-rates-table-meta.php:18 +#: includes/admin/views/tmpl-tax-rates-table-row.php:40 +msgid "Rate" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-add.php:41 +msgid "Add Rate" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-bulk-actions.php:18 +msgid "Bulk Actions" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-bulk-actions.php:23 +msgid "Apply" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-bulk-actions.php:28 +msgid "Show deactivated rates" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-meta.php:19 +#: includes/admin/views/tmpl-tax-rates-table-row.php:45 +#: templates/checkout_cart.php:13 +msgid "Actions" +msgstr "" + +#: includes/admin/views/tmpl-tax-rates-table-row-empty.php:15 +msgid "No rates found." +msgstr "" + +#: includes/ajax-functions.php:136 +#: includes/cart/actions.php:104 +msgid "Missing nonce when removing an item from the cart. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4" +msgstr "" + +#: includes/ajax-functions.php:195 +msgid "Missing nonce when adding an item to the cart. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4" +msgstr "" + +#: includes/ajax-functions.php:483 +msgid "Missing nonce when recalculating taxes. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4" +msgstr "" + +#: includes/ajax-functions.php:536 +msgid "Missing nonce when retrieving state list. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4" +msgstr "" + +#: includes/ajax-functions.php:651 +msgid "No results found" +msgstr "" + +#: includes/ajax-functions.php:693 +msgid "No categories found" +msgstr "" + +#: includes/ajax-functions.php:712 +#: includes/ajax-functions.php:850 +#: includes/class-edd-html-elements.php:381 +msgid "No users found" +msgstr "" + +#: includes/ajax-functions.php:781 +msgid "All Prices" +msgstr "" + +#: includes/ajax-functions.php:1299 +#: includes/ajax-functions.php:1306 +msgid "Unable to verify action. Please refresh the page and try again." +msgstr "" + +#: includes/ajax-functions.php:1345 +msgid "Unable to find download. Please refresh the page and try again." +msgstr "" + +#: includes/api/class-edd-api-v2.php:166 +#: includes/api/class-edd-api.php:1131 +#: includes/api/class-edd-api.php:1338 +#: includes/api/class-edd-api.php:1457 +msgid "Product %s not found!" +msgstr "" + +#: includes/api/class-edd-api-v2.php:349 +msgid "Customer not found!" +msgstr "" + +#: includes/api/class-edd-api-v2.php:354 +#: includes/api/class-edd-api.php:1079 +msgid "No customers found!" +msgstr "" + +#: includes/api/class-edd-api.php:461 +msgid "You must specify both a token and API key!" +msgstr "" + +#: includes/api/class-edd-api.php:478 +msgid "Your request could not be authenticated!" +msgstr "" + +#: includes/api/class-edd-api.php:496 +msgid "Invalid API key!" +msgstr "" + +#: includes/api/class-edd-api.php:512 +msgid "Invalid API version!" +msgstr "" + +#: includes/api/class-edd-api.php:688 +msgid "Invalid query!" +msgstr "" + +#: includes/api/class-edd-api.php:1073 +msgid "Customer %s not found!" +msgstr "" + +#: includes/api/class-edd-api.php:1239 +#: includes/api/class-edd-api.php:1354 +msgid "The end date must be later than the start date!" +msgstr "" + +#: includes/api/class-edd-api.php:1244 +#: includes/api/class-edd-api.php:1359 +msgid "Invalid or no date range specified!" +msgstr "" + +#: includes/api/class-edd-api.php:1613 +msgid "No discounts found!" +msgstr "" + +#: includes/api/class-edd-api.php:1659 +msgid "Discount %s not found!" +msgstr "" + +#: includes/api/class-edd-api.php:1730 +msgid "No download logs found!" +msgstr "" + +#: includes/api/class-edd-api.php:1986 +#: includes/user-functions.php:1107 +msgid "Downloads API Keys" +msgstr "" + +#: includes/api/class-edd-api.php:1996 +#: includes/user-functions.php:1114 +msgid "Generate API Key" +msgstr "" + +#: includes/api/class-edd-api.php:2000 +msgid "Public key:" +msgstr "" + +#: includes/api/class-edd-api.php:2003 +msgid "Secret key:" +msgstr "" + +#: includes/api/class-edd-api.php:2006 +#: includes/user-functions.php:1125 +msgid "Token:" +msgstr "" + +#: includes/api/class-edd-api.php:2010 +#: includes/user-functions.php:1131 +msgid "Revoke API Keys" +msgstr "" + +#: includes/api/class-edd-api.php:2034 +msgid "User ID Required" +msgstr "" + +#: includes/api/class-edd-api.php:2046 +#: includes/api/class-edd-api.php:2048 +msgid "You do not have permission to %s API keys for this user" +msgstr "" + +#: includes/api/v3/Notifications.php:45 +msgid "ID of the notification." +msgstr "" + +#: includes/api/v3/Notifications.php:116 +msgid "Failed to dismiss notification." +msgstr "" + +#: includes/blocks/edd-blocks.php:89 +msgid "Confirmation" +msgstr "" + +#: includes/blocks/edd-blocks.php:90 +#: includes/install.php:291 +msgid "Thank you for your purchase!" +msgstr "" + +#: includes/blocks/edd-blocks.php:93 +#: includes/emails/tags.php:245 +#: includes/emails/template.php:262 +msgid "Receipt" +msgstr "" + +#: includes/blocks/edd-blocks.php:97 +msgid "Order History" +msgstr "" + +#: includes/blocks/includes/admin/functions.php:27 +#: includes/blocks/includes/admin/settings.php:44 +msgid "Login Page" +msgstr "" + +#: includes/blocks/includes/admin/functions.php:31 +#: includes/blocks/includes/admin/settings.php:93 +msgid "Order History Page" +msgstr "" + +#: includes/blocks/includes/admin/functions.php:35 +#: includes/blocks/includes/admin/settings.php:125 +msgid "Receipt Page" +msgstr "" + +#: includes/blocks/includes/admin/functions.php:39 +#: includes/blocks/includes/admin/settings.php:102 +msgid "Confirmation Page" +msgstr "" + +#: includes/blocks/includes/admin/notices.php:28 +msgid "EDD Blocks are now a part of Easy Digital Downloads" +msgstr "" + +#: includes/blocks/includes/admin/notices.php:29 +msgid "If you are using the original Downloads block, you will need to update it to use either the new EDD Products or EDD Downloads Terms block. All other blocks functionality is automatically updated. Once you've replaced your blocks, you can deactivate the EDD Blocks plugin." +msgstr "" + +#: includes/blocks/includes/admin/recaptcha.php:27 +msgid "reCAPTCHA v3" +msgstr "" + +#. translators: 1. opening anchor tag; 2. closing anchor tag +#: includes/blocks/includes/admin/recaptcha.php:30 +msgid "%1$sRegister with Google%2$s to get reCAPTCHA v3 keys. Setting the keys here will enable reCAPTCHA on your registration block and when a user requests a password reset using the login block." +msgstr "" + +#: includes/blocks/includes/admin/recaptcha.php:39 +msgid "reCAPTCHA Site Key" +msgstr "" + +#: includes/blocks/includes/admin/recaptcha.php:46 +msgid "reCAPTCHA Secret Key" +msgstr "" + +#: includes/blocks/includes/admin/settings.php:31 +msgid "This page must include the EDD Login block. Setting this allows the front end form to be used for resetting passwords." +msgstr "" + +#. translators: 1. opening code tag, do not translate; 2. closing code tag, do not translate. +#: includes/blocks/includes/admin/settings.php:35 +msgid "Do not use this with the %1$s[edd_login]%2$s shortcode; it does not support resetting passwords." +msgstr "" + +#: includes/blocks/includes/admin/settings.php:49 +#: includes/blocks/includes/admin/settings.php:107 +#: src/Admin/Settings/Tabs/General.php:125 +#: src/Admin/Settings/Tabs/General.php:134 +#: src/Admin/Settings/Tabs/General.php:143 +#: src/Admin/Settings/Tabs/General.php:152 +#: src/Admin/Settings/Tabs/General.php:165 +msgid "Select a page" +msgstr "" + +#: includes/blocks/includes/admin/settings.php:55 +msgid "This is the checkout page where customers will complete their purchases." +msgstr "" + +#. translators: 1. opening code tag, do not translate; 2. closing code tag, do not translate. +#: includes/blocks/includes/admin/settings.php:59 +msgid "The Checkout block or %1$s[download_checkout]%2$s shortcode must be on this page." +msgstr "" + +#. translators: 1. opening code tag, do not translate; 2. closing code tag, do not translate. +#: includes/blocks/includes/admin/settings.php:71 +msgid "If a customer logs in using the EDD Login block or %1$s[edd_login]%2$s shortcode, will be redirected to this page." +msgstr "" + +#: includes/blocks/includes/admin/settings.php:76 +msgid "This can be overridden in the block settings or shortcode parameters." +msgstr "" + +#: includes/blocks/includes/admin/settings.php:83 +msgid "This page shows a complete order history for the current user, including download links." +msgstr "" + +#. translators: 1. opening code tag, do not translate; 2. closing code tag, do not translate. +#: includes/blocks/includes/admin/settings.php:87 +msgid "Either the EDD Order History block or the %1$s[purchase_history]%2$s shortcode must be on this page." +msgstr "" + +#: includes/blocks/includes/admin/settings.php:96 +msgid "This page must include the EDD Confirmation block." +msgstr "" + +#: includes/blocks/includes/admin/settings.php:98 +msgid "Use this page separately from your receipt page to ensure proper conversion tracking." +msgstr "" + +#: includes/blocks/includes/admin/settings.php:115 +msgid "This is the page to show a detailed receipt for an order." +msgstr "" + +#. translators: 1. opening code tag, do not translate; 2. closing code tag, do not translate. +#: includes/blocks/includes/admin/settings.php:119 +msgid "Use the EDD Receipt block or the %1$s[edd_receipt]%2$s shortcode to work with the confirmation page." +msgstr "" + +#: includes/blocks/includes/admin/settings.php:143 +msgid "Default Button Colors" +msgstr "" + +#: includes/blocks/includes/admin/settings.php:168 +msgid "Background" +msgstr "" + +#: includes/blocks/includes/admin/settings.php:195 +msgid "Do not allow users to redownload items from their order history." +msgstr "" + +#: includes/blocks/includes/checkout/ajax.php:39 +#: includes/blocks/includes/checkout/checkout.php:122 +#: includes/blocks/includes/checkout/checkout.php:162 +#: includes/cart/template.php:127 +#: includes/gateways/stripe/includes/compat.php:221 +msgid "Your cart is empty." +msgstr "" + +#: includes/blocks/includes/checkout/forms.php:31 +#: includes/blocks/views/checkout/purchase-form/login.php:26 +#: includes/checkout/template.php:497 +#: includes/checkout/template.php:602 +msgid "Log in" +msgstr "" + +#: includes/blocks/includes/checkout/forms.php:35 +msgid "Register for a new account" +msgstr "" + +#: includes/blocks/includes/checkout/forms.php:39 +msgid "Check out as a guest" +msgstr "" + +#: includes/blocks/includes/checkout/functions.php:42 +msgid "item" +msgid_plural "items" +msgstr[0] "" +msgstr[1] "" + +#. translators: the plurals downloads name. +#: includes/blocks/includes/downloads/downloads.php:90 +msgctxt "download post type name" +msgid "No %s found." +msgstr "" + +#: includes/blocks/includes/forms/forms.php:67 +#: includes/users/lost-password.php:244 +msgid "Your password reset link appears to be invalid. Please request a new link below." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:96 +msgid "reCAPTCHA validation missing." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:132 +msgid "There was an error validating the reCAPTCHA. Please try again." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:149 +msgid "Unexpected reCAPTCHA error. Please try again." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:156 +msgid "reCAPTCHA verification failed. Please contact a site administrator." +msgstr "" + +#: includes/blocks/includes/forms/recaptcha.php:163 +msgid "reCAPTCHA verification failed with low score. Please contact a site administrator." +msgstr "" + +#: includes/blocks/includes/orders/functions.php:30 +#: templates/history-purchases.php:74 +msgid "Complete Purchase" +msgstr "" + +#: includes/blocks/includes/orders/functions.php:38 +#: includes/blocks/includes/orders/orders.php:143 +#: templates/history-purchases.php:79 +msgid "View Details and Downloads" +msgstr "" + +#: includes/blocks/includes/orders/orders.php:111 +msgid "To view a sample confirmation screen, you need to have at least one order in your store." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:114 +msgid "Your purchase session could not be retrieved." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:165 +#: includes/shortcodes.php:668 +msgid "Sorry, trouble retrieving order receipt." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:175 +msgid "To view a sample receipt, you need to have at least one order in your store." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:220 +msgid "Sorry, you do not have permission to view this receipt." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:229 +msgid "Please confirm your email address to access your downloads." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:239 +msgid "Please log in to view your order." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:271 +msgid "Please log in to access your downloads." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:287 +#: includes/blocks/includes/orders/orders.php:299 +msgid "Your email address could not be verified." +msgstr "" + +#: includes/blocks/includes/orders/orders.php:326 +#: includes/blocks/views/orders/receipt-files.php:70 +#: includes/emails/tags.php:377 +#: templates/history-downloads.php:89 +#: templates/shortcode-receipt.php:306 +msgid "No downloadable files found." +msgstr "" + +#: includes/blocks/includes/terms/images.php:77 +#: includes/blocks/includes/terms/images.php:178 +msgid "Select Image" +msgstr "" + +#: includes/blocks/includes/terms/images.php:93 +#: includes/blocks/includes/terms/images.php:118 +msgid "Term Image" +msgstr "" + +#: includes/blocks/includes/terms/images.php:96 +msgid "Set Term Image." +msgstr "" + +#. translators: 1. name of the term +#: includes/blocks/includes/terms/images.php:132 +msgid "Set Term Image for %1$s." +msgstr "" + +#. translators: the placeholder refers to which featured image +#: includes/blocks/includes/terms/images.php:156 +msgid "%s featured image" +msgstr "" + +#: includes/blocks/includes/terms/images.php:181 +msgid "Delete Image" +msgstr "" + +#: includes/blocks/includes/terms/images.php:214 +msgid "Image" +msgstr "" + +#: includes/blocks/pro/search.php:16 +msgid "downloads found" +msgstr "" + +#: includes/blocks/views/checkout/cart/cart-item.php:23 +#: includes/blocks/views/orders/receipt-item.php:31 +msgid "Quantity:" +msgstr "" + +#: includes/blocks/views/checkout/cart/cart.php:15 +#: templates/checkout_cart.php:11 +msgid "Item Name" +msgstr "" + +#: includes/blocks/views/checkout/cart/cart.php:16 +#: templates/checkout_cart.php:12 +msgid "Item Price" +msgstr "" + +#: includes/blocks/views/checkout/discount.php:12 +msgid "Enter a discount code" +msgstr "" + +#: includes/blocks/views/checkout/discount.php:19 +msgid "Enter discount code" +msgstr "" + +#: includes/blocks/views/checkout/discount.php:20 +#: includes/checkout/template.php:746 +msgctxt "Apply discount at checkout" +msgid "Apply" +msgstr "" + +#: includes/blocks/views/checkout/logged-in.php:2 +msgid "Account Information" +msgstr "" + +#. translators: 1. The current user's email address; 2. opening anchor tag, do not translate; 3. closing anchor tag, do not translate. +#: includes/blocks/views/checkout/logged-in.php:7 +msgid "You are currently logged in as %1$s. (%2$slog out%3$s)" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/address.php:38 +#: includes/checkout/template.php:373 +msgid "Address line 1" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/address.php:49 +#: includes/checkout/template.php:383 +msgid "Address line 2" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/address.php:66 +#: includes/blocks/views/checkout/purchase-form/address.php:93 +#: includes/checkout/template.php:455 +msgid "State/Province" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/address.php:99 +msgid "ZIP/Postal Code" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:2 +#: includes/gateways/stripe/includes/template-functions.php:39 +msgid "Credit Card Info" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:11 +#: includes/checkout/template.php:249 +msgid "Secure SSL encrypted payment" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:16 +#: includes/gateways/stripe/includes/template-functions.php:60 +msgid "This is a secure SSL encrypted payment." +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:22 +#: includes/gateways/stripe/includes/template-functions.php:142 +#: includes/gateways/stripe/includes/template-functions.php:164 +msgid "Name on the Card" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:26 +#: includes/checkout/template.php:280 +#: includes/gateways/stripe/includes/template-functions.php:145 +#: includes/gateways/stripe/includes/template-functions.php:168 +msgid "Card name" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:31 +msgid "Card Number" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:36 +#: includes/checkout/template.php:259 +#: includes/checkout/template.php:264 +msgid "Card number" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:42 +#: includes/checkout/template.php:268 +#: includes/gateways/stripe/includes/template-functions.php:210 +msgid "CVC" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:46 +#: includes/checkout/template.php:272 +msgid "Security code" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/credit-card.php:52 +#: includes/checkout/template.php:285 +#: includes/gateways/stripe/includes/template-functions.php:607 +msgid "Expiration (MM/YY)" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/gateways.php:9 +msgid "Select Payment Method" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/gateways.php:51 +#: includes/checkout/template.php:947 +#: includes/gateways/stripe/includes/scripts.php:85 +#: src/Admin/Pointers.php:40 +msgid "Next" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/login.php:2 +msgid "Log Into Your Account" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/login.php:6 +#: includes/blocks/views/forms/login.php:11 +#: includes/blocks/views/forms/registration.php:13 +#: templates/shortcode-login.php:15 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +msgid "Username or Email" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/login.php:17 +#: includes/blocks/views/checkout/purchase-form/register.php:18 +#: includes/blocks/views/forms/login.php:22 +#: includes/blocks/views/forms/registration.php:35 +#: includes/checkout/template.php:518 +#: includes/checkout/template.php:524 +#: includes/checkout/template.php:591 +#: templates/shortcode-login.php:19 +#: templates/shortcode-register.php:30 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +msgid "Password" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/personal-info.php:7 +msgid "Personal Info" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/personal-info.php:12 +#: includes/blocks/views/checkout/purchase-form/personal-info.php:16 +#: includes/checkout/template.php:183 +#: includes/checkout/template.php:189 +msgid "Email address" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/personal-info.php:17 +#: includes/checkout/template.php:188 +msgid "We will send the purchase receipt to this address." +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/personal-info.php:24 +#: includes/blocks/views/checkout/purchase-form/personal-info.php:30 +#: includes/checkout/template.php:194 +#: includes/checkout/template.php:200 +msgid "First name" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/personal-info.php:31 +#: includes/checkout/template.php:199 +msgid "We will use this to personalize your account experience." +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/personal-info.php:38 +#: includes/blocks/views/checkout/purchase-form/personal-info.php:44 +#: includes/checkout/template.php:204 +#: includes/checkout/template.php:210 +msgid "Last name" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/personal-info.php:45 +#: includes/checkout/template.php:209 +msgid "We will use this as well to personalize your account experience." +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/register.php:3 +msgid "Register For a New Account" +msgstr "" + +#: includes/blocks/views/checkout/purchase-form/register.php:29 +#: includes/blocks/views/forms/registration.php:57 +#: templates/shortcode-register.php:35 +#: includes/blocks/build/register/index.js:1 +msgid "Confirm Password" +msgstr "" + +#: includes/blocks/views/downloads/content.php:18 +#: includes/template-functions.php:151 +msgid "Free" +msgstr "" + +#: includes/blocks/views/forms/login.php:33 +#: templates/shortcode-login.php:23 +#: includes/blocks/build/login/index.js:1 +msgid "Remember Me" +msgstr "" + +#: includes/blocks/views/forms/login.php:40 +#: templates/shortcode-login.php:29 +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +msgid "Log In" +msgstr "" + +#: includes/blocks/views/forms/login.php:44 +#: includes/payments/actions.php:435 +#: templates/shortcode-login.php:33 +#: includes/blocks/build/login/index.js:1 +msgid "Lost Password?" +msgstr "" + +#: includes/blocks/views/forms/lost-password.php:8 +msgid "Please enter your username or email address. You will receive an email message with instructions on how to reset your password." +msgstr "" + +#: includes/blocks/views/forms/lost-password.php:14 +msgid "Username or Email Address" +msgstr "" + +#: includes/blocks/views/forms/lost-password.php:28 +msgid "Get New Password" +msgstr "" + +#: includes/blocks/views/forms/registration.php:42 +#: includes/blocks/views/forms/reset-password.php:17 +msgid "Hide password" +msgstr "" + +#: includes/blocks/views/forms/registration.php:45 +#: includes/blocks/views/forms/reset-password.php:20 +msgid "Strength indicator" +msgstr "" + +#: includes/blocks/views/forms/registration.php:51 +#: includes/blocks/views/forms/reset-password.php:26 +msgid "Confirm use of weak password" +msgstr "" + +#: includes/blocks/views/forms/registration.php:73 +#: includes/blocks/views/forms/reset-password.php:43 +msgid "Generate Password" +msgstr "" + +#: includes/blocks/views/forms/registration.php:74 +#: includes/checkout/template.php:573 +#: templates/shortcode-register.php:46 +msgid "Register" +msgstr "" + +#: includes/blocks/views/forms/reset-password.php:7 +msgid "Enter your new password below or generate one." +msgstr "" + +#: includes/blocks/views/forms/reset-password.php:13 +msgid "New password" +msgstr "" + +#: includes/blocks/views/forms/reset-password.php:31 +msgid "Confirm new password" +msgstr "" + +#: includes/blocks/views/forms/reset-password.php:44 +msgid "Save Password" +msgstr "" + +#: includes/blocks/views/orders/guest.php:22 +msgid "Confirm Email" +msgstr "" + +#: includes/blocks/views/orders/orders.php:4 +msgid "You have no orders." +msgstr "" + +#: includes/blocks/views/orders/pending.php:5 +#: templates/account-pending.php:3 +msgid "An email with an activation link has been sent." +msgstr "" + +#. translators: 1. Opening anchor tag. 2. Closing anchor tag. +#: includes/blocks/views/orders/pending.php:13 +msgid "Your account is pending verification. Please click the link in your email to activate your account. No email? %1$sSend a new activation code.%2$s" +msgstr "" + +#: includes/blocks/views/orders/receipt-files.php:61 +#: templates/shortcode-receipt.php:297 +msgid "No downloadable files found for this bundled item." +msgstr "" + +#: includes/blocks/views/orders/receipt-item.php:25 +msgid "SKU:" +msgstr "" + +#: includes/blocks/views/orders/receipt-items.php:7 +#: templates/shortcode-receipt.php:19 +msgid "The specified receipt ID appears to be invalid." +msgstr "" + +#: includes/blocks/views/orders/totals.php:14 +#: templates/shortcode-receipt.php:42 +msgctxt "heading" +msgid "Order" +msgstr "" + +#: includes/blocks/views/orders/totals.php:19 +#: templates/shortcode-receipt.php:50 +msgid "Order Status" +msgstr "" + +#: includes/blocks/views/orders/totals.php:25 +#: templates/shortcode-receipt.php:56 +msgid "Payment Key" +msgstr "" + +#: includes/blocks/views/search.php:2 +#: includes/blocks/views/search.php:7 +msgid "Search Downloads" +msgstr "" + +#: includes/cart/class-edd-cart.php:902 +msgid "includes %s tax" +msgstr "" + +#: includes/cart/class-edd-cart.php:904 +msgid "excludes %s tax" +msgstr "" + +#: includes/cart/class-edd-cart.php:1417 +#: includes/cart/class-edd-cart.php:1478 +#: includes/error-tracking.php:84 +#: templates/shortcode-profile-editor.php:28 +msgid "Success" +msgstr "" + +#: includes/cart/class-edd-cart.php:1418 +msgid "Cart saved successfully. You can restore your cart using this URL:" +msgstr "" + +#: includes/cart/class-edd-cart.php:1452 +#: includes/cart/class-edd-cart.php:1466 +msgid "Cart restoration failed. Invalid token." +msgstr "" + +#: includes/cart/class-edd-cart.php:1460 +#: includes/cart/class-edd-cart.php:1469 +msgid "The cart cannot be restored. Invalid token." +msgstr "" + +#: includes/cart/class-edd-cart.php:1478 +msgid "Cart restored successfully." +msgstr "" + +#: includes/cart/template.php:189 +msgid "Restore Previous Cart" +msgstr "" + +#: includes/cart/template.php:191 +msgid "Save Cart" +msgstr "" + +#: includes/cart/template.php:208 +msgid "Restore Previous Cart." +msgstr "" + +#: includes/cart/template.php:225 +msgid "Update Cart" +msgstr "" + +#: includes/cart/template.php:282 +msgid "You have successfully added %s to your shopping cart." +msgstr "" + +#: includes/cart/template.php:283 +msgid "Checkout." +msgstr "" + +#: includes/checkout/template.php:179 +msgid "Personal info" +msgstr "" + +#: includes/checkout/template.php:240 +msgid "Credit card info" +msgstr "" + +#: includes/checkout/template.php:254 +msgid "This is a secure SSL encrypted payment" +msgstr "" + +#: includes/checkout/template.php:263 +msgid "The (typically) 16 digits on the front of your credit card." +msgstr "" + +#: includes/checkout/template.php:271 +msgid "The 3 digit (back) or 4 digit (front) value on your card." +msgstr "" + +#: includes/checkout/template.php:276 +msgid "Name on the card" +msgstr "" + +#: includes/checkout/template.php:279 +#: includes/gateways/stripe/includes/template-functions.php:167 +msgid "The name printed on the front of your credit card." +msgstr "" + +#: includes/checkout/template.php:288 +msgid "The date your credit card expires, typically on the front of the card." +msgstr "" + +#: includes/checkout/template.php:363 +msgid "Billing details" +msgstr "" + +#: includes/checkout/template.php:367 +msgid "Billing address" +msgstr "" + +#: includes/checkout/template.php:372 +msgid "The primary billing address for your credit card." +msgstr "" + +#: includes/checkout/template.php:377 +msgid "Billing address line 2 (optional)" +msgstr "" + +#: includes/checkout/template.php:382 +msgid "The suite, apt no, PO box, etc, associated with your billing address." +msgstr "" + +#: includes/checkout/template.php:387 +msgid "Billing city" +msgstr "" + +#: includes/checkout/template.php:392 +msgid "The city for your billing address." +msgstr "" + +#: includes/checkout/template.php:397 +msgid "Billing zip/Postal code" +msgstr "" + +#: includes/checkout/template.php:402 +#: includes/gateways/stripe/includes/template-functions.php:838 +msgid "The zip or postal code for your billing address." +msgstr "" + +#: includes/checkout/template.php:407 +msgid "Billing country" +msgstr "" + +#: includes/checkout/template.php:412 +#: includes/gateways/stripe/includes/template-functions.php:814 +msgid "The country for your billing address." +msgstr "" + +#: includes/checkout/template.php:430 +msgid "Billing state/Province" +msgstr "" + +#: includes/checkout/template.php:435 +msgid "The state or province for your billing address." +msgstr "" + +#: includes/checkout/template.php:497 +msgid "Already have an account?" +msgstr "" + +#: includes/checkout/template.php:504 +msgid "Create an account" +msgstr "" + +#: includes/checkout/template.php:504 +msgid "(optional)" +msgstr "" + +#: includes/checkout/template.php:513 +msgid "The username you will use to log into your account." +msgstr "" + +#: includes/checkout/template.php:523 +msgid "The password used to access your account." +msgstr "" + +#: includes/checkout/template.php:528 +msgid "Password again" +msgstr "" + +#: includes/checkout/template.php:533 +msgid "Confirm your password." +msgstr "" + +#: includes/checkout/template.php:534 +msgid "Confirm password" +msgstr "" + +#: includes/checkout/template.php:571 +msgid "Need to create an account?" +msgstr "" + +#: includes/checkout/template.php:573 +msgid "or checkout as a guest" +msgstr "" + +#: includes/checkout/template.php:582 +msgid "Username or email" +msgstr "" + +#: includes/checkout/template.php:587 +msgid "Your username or email address" +msgstr "" + +#: includes/checkout/template.php:596 +msgid "Your password" +msgstr "" + +#: includes/checkout/template.php:634 +msgid "Select payment method" +msgstr "" + +#: includes/checkout/template.php:737 +msgid "Have a discount code?" +msgstr "" + +#: includes/checkout/template.php:737 +msgctxt "Entering a discount code" +msgid "Click to enter it" +msgstr "" + +#: includes/checkout/template.php:743 +msgid "Enter a discount code if you have one." +msgstr "" + +#: includes/checkout/template.php:745 +#: includes/process-purchase.php:396 +#: includes/scripts.php:167 +msgid "Enter discount" +msgstr "" + +#: includes/checkout/template.php:773 +msgid "Agree to Terms?" +msgstr "" + +#: includes/checkout/template.php:794 +msgid "Show Terms" +msgstr "" + +#: includes/checkout/template.php:795 +msgid "Hide Terms" +msgstr "" + +#: includes/checkout/template.php:844 +msgid "Agree to Privacy Policy?" +msgstr "" + +#: includes/checkout/template.php:866 +msgid "Show Privacy Policy" +msgstr "" + +#: includes/checkout/template.php:868 +msgid "Hide Privacy Policy" +msgstr "" + +#: includes/checkout/template.php:902 +msgid "Purchase Total:" +msgstr "" + +#: includes/checkout/template.php:926 +msgid "Go back" +msgstr "" + +#: includes/checkout/template.php:997 +#: src/Admin/Settings/Tabs/Misc.php:93 +msgid "Free Download" +msgstr "" + +#: includes/class-easy-digital-downloads.php:265 +#: includes/class-easy-digital-downloads.php:277 +msgid "Cheatin’ huh?" +msgstr "" + +#: includes/class-edd-cache-helper.php:113 +msgid "In order for database caching to work with Easy Digital Downloads you must add _wp_session_ to the \"Ignored query stems\" option in W3 Total Cache settings here." +msgstr "" + +#: includes/class-edd-cli.php:60 +msgid "You are running EDD version: %s" +msgstr "" + +#: includes/class-edd-cli.php:61 +msgid "Test mode is: %s" +msgstr "" + +#: includes/class-edd-cli.php:61 +#: includes/class-edd-cli.php:62 +#: includes/class-edd-cli.php:63 +#: includes/class-edd-cli.php:64 +#: includes/class-edd-cli.php:70 +#: src/Admin/Settings/Tabs/Gateways.php:174 +#: src/Admin/Settings/Tabs/Misc.php:38 +#: src/Admin/Settings/Tabs/Taxes.php:37 +msgid "Enabled" +msgstr "" + +#: includes/class-edd-cli.php:61 +#: includes/class-edd-cli.php:62 +#: includes/class-edd-cli.php:63 +#: includes/class-edd-cli.php:64 +#: includes/class-edd-cli.php:70 +#: includes/gateways/paypal-standard.php:93 +msgid "Disabled" +msgstr "" + +#: includes/class-edd-cli.php:62 +msgid "AJAX is: %s" +msgstr "" + +#: includes/class-edd-cli.php:63 +msgid "Guest checkouts are: %s" +msgstr "" + +#: includes/class-edd-cli.php:64 +msgid "Symlinks are: %s" +msgstr "" + +#: includes/class-edd-cli.php:65 +msgid "Checkout page is: %s" +msgstr "" + +#: includes/class-edd-cli.php:65 +msgid "Valid" +msgstr "" + +#: includes/class-edd-cli.php:65 +msgid "Invalid" +msgstr "" + +#: includes/class-edd-cli.php:66 +msgid "Checkout URL is: %s" +msgstr "" + +#: includes/class-edd-cli.php:66 +#: includes/class-edd-cli.php:67 +#: includes/class-edd-cli.php:68 +msgid "Undefined" +msgstr "" + +#: includes/class-edd-cli.php:67 +msgid "Success URL is: %s" +msgstr "" + +#: includes/class-edd-cli.php:68 +msgid "Failure URL is: %s" +msgstr "" + +#: includes/class-edd-cli.php:69 +msgid "Downloads slug is: %s" +msgstr "" + +#: includes/class-edd-cli.php:70 +msgid "Taxes are: %s" +msgstr "" + +#: includes/class-edd-cli.php:71 +msgid "Tax rate is: %s" +msgstr "" + +#: includes/class-edd-cli.php:76 +msgid "Country: %s, State: %s, Rate: %s" +msgstr "" + +#: includes/class-edd-cli.php:119 +#: includes/class-edd-cli.php:192 +msgid "Earnings: %s" +msgstr "" + +#: includes/class-edd-cli.php:120 +#: includes/class-edd-cli.php:191 +msgid "Sales: %s" +msgstr "" + +#: includes/class-edd-cli.php:146 +msgid "No Downloads found" +msgstr "" + +#: includes/class-edd-cli.php:185 +msgid "ID: %d" +msgstr "" + +#. translators: the order item's status. +#: includes/class-edd-cli.php:186 +#: includes/class-edd-cli.php:445 +#: templates/history-downloads.php:97 +msgid "Status: %s" +msgstr "" + +#: includes/class-edd-cli.php:187 +msgid "Posted: %s" +msgstr "" + +#: includes/class-edd-cli.php:188 +msgid "Categories: %s" +msgstr "" + +#: includes/class-edd-cli.php:189 +msgid "Tags: %s" +msgstr "" + +#: includes/class-edd-cli.php:190 +msgid "Pricing: %s" +msgstr "" + +#: includes/class-edd-cli.php:194 +msgid "Slug: %s" +msgstr "" + +#: includes/class-edd-cli.php:195 +msgid "Permalink: %s" +msgstr "" + +#: includes/class-edd-cli.php:199 +msgid "Download Files:" +msgstr "" + +#: includes/class-edd-cli.php:202 +msgid "File: %s (%s)" +msgstr "" + +#: includes/class-edd-cli.php:205 +msgid "Price Assignment: %s" +msgstr "" + +#: includes/class-edd-cli.php:269 +msgid "Customer %d created successfully" +msgstr "" + +#: includes/class-edd-cli.php:271 +msgid "Failed to create customer" +msgstr "" + +#: includes/class-edd-cli.php:279 +msgid "%d customers created in %d seconds" +msgstr "" + +#: includes/class-edd-cli.php:307 +#: includes/class-edd-html-elements.php:309 +msgid "No customers found" +msgstr "" + +#: includes/class-edd-cli.php:314 +msgid "Customer User ID: %s" +msgstr "" + +#. translators: the username +#. translators: %s: User login. +#: includes/class-edd-cli.php:315 +#: includes/user-functions.php:588 +#: includes/user-functions.php:602 +#: includes/users/lost-password.php:180 +msgid "Username: %s" +msgstr "" + +#: includes/class-edd-cli.php:316 +msgid "Display Name: %s" +msgstr "" + +#: includes/class-edd-cli.php:319 +msgid "First Name: %s" +msgstr "" + +#: includes/class-edd-cli.php:323 +msgid "Last Name: %s" +msgstr "" + +#: includes/class-edd-cli.php:326 +#: includes/class-edd-cli.php:371 +msgid "Email: %s" +msgstr "" + +#: includes/class-edd-cli.php:329 +msgid "Purchases: %s" +msgstr "" + +#: includes/class-edd-cli.php:330 +msgid "Total Spent: %s" +msgstr "" + +#: includes/class-edd-cli.php:331 +msgid "Total Downloads: %s" +msgstr "" + +#: includes/class-edd-cli.php:363 +msgid "No sales found" +msgstr "" + +#: includes/class-edd-cli.php:370 +msgid "Purchase Key: %s" +msgstr "" + +#: includes/class-edd-cli.php:372 +msgid "Date: %s" +msgstr "" + +#: includes/class-edd-cli.php:373 +msgid "Subtotal: %s" +msgstr "" + +#: includes/class-edd-cli.php:374 +msgid "Tax: %s" +msgstr "" + +#: includes/class-edd-cli.php:377 +msgid "Fees:" +msgstr "" + +#: includes/class-edd-cli.php:380 +msgid " Fee: %s - %s" +msgstr "" + +#: includes/class-edd-cli.php:384 +msgid "Total: %s" +msgstr "" + +#: includes/class-edd-cli.php:386 +msgid "Gateway: %s" +msgstr "" + +#: includes/class-edd-cli.php:389 +msgid "Products:" +msgstr "" + +#: includes/class-edd-cli.php:393 +msgid " Product: %s - %s" +msgstr "" + +#: includes/class-edd-cli.php:424 +#: includes/class-edd-html-elements.php:477 +#: includes/shortcodes.php:259 +msgid "No discounts found" +msgstr "" + +#: includes/class-edd-cli.php:431 +msgid "Name: %s" +msgstr "" + +#: includes/class-edd-cli.php:432 +msgid "Code: %s" +msgstr "" + +#: includes/class-edd-cli.php:440 +msgid "Amount: %s" +msgstr "" + +#: includes/class-edd-cli.php:441 +msgid "Uses: %s" +msgstr "" + +#: includes/class-edd-cli.php:442 +msgid "Max Uses: %s" +msgstr "" + +#: includes/class-edd-cli.php:443 +msgid "Start Date: %s" +msgstr "" + +#: includes/class-edd-cli.php:443 +msgid "No Start Date" +msgstr "" + +#: includes/class-edd-cli.php:444 +msgid "Expiration Date: %s" +msgstr "" + +#: includes/class-edd-cli.php:444 +msgid "No Expiration" +msgstr "" + +#: includes/class-edd-cli.php:450 +msgid "Product Requirements:" +msgstr "" + +#: includes/class-edd-cli.php:453 +msgid " Product: %s" +msgstr "" + +#: includes/class-edd-cli.php:459 +msgid "Global Discount: %s" +msgstr "" + +#: includes/class-edd-cli.php:460 +msgid "Single Use: %s" +msgstr "" + +#: includes/class-edd-cli.php:487 +#: includes/class-edd-cli.php:2061 +msgid "No action specified, did you mean" +msgstr "" + +#: includes/class-edd-cli.php:489 +#: includes/class-edd-cli.php:2063 +msgid "Invalid action specified, did you mean" +msgstr "" + +#: includes/class-edd-cli.php:545 +msgid "Invalid status '%s', defaulting to 'complete'" +msgstr "" + +#: includes/class-edd-cli.php:579 +msgid "Specified ID is not a product" +msgstr "" + +#: includes/class-edd-cli.php:727 +msgid "Created %s orders" +msgstr "" + +#: includes/class-edd-cli.php:824 +msgid "Created %s discounts" +msgstr "" + +#: includes/class-edd-cli.php:914 +msgid "This process will remove and recreate discounts in your database. Please make sure you've backed up your EDD database tables. Are you sure you want to delete discounts that have already been migrated and run the migration again?" +msgstr "" + +#: includes/class-edd-cli.php:920 +msgid "The discounts custom database migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:942 +msgid "Migration complete: Discounts" +msgstr "" + +#: includes/class-edd-cli.php:945 +#: includes/class-edd-cli.php:1067 +#: includes/class-edd-cli.php:1540 +#: includes/class-edd-cli.php:1774 +msgid "Old Records: " +msgstr "" + +#: includes/class-edd-cli.php:946 +#: includes/class-edd-cli.php:1068 +#: includes/class-edd-cli.php:1541 +#: includes/class-edd-cli.php:1775 +msgid "New Records: " +msgstr "" + +#: includes/class-edd-cli.php:952 +msgid "No discount records found." +msgstr "" + +#: includes/class-edd-cli.php:984 +msgid "The logs custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:987 +msgid "Preparing to migrate logs (this can take several minutes)." +msgstr "" + +#: includes/class-edd-cli.php:990 +msgid "Migrating Logs" +msgstr "" + +#: includes/class-edd-cli.php:1062 +msgid "No log records found." +msgstr "" + +#: includes/class-edd-cli.php:1065 +msgid "Migration complete: Logs" +msgstr "" + +#: includes/class-edd-cli.php:1101 +msgid "The order notes custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1104 +msgid "Preparing to migrate order notes." +msgstr "" + +#: includes/class-edd-cli.php:1106 +msgid "Migrating Order Notes" +msgstr "" + +#: includes/class-edd-cli.php:1170 +msgid "No order notes found." +msgstr "" + +#: includes/class-edd-cli.php:1172 +msgid "Migration complete: Order Notes" +msgstr "" + +#: includes/class-edd-cli.php:1174 +msgid "Old order notes: " +msgstr "" + +#: includes/class-edd-cli.php:1175 +msgid "New order notes: " +msgstr "" + +#: includes/class-edd-cli.php:1207 +msgid "The customer notes custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1209 +msgid "Preparing to migrate customer notes." +msgstr "" + +#: includes/class-edd-cli.php:1211 +msgid "Migrating Customer Notes" +msgstr "" + +#: includes/class-edd-cli.php:1271 +msgid "No customer notes found." +msgstr "" + +#: includes/class-edd-cli.php:1273 +msgid "Migration complete: Customer Notes" +msgstr "" + +#: includes/class-edd-cli.php:1275 +msgid "Old customer notes: " +msgstr "" + +#: includes/class-edd-cli.php:1276 +msgid "New customer notes: " +msgstr "" + +#: includes/class-edd-cli.php:1312 +msgid "Preparing to migrate additional customer data." +msgstr "" + +#: includes/class-edd-cli.php:1337 +msgid "The user addresses custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1339 +msgid "Preparing to migrate customer address data." +msgstr "" + +#: includes/class-edd-cli.php:1340 +msgid "Migrating Customer Addresses" +msgstr "" + +#: includes/class-edd-cli.php:1411 +msgid "The user email addresses custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1413 +msgid "Preparing to migrate customer email addresses (this can take several minutes)." +msgstr "" + +#: includes/class-edd-cli.php:1414 +msgid "Migrating Customer Email Addresses" +msgstr "" + +#: includes/class-edd-cli.php:1477 +msgid "Migration complete: Customer Email Addresses" +msgstr "" + +#: includes/class-edd-cli.php:1508 +msgid "The tax rates custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1511 +msgid "Checking for default tax rate" +msgstr "" + +#: includes/class-edd-cli.php:1514 +msgid "Migrating default tax rate" +msgstr "" + +#: includes/class-edd-cli.php:1538 +msgid "Migration complete: Tax Rates" +msgstr "" + +#: includes/class-edd-cli.php:1580 +msgid "This process will remove and recreate orders in your database. Please make sure you've backed up your EDD database tables. Are you sure you want to delete orders that have already been migrated and run the migration again?" +msgstr "" + +#: includes/class-edd-cli.php:1586 +msgid "The payments custom table migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1589 +msgid "Preparing to migrate payments." +msgstr "" + +#: includes/class-edd-cli.php:1592 +msgid "Migrating Payments" +msgstr "" + +#: includes/class-edd-cli.php:1619 +msgid "An EDD Payment could not be found for that ID." +msgstr "" + +#: includes/class-edd-cli.php:1622 +msgid "The payment ID must be an integer from the post_id column." +msgstr "" + +#: includes/class-edd-cli.php:1633 +msgid "The starting ID must be an integer from the post_id column." +msgstr "" + +#: includes/class-edd-cli.php:1643 +msgid "The ending ID must be an integer from the post_id column." +msgstr "" + +#: includes/class-edd-cli.php:1650 +msgid "Are you sure you want to run a partial order migration?" +msgstr "" + +#. translators: 1. the refund order ID; 2. the original payment ID. +#: includes/class-edd-cli.php:1692 +msgid "%1$d is a refund order. EDD will delete the refund and migrate payment %1$d, then re-migrate payment %2$d." +msgstr "" + +#. translators: 1. the order/payment ID. +#: includes/class-edd-cli.php:1702 +msgid "Order ID %1$d appears to be a different record from Payment ID %1$d. Are you sure you want to destroy this order and overwrite it?" +msgstr "" + +#. translators: payment ID. +#: includes/class-edd-cli.php:1737 +msgid "Migration failed for payment %d." +msgstr "" + +#: includes/class-edd-cli.php:1760 +msgid "No payment records found." +msgstr "" + +#: includes/class-edd-cli.php:1767 +msgid "Partial order migration complete. Orders Processed: " +msgstr "" + +#: includes/class-edd-cli.php:1768 +msgid "To recalculate all download sales and earnings, run `wp edd recalculate_download_sales_earnings`." +msgstr "" + +#: includes/class-edd-cli.php:1769 +msgid "To recalculate all customer sales and earnings, run `wp edd recalculate_customer_values`." +msgstr "" + +#: includes/class-edd-cli.php:1771 +msgid "Migration complete: Orders" +msgstr "" + +#: includes/class-edd-cli.php:1778 +msgid "Refund Records Created: " +msgstr "" + +#: includes/class-edd-cli.php:1800 +msgid "You must enter a payment ID to display legacy data." +msgstr "" + +#: includes/class-edd-cli.php:1815 +msgid "The legacy payment data could not be found." +msgstr "" + +#: includes/class-edd-cli.php:1858 +msgid "Sales and Earnings successfully recalculated for all downloads." +msgstr "" + +#: includes/class-edd-cli.php:1859 +msgid "Downloads Updated: " +msgstr "" + +#: includes/class-edd-cli.php:1885 +msgid "Sales and Earnings successfully recalculated for all customers." +msgstr "" + +#: includes/class-edd-cli.php:1886 +msgid "Customers Updated: " +msgstr "" + +#: includes/class-edd-cli.php:1904 +msgid "Do you want to remove legacy data? This will permanently remove legacy discounts, logs, and order notes." +msgstr "" + +#: includes/class-edd-cli.php:1912 +msgid "Legacy discounts have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1914 +msgid "Removing old discount data." +msgstr "" + +#: includes/class-edd-cli.php:1935 +msgid "Legacy logs have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1937 +msgid "Removing old logs." +msgstr "" + +#: includes/class-edd-cli.php:1958 +msgid "Legacy order notes have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:1960 +msgid "Removing old order notes." +msgstr "" + +#: includes/class-edd-cli.php:1984 +msgid "Updating customers database table." +msgstr "" + +#: includes/class-edd-cli.php:1987 +msgid "Removing Payment IDs column." +msgstr "" + +#: includes/class-edd-cli.php:1993 +msgid "Removing notes column." +msgstr "" + +#: includes/class-edd-cli.php:2003 +msgid "Legacy customer emails have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2005 +msgid "Removing old customer emails." +msgstr "" + +#: includes/class-edd-cli.php:2016 +msgid "Legacy customer addresses have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2018 +msgid "Removing old customer addresses." +msgstr "" + +#: includes/class-edd-cli.php:2029 +msgid "Legacy orders have already been removed. To run this anyway, use the --force argument." +msgstr "" + +#: includes/class-edd-cli.php:2031 +msgid "Removing old orders." +msgstr "" + +#: includes/class-edd-cron.php:46 +msgid "Once Weekly" +msgstr "" + +#: includes/class-edd-discount.php:358 +#: includes/class-edd-download.php:189 +msgid "Can't get property %s" +msgstr "" + +#: includes/class-edd-discount.php:675 +msgid "Draft" +msgstr "" + +#: includes/class-edd-discount.php:678 +#: includes/gateways/stripe/includes/template-functions.php:379 +#: includes/misc-functions.php:1457 +msgid "Expired" +msgstr "" + +#: includes/class-edd-discount.php:1284 +msgctxt "error shown when attempting to use a discount before its start date" +msgid "This discount is invalid." +msgstr "" + +#: includes/class-edd-discount.php:1351 +msgid "This discount has reached its maximum usage." +msgstr "" + +#: includes/class-edd-discount.php:1385 +msgid "Minimum order of %s not met." +msgstr "" + +#: includes/class-edd-discount.php:1460 +msgid "The product requirements for this discount are not met." +msgstr "" + +#: includes/class-edd-discount.php:1473 +msgid "This discount is not valid for the cart contents." +msgstr "" + +#: includes/class-edd-discount.php:1568 +msgid "This discount has already been redeemed." +msgstr "" + +#: includes/class-edd-discount.php:1617 +#: includes/discount-functions.php:933 +msgctxt "error for when a discount is invalid based on its configuration" +msgid "This discount is invalid." +msgstr "" + +#: includes/class-edd-discount.php:1650 +msgid "This discount is expired." +msgstr "" + +#: includes/class-edd-discount.php:1655 +msgid "This discount is not active." +msgstr "" + +#: includes/class-edd-download.php:209 +msgid "New Download Product" +msgstr "" + +#. translators: singular download label +#: includes/class-edd-html-elements.php:45 +#: includes/scripts.php:522 +msgid "Choose a %s" +msgstr "" + +#: includes/class-edd-html-elements.php:105 +#: src/Downloads/Search.php:119 +msgid "All Price Options" +msgstr "" + +#: includes/class-edd-html-elements.php:285 +msgid "Choose a Customer" +msgstr "" + +#: includes/class-edd-html-elements.php:289 +msgid "Search Customers" +msgstr "" + +#: includes/class-edd-html-elements.php:291 +msgid "No customer attached" +msgstr "" + +#: includes/class-edd-html-elements.php:359 +msgid "Select a User" +msgstr "" + +#: includes/class-edd-html-elements.php:363 +msgid "Search Users" +msgstr "" + +#: includes/class-edd-html-elements.php:439 +msgid "Choose a Discount" +msgstr "" + +#: includes/class-edd-html-elements.php:440 +#: includes/post-types.php:123 +msgid "All Discounts" +msgstr "" + +#: includes/class-edd-html-elements.php:619 +msgid "Choose a Country" +msgstr "" + +#: includes/class-edd-html-elements.php:620 +msgid "All Countries" +msgstr "" + +#: includes/class-edd-html-elements.php:657 +msgid "Choose a Region" +msgstr "" + +#: includes/class-edd-html-elements.php:690 +msgctxt "all dropdown items" +msgid "All" +msgstr "" + +#: includes/class-edd-html-elements.php:691 +msgctxt "no dropdown items" +msgid "None" +msgstr "" + +#: includes/class-edd-html-elements.php:989 +msgid "Enter Username" +msgstr "" + +#: includes/class-edd-html-elements.php:1019 +#: includes/blocks/build/register/index.js:1 +msgid "Required" +msgstr "" + +#. translators: 1. opening anchor tag; 2. closing anchor tag +#: includes/class-edd-license-handler.php:300 +#: src/Extensions/Handler.php:256 +msgid "You have invalid or expired license keys for Easy Digital Downloads. %1$sActivate License(s)%2$s" +msgstr "" + +#: includes/class-edd-license-handler.php:322 +#: src/Extensions/Handler.php:275 +msgid "Enter valid license key for automatic updates." +msgstr "" + +#: includes/class-edd-register-meta.php:75 +msgid "The total earnings for the specified product" +msgstr "" + +#: includes/class-edd-register-meta.php:86 +msgid "The number of sales for the specified product." +msgstr "" + +#: includes/class-edd-register-meta.php:97 +msgid "The price of the product." +msgstr "" + +#: includes/class-edd-register-meta.php:114 +msgid "An array of variable prices for the product." +msgstr "" + +#: includes/class-edd-register-meta.php:146 +msgid "The files associated with the product, available for download." +msgstr "" + +#: includes/class-edd-register-meta.php:158 +msgid "An array of product IDs to associate with a bundle." +msgstr "" + +#: includes/class-edd-register-meta.php:177 +msgid "Defines how this product's 'Purchase' button should behave, either add to cart or buy now" +msgstr "" + +#: includes/class-edd-register-meta.php:189 +msgid "When variable pricing is enabled, this value defines which option should be chosen by default." +msgstr "" + +#: includes/class-edd-register-meta.php:210 +msgid "The email address associated with the purchase." +msgstr "" + +#: includes/class-edd-register-meta.php:221 +msgid "The Customer ID associated with the payment." +msgstr "" + +#: includes/class-edd-register-meta.php:232 +msgid "The User ID associated with the payment." +msgstr "" + +#: includes/class-edd-register-meta.php:242 +msgid "The IP address the payment was made from." +msgstr "" + +#: includes/class-edd-register-meta.php:252 +msgid "The unique purchase key for this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:262 +msgid "The purchase total for this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:272 +msgid "Identifies if the purchase was made in Test or Live mode." +msgstr "" + +#: includes/class-edd-register-meta.php:282 +msgid "The registered gateway that was used to process this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:292 +msgid "Array of payment meta that contains cart details, downloads, amounts, taxes, discounts, and subtotals, etc." +msgstr "" + +#: includes/class-edd-register-meta.php:302 +msgid "The total amount of tax paid for this payment." +msgstr "" + +#: includes/class-edd-register-meta.php:312 +msgid "The date this payment was changed to the `completed` status." +msgstr "" + +#: includes/class-edd-requirements-check.php:162 +msgid "This plugin is not fully active." +msgstr "" + +#: includes/class-edd-requirements-check.php:172 +msgid "Requires %1$s (%2$s), but (%3$s) is installed." +msgstr "" + +#: includes/class-edd-requirements-check.php:182 +msgid "Requires %1$s (%2$s), but it appears to be missing." +msgstr "" + +#: includes/class-edd-requirements-check.php:192 +msgid "Requirements" +msgstr "" + +#: includes/class-edd-requirements-check.php:202 +msgid "Easy Digital Download Requirements" +msgstr "" + +#: includes/class-edd-roles.php:43 +msgid "Shop Manager" +msgstr "" + +#: includes/class-edd-roles.php:74 +msgid "Shop Accountant" +msgstr "" + +#: includes/class-edd-roles.php:80 +msgid "Shop Worker" +msgstr "" + +#: includes/class-edd-roles.php:87 +msgid "Shop Vendor" +msgstr "" + +#: includes/class-edd-stats.php:80 +#: includes/reports/reports-functions.php:496 +msgid "Yesterday" +msgstr "" + +#: includes/class-edd-stats.php:81 +#: includes/reports/reports-functions.php:497 +msgid "This Week" +msgstr "" + +#: includes/class-edd-stats.php:82 +#: includes/reports/reports-functions.php:498 +msgid "Last Week" +msgstr "" + +#: includes/class-edd-stats.php:83 +msgid "This Month" +msgstr "" + +#: includes/class-edd-stats.php:85 +msgid "This Quarter" +msgstr "" + +#: includes/class-edd-stats.php:86 +#: includes/reports/reports-functions.php:503 +msgid "Last Quarter" +msgstr "" + +#: includes/class-edd-stats.php:87 +msgid "This Year" +msgstr "" + +#: includes/class-edd-stats.php:88 +#: includes/reports/reports-functions.php:505 +msgid "Last Year" +msgstr "" + +#: includes/class-edd-stats.php:428 +msgid "Improper date provided." +msgstr "" + +#: includes/class-stats.php:459 +msgid "Sunday" +msgstr "" + +#: includes/class-stats.php:460 +msgid "Monday" +msgstr "" + +#: includes/class-stats.php:461 +msgid "Tuesday" +msgstr "" + +#: includes/class-stats.php:462 +msgid "Wednesday" +msgstr "" + +#: includes/class-stats.php:463 +msgid "Thursday" +msgstr "" + +#: includes/class-stats.php:464 +msgid "Friday" +msgstr "" + +#: includes/class-stats.php:465 +msgid "Saturday" +msgstr "" + +#: includes/class-stats.php:3206 +#: includes/emails/email-summary/class-edd-email-summary.php:329 +msgid "No Change" +msgstr "" + +#: includes/class-stats.php:3216 +#: includes/emails/email-summary/class-edd-email-summary.php:327 +msgid "No data to compare" +msgstr "" + +#: includes/compat/class-customer.php:194 +#: includes/compat/class-discount.php:121 +#: includes/compat/class-discount.php:171 +#: includes/compat/class-log.php:67 +#: includes/compat/class-log.php:215 +#: includes/compat/class-payment.php:94 +#: includes/compat/class-payment.php:134 +msgid "This function is not meant to be called directly. It is only here for backwards compatibility purposes." +msgstr "" + +#: includes/compat/class-discount.php:77 +msgid "As of Easy Digital Downloads 3.0, discounts no longer exist in the %1$s table. They have been migrated to %2$s. Discounts should be accessed using %3$s, %4$s or instantiating a new instance of %5$s. See %6$s for more information." +msgstr "" + +#: includes/compat/class-log.php:275 +msgid "All log postmeta has been deprecated since Easy Digital Downloads 3.0! Use edd_get_api_request_log() instead." +msgstr "" + +#: includes/compat/class-payment.php:108 +msgid "As of Easy Digital Downloads 3.0, orders no longer exist in the %1$s table. They have been migrated to %2$s. Orders should be accessed using %3$s or %4$s. See %5$s for more information." +msgstr "" + +#: includes/compat/class-template.php:109 +msgid "Easy Digital Downloads failed to automatically update your purchase receipt template. This update is necessary for the purchase receipt to display correctly." +msgstr "" + +#: includes/compat/class-template.php:110 +msgid "This update must be completed manually. Please click %shere%s for more information." +msgstr "" + +#: includes/compat/class-template.php:111 +msgid "The file that needs to be updated is located at:" +msgstr "" + +#: includes/currency/functions.php:22 +msgid "US Dollars ($)" +msgstr "" + +#: includes/currency/functions.php:23 +msgid "Euros (€)" +msgstr "" + +#: includes/currency/functions.php:24 +msgid "Pound Sterling (£)" +msgstr "" + +#: includes/currency/functions.php:25 +msgid "Australian Dollars ($)" +msgstr "" + +#: includes/currency/functions.php:26 +msgid "Brazilian Real (R$)" +msgstr "" + +#: includes/currency/functions.php:27 +msgid "Canadian Dollars ($)" +msgstr "" + +#: includes/currency/functions.php:28 +msgid "Czech Koruna" +msgstr "" + +#: includes/currency/functions.php:29 +msgid "Danish Krone" +msgstr "" + +#: includes/currency/functions.php:30 +msgid "Hong Kong Dollar ($)" +msgstr "" + +#: includes/currency/functions.php:31 +msgid "Hungarian Forint" +msgstr "" + +#: includes/currency/functions.php:32 +msgid "Israeli Shekel (₪)" +msgstr "" + +#: includes/currency/functions.php:33 +msgid "Japanese Yen (¥)" +msgstr "" + +#: includes/currency/functions.php:34 +msgid "Malaysian Ringgits" +msgstr "" + +#: includes/currency/functions.php:35 +msgid "Mexican Peso ($)" +msgstr "" + +#: includes/currency/functions.php:36 +msgid "New Zealand Dollar ($)" +msgstr "" + +#: includes/currency/functions.php:37 +msgid "Norwegian Krone" +msgstr "" + +#: includes/currency/functions.php:38 +msgid "Philippine Pesos" +msgstr "" + +#: includes/currency/functions.php:39 +msgid "Polish Zloty" +msgstr "" + +#: includes/currency/functions.php:40 +msgid "Singapore Dollar ($)" +msgstr "" + +#: includes/currency/functions.php:41 +msgid "Swedish Krona" +msgstr "" + +#: includes/currency/functions.php:42 +msgid "Swiss Franc" +msgstr "" + +#: includes/currency/functions.php:43 +msgid "Taiwan New Dollars" +msgstr "" + +#: includes/currency/functions.php:44 +msgid "Thai Baht (฿)" +msgstr "" + +#: includes/currency/functions.php:45 +msgid "Indian Rupee (₹)" +msgstr "" + +#: includes/currency/functions.php:46 +msgid "Turkish Lira (₺)" +msgstr "" + +#: includes/currency/functions.php:47 +msgid "Iranian Rial (﷼)" +msgstr "" + +#: includes/currency/functions.php:48 +msgid "Russian Rubles" +msgstr "" + +#: includes/currency/functions.php:49 +msgid "Angolan Kwanza" +msgstr "" + +#: includes/customer-functions.php:292 +#: includes/payments/class-payments-query.php:626 +msgid "Do not use -1 to retrieve all results." +msgstr "" + +#: includes/deprecated-functions.php:187 +msgid "You have already purchased this item, but you may purchase it again." +msgstr "" + +#: includes/deprecated-functions.php:268 +msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" +msgstr "" + +#: includes/deprecated-functions.php:269 +#: includes/deprecated-functions.php:302 +msgid "A list of download links for each download purchased" +msgstr "" + +#: includes/deprecated-functions.php:270 +#: includes/deprecated-functions.php:303 +msgid "A plain-text list of download URLs for each download purchased" +msgstr "" + +#: includes/deprecated-functions.php:271 +#: includes/deprecated-functions.php:304 +msgid "The buyer's first name" +msgstr "" + +#: includes/deprecated-functions.php:272 +#: includes/deprecated-functions.php:305 +msgid "The buyer's full name, first and last" +msgstr "" + +#: includes/deprecated-functions.php:273 +#: includes/deprecated-functions.php:306 +msgid "The buyer's user name on the site, if they registered an account" +msgstr "" + +#: includes/deprecated-functions.php:274 +#: includes/deprecated-functions.php:307 +msgid "The buyer's email address" +msgstr "" + +#: includes/deprecated-functions.php:275 +#: includes/deprecated-functions.php:308 +msgid "The buyer's billing address" +msgstr "" + +#: includes/deprecated-functions.php:276 +#: includes/deprecated-functions.php:309 +msgid "The date of the purchase" +msgstr "" + +#: includes/deprecated-functions.php:277 +#: includes/deprecated-functions.php:310 +msgid "The price of the purchase before taxes" +msgstr "" + +#: includes/deprecated-functions.php:278 +#: includes/deprecated-functions.php:311 +#: includes/emails/tags.php:210 +msgid "The taxed amount of the purchase" +msgstr "" + +#: includes/deprecated-functions.php:279 +#: includes/deprecated-functions.php:312 +#: includes/emails/tags.php:216 +msgid "The total price of the purchase" +msgstr "" + +#: includes/deprecated-functions.php:280 +#: includes/deprecated-functions.php:313 +msgid "The unique ID number for this purchase" +msgstr "" + +#: includes/deprecated-functions.php:281 +#: includes/deprecated-functions.php:314 +msgid "The unique ID number for this purchase receipt" +msgstr "" + +#: includes/deprecated-functions.php:282 +#: includes/deprecated-functions.php:315 +msgid "The method of payment used for this purchase" +msgstr "" + +#: includes/deprecated-functions.php:283 +#: includes/deprecated-functions.php:316 +msgid "Your site name" +msgstr "" + +#: includes/deprecated-functions.php:284 +msgid "Adds a link so users can view their receipt directly on your website if they are unable to view it in the browser correctly." +msgstr "" + +#: includes/deprecated-functions.php:301 +msgid "Enter the email that is sent to sale notification emails after completion of a purchase. HTML is accepted. Available template tags:" +msgstr "" + +#: includes/deprecated-functions.php:488 +#: includes/process-download.php:914 +msgid "Sorry but you have hit your download limit for this file." +msgstr "" + +#: includes/deprecated-functions.php:505 +#: includes/download-functions.php:1520 +msgid "Sorry but your download link has expired." +msgstr "" + +#: includes/deprecated-functions.php:513 +msgid "No payments matching your request were found." +msgstr "" + +#: includes/deprecated-functions.php:915 +msgid "You do not have permission to access this report" +msgstr "" + +#: includes/deprecated-functions.php:1156 +msgid "Status changed from %s to %s" +msgstr "" + +#: includes/deprecated-functions.php:1194 +msgid "Refund Payment in PayPal" +msgstr "" + +#: includes/deprecated-functions.php:1285 +msgid "Disconnect Jilt" +msgstr "" + +#. Translators: %1$s - tag, %2$s - tag +#: includes/deprecated-functions.php:1293 +msgid "%1$sClick here%2$s to visit your Jilt dashboard" +msgstr "" + +#: includes/deprecated-functions.php:1305 +msgid "Connect to Jilt" +msgstr "" + +#: includes/deprecated-functions.php:1315 +msgid "Install Jilt" +msgstr "" + +#: includes/deprecated-functions.php:1336 +#: includes/deprecated-functions.php:1415 +#: includes/deprecated-functions.php:1444 +#: includes/deprecated-functions.php:1590 +#: includes/deprecated-functions.php:1666 +#: includes/emails/functions.php:294 +msgid "You do not have permission to do this." +msgstr "" + +#: includes/deprecated-functions.php:1394 +#: includes/deprecated-functions.php:1423 +msgid "Something went wrong. Jilt was not installed correctly." +msgstr "" + +#. Translators: %1$s - tag, %2$s - tag, %3$s - tag, %4$s - tag +#: includes/deprecated-functions.php:1485 +msgid "%1$sRecover abandoned purchases like this one.%2$s %3$sTry Jilt for free%4$s." +msgstr "" + +#. Translators: %1$s - Opening anchor tag, %2$s - The url to dismiss the ajax notice, %3$s - Complete the opening of the anchor tag, %4$s - Open span tag, %4$s - Close span tag +#: includes/deprecated-functions.php:1498 +#: includes/emails/functions.php:386 +msgid "%1$s %2$s %3$s %4$s Dismiss this notice. %5$s" +msgstr "" + +#: includes/deprecated-functions.php:1535 +msgid "Access your SendWP account" +msgstr "" + +#: includes/deprecated-functions.php:1539 +msgid "Note: Email sending is currently disabled. Product Reviews extension." +msgstr "" + +#: includes/discount-functions.php:422 +msgid "%1$s is deprecated since Easy Digital Downloads version %2$s! Use %3$s instead." +msgstr "" + +#: includes/download-functions.php:605 +msgid "Single Product" +msgstr "" + +#: includes/download-functions.php:606 +msgid "Bundle" +msgstr "" + +#. translators: the plugin name. +#: includes/EDD_SL_Plugin_Updater.php:224 +#: src/Extensions/Updater.php:243 +msgid "There is a new version of %1$s available." +msgstr "" + +#: includes/EDD_SL_Plugin_Updater.php:230 +#: src/Extensions/Updater.php:249 +msgid "Contact your network administrator to install the update." +msgstr "" + +#. translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. +#: includes/EDD_SL_Plugin_Updater.php:235 +#: src/Extensions/Updater.php:254 +msgid "%1$sView version %2$s details%3$s." +msgstr "" + +#: includes/EDD_SL_Plugin_Updater.php:243 +#: src/Extensions/Updater.php:262 +msgid "%1$sView version %2$s details%3$s or %4$supdate now%5$s." +msgstr "" + +#: includes/EDD_SL_Plugin_Updater.php:254 +#: src/Extensions/Updater.php:273 +msgid "Update now." +msgstr "" + +#: includes/EDD_SL_Plugin_Updater.php:486 +#: src/Extensions/Updater.php:480 +msgid "You do not have permission to install plugin updates" +msgstr "" + +#: includes/emails/class-edd-emails.php:174 +msgid "Default Template" +msgstr "" + +#: includes/emails/class-edd-emails.php:175 +msgid "No template, plain text only" +msgstr "" + +#: includes/emails/class-edd-emails.php:293 +msgid "You cannot send email with EDD_Emails until init/admin_init has been reached" +msgstr "" + +#: includes/emails/class-edd-emails.php:320 +msgid "" +"Email from Easy Digital Downloads failed to send.\n" +"Send time: %s\n" +"To: %s\n" +"Subject: %s\n" +"\n" +"" +msgstr "" + +#. Translators: Site domain name +#: includes/emails/email-summary/class-edd-email-summary.php:88 +msgid "Easy Digital Downloads Summary - %s" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:120 +msgid "Missing email recipients for Email Summary" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:372 +msgid "month" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:372 +msgid "week" +msgstr "" + +#. Translators: period name (e.g. week) +#: includes/emails/email-summary/class-edd-email-summary.php:374 +msgid "vs previous %s" +msgstr "" + +#: includes/emails/email-summary/class-edd-email-summary.php:402 +msgid "Email body for Email Summary was empty." +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:165 +msgid "Store performance summary" +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:189 +msgid "Your eCommerce Summary" +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:201 +msgid "Hey there!" +msgstr "" + +#. Translators: period name (e.g. week) +#: includes/emails/email-summary/edd-email-summary-template.php:208 +msgid "Below is a look at how your store performed in the last %s." +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:224 +#: includes/emails/email-summary/edd-email-summary-template.php:341 +msgid "Gross Revenue" +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:304 +msgid "Average Order" +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:335 +msgid "Top 5 Products by Revenue" +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:366 +msgid "View Full Report" +msgstr "" + +#: includes/emails/email-summary/edd-email-summary-template.php:390 +msgid "Pro-tip from our expert" +msgstr "" + +#: includes/emails/functions.php:45 +#: includes/emails/functions.php:49 +#: includes/emails/functions.php:89 +#: includes/emails/functions.php:93 +#: includes/emails/template.php:142 +#: src/Admin/Settings/Tabs/Emails.php:87 +#: src/Admin/Settings/Tabs/Emails.php:94 +#: src/Admin/Settings/Tabs/Emails.php:98 +msgid "Purchase Receipt" +msgstr "" + +#: includes/emails/functions.php:138 +msgid "New download purchase - Order #%1$s" +msgstr "" + +#: includes/emails/functions.php:142 +#: src/Admin/Settings/Tabs/Emails.php:117 +msgid "New Sale!" +msgstr "" + +#: includes/emails/functions.php:202 +#: includes/emails/template.php:209 +msgid "Hello" +msgstr "" + +#: includes/emails/functions.php:202 +#: includes/emails/template.php:209 +msgid "A %s purchase has been made" +msgstr "" + +#: includes/emails/functions.php:203 +#: includes/emails/template.php:210 +msgid "%s sold:" +msgstr "" + +#: includes/emails/functions.php:205 +#: includes/emails/template.php:212 +msgid "Purchased by: " +msgstr "" + +#: includes/emails/functions.php:206 +#: includes/emails/template.php:213 +msgid "Amount: " +msgstr "" + +#: includes/emails/functions.php:207 +#: includes/emails/template.php:214 +msgid "Payment Method: " +msgstr "" + +#: includes/emails/functions.php:208 +#: includes/emails/template.php:215 +msgid "Thank you" +msgstr "" + +#: includes/emails/functions.php:345 +msgid "Something went wrong. Recapture for EDD was not installed correctly." +msgstr "" + +#. Translators: %1$s - tag, %2$s - tag, %3$s - tag, %4$s - tag +#: includes/emails/functions.php:373 +msgid "%1$sRecover abandoned purchases like this one.%2$s %3$sTry Recapture for free%4$s." +msgstr "" + +#: includes/emails/tags-inserter.php:75 +#: src/Admin/Onboarding/Steps/ConfigureEmails.php:58 +msgid "Insert Marker" +msgstr "" + +#: includes/emails/tags-inserter.php:124 +msgid "Find a tag..." +msgstr "" + +#: includes/emails/tags.php:153 +msgid "Download List" +msgstr "" + +#: includes/emails/tags.php:154 +msgid "A list of download links for each download purchased." +msgstr "" + +#: includes/emails/tags.php:161 +msgid "File URLs" +msgstr "" + +#: includes/emails/tags.php:162 +msgid "A plain-text list of download URLs for each download purchased." +msgstr "" + +#: includes/emails/tags.php:168 +msgid "The buyer's first name." +msgstr "" + +#: includes/emails/tags.php:173 +msgid "Full Name" +msgstr "" + +#: includes/emails/tags.php:174 +msgid "The buyer's full name: first and last." +msgstr "" + +#: includes/emails/tags.php:180 +msgid "The buyer's user name on the site, if they registered an account." +msgstr "" + +#: includes/emails/tags.php:192 +msgid "The buyer's billing address." +msgstr "" + +#: includes/emails/tags.php:197 +msgid "Purchase Date" +msgstr "" + +#: includes/emails/tags.php:198 +msgid "The date of the purchase." +msgstr "" + +#: includes/emails/tags.php:204 +msgid "The price of the purchase before taxes." +msgstr "" + +#: includes/emails/tags.php:221 +msgid "Payment ID" +msgstr "" + +#: includes/emails/tags.php:222 +msgid "The unique identifier for this purchase." +msgstr "" + +#: includes/emails/tags.php:227 +msgid "Receipt ID" +msgstr "" + +#: includes/emails/tags.php:228 +msgid "The unique identifier for the receipt of this purchase." +msgstr "" + +#: includes/emails/tags.php:234 +msgid "The method of payment used for this purchase." +msgstr "" + +#: includes/emails/tags.php:239 +msgid "Site Name" +msgstr "" + +#: includes/emails/tags.php:240 +msgid "Your site name." +msgstr "" + +#: includes/emails/tags.php:246 +msgid "Links to the EDD success page with the text \"View Receipt\"." +msgstr "" + +#: includes/emails/tags.php:251 +msgid "Receipt Link" +msgstr "" + +#: includes/emails/tags.php:252 +msgid "Adds a link so users can view their receipt directly on a simplified page on your site if they are unable to view it in the browser correctly." +msgstr "" + +#: includes/emails/tags.php:257 +msgid "Discount Codes" +msgstr "" + +#: includes/emails/tags.php:258 +msgid "Adds a list of any discount codes applied to this purchase." +msgstr "" + +#: includes/emails/tags.php:264 +msgid "The buyer's IP Address." +msgstr "" + +#: includes/emails/tags.php:407 +msgid "Additional information about your purchase:" +msgstr "" + +#: includes/emails/tags.php:801 +msgid "%1$sView it in your browser %2$s" +msgstr "" + +#: includes/emails/template.php:54 +msgid "Sample Product Title" +msgstr "" + +#: includes/emails/template.php:56 +msgid "Sample Download File Name" +msgstr "" + +#: includes/emails/template.php:56 +msgid "Optional notes about this download." +msgstr "" + +#: includes/emails/template.php:69 +msgid "These are some sample notes added to a product." +msgstr "" + +#: includes/emails/template.php:114 +msgid "Preview Purchase Receipt" +msgstr "" + +#: includes/emails/template.php:115 +#: src/Admin/Settings/Tabs/Emails.php:205 +msgid "Send Test Email" +msgstr "" + +#: includes/emails/template.php:160 +#: src/Admin/Settings/Tabs/Emails.php:101 +msgid "Dear" +msgstr "" + +#: includes/emails/template.php:161 +#: src/Admin/Settings/Tabs/Emails.php:101 +msgid "Thank you for your purchase. Please click on the link(s) below to download your files." +msgstr "" + +#: includes/emails/template.php:241 +msgid "Missing purchase key." +msgstr "" + +#: includes/formatting.php:327 +msgid "Billing" +msgstr "" + +#: includes/gateways/actions.php:37 +#: includes/gateways/stripe/includes/payment-methods/payment-request/checkout.php:321 +msgid "Missing nonce when loading the gateway fields. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4" +msgstr "" + +#: includes/gateways/actions.php:71 +msgid "You must enable a payment gateway to use Easy Digital Downloads" +msgstr "" + +#: includes/gateways/actions.php:73 +msgid "Your order cannot be completed at this time. Please try again or contact site support." +msgstr "" + +#: includes/gateways/amazon-payments.php:197 +msgid "There is an error with the Amazon Payments configuration." +msgstr "" + +#: includes/gateways/amazon-payments.php:267 +#: includes/gateways/amazon-payments.php:268 +msgid "Amazon" +msgstr "" + +#: includes/gateways/amazon-payments.php:317 +msgid "Amazon Payments" +msgstr "" + +#: includes/gateways/amazon-payments.php:333 +msgid "Register with Amazon" +msgstr "" + +#: includes/gateways/amazon-payments.php:335 +msgid "Connect Easy Digital Downloads to Amazon" +msgstr "" + +#: includes/gateways/amazon-payments.php:338 +msgid "Once registration is complete, enter your API credentials below." +msgstr "" + +#: includes/gateways/amazon-payments.php:344 +msgid "Seller ID" +msgstr "" + +#: includes/gateways/amazon-payments.php:345 +msgid "Found in the Integration settings. Also called a Merchant ID" +msgstr "" + +#: includes/gateways/amazon-payments.php:351 +msgid "MWS Access Key" +msgstr "" + +#: includes/gateways/amazon-payments.php:352 +#: includes/gateways/amazon-payments.php:359 +msgid "Found on Seller Central in the MWS Keys section" +msgstr "" + +#: includes/gateways/amazon-payments.php:358 +msgid "MWS Secret Key" +msgstr "" + +#: includes/gateways/amazon-payments.php:365 +msgid "Client ID" +msgstr "" + +#: includes/gateways/amazon-payments.php:366 +msgid "The Amazon Client ID. Should look like `amzn1.application-oa2...`" +msgstr "" + +#: includes/gateways/amazon-payments.php:372 +msgid "Amazon MWS Callback URL" +msgstr "" + +#: includes/gateways/amazon-payments.php:373 +msgid "The Return URL to provide in your MWS Application. Enter this under your Login and Pay → Web Settings" +msgstr "" + +#: includes/gateways/amazon-payments.php:381 +msgid "Amazon Merchant IPN URL" +msgstr "" + +#: includes/gateways/amazon-payments.php:382 +msgid "The IPN URL to provide in your MWS account. Enter this under your Integration Settings" +msgstr "" + +#: includes/gateways/amazon-payments.php:666 +msgid "Currently logged into Amazon as" +msgstr "" + +#: includes/gateways/amazon-payments.php:667 +msgid "Logout" +msgstr "" + +#: includes/gateways/amazon-payments.php:817 +#: includes/gateways/amazon-payments.php:831 +msgid "Missing Reference ID, please try again." +msgstr "" + +#: includes/gateways/amazon-payments.php:864 +msgid "Your payment could not be authorized, please try a different payment method. Reason: %s" +msgstr "" + +#: includes/gateways/amazon-payments.php:910 +msgid "There was an issue processing your payment. Amazon error: %s" +msgstr "" + +#: includes/gateways/amazon-payments.php:1024 +msgid "Invalid Amazon seller ID" +msgstr "" + +#. Translators: %s - IPN Verification response +#: includes/gateways/amazon-payments.php:1024 +#: includes/gateways/amazon-payments.php:1064 +#: includes/gateways/paypal-standard.php:492 +#: includes/gateways/paypal-standard.php:499 +#: includes/gateways/paypal-standard.php:592 +#: includes/gateways/paypal-standard.php:602 +#: includes/gateways/paypal-standard.php:675 +#: includes/gateways/paypal-standard.php:684 +#: includes/gateways/paypal/ipn.php:127 +#: includes/gateways/paypal/ipn.php:135 +msgid "IPN Error" +msgstr "" + +#: includes/gateways/amazon-payments.php:1043 +msgid "Capture declined in Amazon" +msgstr "" + +#: includes/gateways/amazon-payments.php:1057 +#: includes/gateways/amazon-payments.php:1132 +msgid "Refund completed in Amazon. Refund ID: %s" +msgstr "" + +#: includes/gateways/amazon-payments.php:1127 +msgid "Refund declined in Amazon. Refund ID: %s" +msgstr "" + +#: includes/gateways/amazon-payments.php:1136 +msgid "Refund initiated in Amazon. Reference ID: %s" +msgstr "" + +#: includes/gateways/amazon-payments.php:1142 +msgid "Refund request failed in Amazon." +msgstr "" + +#: includes/gateways/functions.php:32 +msgid "Test" +msgstr "" + +#: includes/gateways/functions.php:54 +#: includes/gateways/functions.php:55 +#: includes/gateways/functions.php:71 +#: includes/gateways/paypal/admin/settings.php:36 +msgid "PayPal" +msgstr "" + +#: includes/gateways/functions.php:70 +#: includes/gateways/paypal-standard.php:36 +msgid "PayPal Standard" +msgstr "" + +#: includes/gateways/functions.php:320 +msgid "The requested price ID does not exist." +msgstr "" + +#: includes/gateways/manual.php:33 +#: includes/gateways/paypal-standard.php:170 +#: includes/gateways/paypal/checkout-actions.php:114 +msgid "Nonce verification has failed" +msgstr "" + +#: includes/gateways/manual.php:73 +msgid "Payment creation failed while processing a manual (free or test) purchase. Payment data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:58 +msgid "PayPal Email" +msgstr "" + +#: includes/gateways/paypal-standard.php:59 +msgid "Enter your PayPal account's email" +msgstr "" + +#: includes/gateways/paypal-standard.php:65 +msgid "PayPal Image" +msgstr "" + +#: includes/gateways/paypal-standard.php:66 +msgid "Upload an image to display on the PayPal checkout page." +msgstr "" + +#: includes/gateways/paypal-standard.php:73 +msgid "Enter your PayPal Identity Token in order to enable Payment Data Transfer (PDT). This allows payments to be verified without relying on the PayPal IPN. See our documentation for further information." +msgstr "" + +#: includes/gateways/paypal-standard.php:79 +msgid "PayPal Identity Token" +msgstr "" + +#: includes/gateways/paypal-standard.php:86 +msgid "If you are unable to use Payment Data Transfer and payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases. See our FAQ for further information." +msgstr "" + +#: includes/gateways/paypal-standard.php:92 +msgid "Disable PayPal IPN Verification" +msgstr "" + +#: includes/gateways/paypal-standard.php:101 +msgid "API Credentials" +msgstr "" + +#: includes/gateways/paypal-standard.php:104 +msgid "API credentials are necessary to process PayPal refunds from inside WordPress. These can be obtained from your PayPal account." +msgstr "" + +#: includes/gateways/paypal-standard.php:110 +msgid "Live API Username" +msgstr "" + +#: includes/gateways/paypal-standard.php:111 +msgid "Your PayPal live API username. " +msgstr "" + +#: includes/gateways/paypal-standard.php:117 +msgid "Live API Password" +msgstr "" + +#: includes/gateways/paypal-standard.php:118 +msgid "Your PayPal live API password." +msgstr "" + +#: includes/gateways/paypal-standard.php:124 +msgid "Live API Signature" +msgstr "" + +#: includes/gateways/paypal-standard.php:125 +msgid "Your PayPal live API signature." +msgstr "" + +#: includes/gateways/paypal-standard.php:131 +msgid "Test API Username" +msgstr "" + +#: includes/gateways/paypal-standard.php:132 +msgid "Your PayPal test API username." +msgstr "" + +#: includes/gateways/paypal-standard.php:138 +msgid "Test API Password" +msgstr "" + +#: includes/gateways/paypal-standard.php:139 +msgid "Your PayPal test API password." +msgstr "" + +#: includes/gateways/paypal-standard.php:145 +msgid "Test API Signature" +msgstr "" + +#: includes/gateways/paypal-standard.php:146 +msgid "Your PayPal test API signature." +msgstr "" + +#: includes/gateways/paypal-standard.php:193 +msgid "Payment creation failed before sending buyer to PayPal. Payment data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:492 +#: includes/gateways/paypal-standard.php:499 +msgid "Invalid IPN verification response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:592 +msgid "Invalid business email in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:595 +msgid "Payment failed due to invalid PayPal business email." +msgstr "" + +#: includes/gateways/paypal-standard.php:602 +msgid "Invalid currency in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:605 +msgid "Payment failed due to invalid currency in PayPal IPN." +msgstr "" + +#: includes/gateways/paypal-standard.php:675 +msgid "Invalid payment amount in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:678 +msgid "Payment failed due to invalid amount in PayPal IPN." +msgstr "" + +#: includes/gateways/paypal-standard.php:684 +msgid "Invalid purchase key in IPN response. IPN data: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:686 +msgid "Payment failed due to invalid purchase key in PayPal IPN." +msgstr "" + +#. Translators: %s - transaction ID +#: includes/gateways/paypal-standard.php:692 +#: includes/gateways/paypal/checkout-actions.php:404 +msgid "PayPal Transaction ID: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:706 +msgid "Payment made via eCheck and will clear automatically in 5-8 days" +msgstr "" + +#: includes/gateways/paypal-standard.php:713 +msgid "Payment requires a confirmed customer address and must be accepted manually through PayPal" +msgstr "" + +#: includes/gateways/paypal-standard.php:719 +msgid "Payment must be accepted manually through PayPal due to international account regulations" +msgstr "" + +#: includes/gateways/paypal-standard.php:725 +msgid "Payment received in non-shop currency and must be accepted manually through PayPal" +msgstr "" + +#: includes/gateways/paypal-standard.php:732 +msgid "Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations" +msgstr "" + +#: includes/gateways/paypal-standard.php:738 +msgid "Payment was sent to non-confirmed or non-registered email address." +msgstr "" + +#: includes/gateways/paypal-standard.php:744 +msgid "PayPal account must be upgraded before this payment can be accepted" +msgstr "" + +#: includes/gateways/paypal-standard.php:750 +msgid "PayPal account is not verified. Verify account in order to accept this payment" +msgstr "" + +#: includes/gateways/paypal-standard.php:756 +msgid "Payment is pending for unknown reasons. Contact PayPal support for assistance" +msgstr "" + +#: includes/gateways/paypal-standard.php:809 +msgid "Partial PayPal refund processed: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:814 +msgid "PayPal Payment #%s Refunded for reason: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:815 +msgid "PayPal Refund Transaction ID: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:1029 +msgid "Payment could not be verified while validating PayPal PDT. Missing payment total fields." +msgstr "" + +#: includes/gateways/paypal-standard.php:1040 +msgid "Payment failed while validating PayPal PDT. Amount expected: %f. Amount Received: %f" +msgstr "" + +#: includes/gateways/paypal-standard.php:1070 +msgid "Payment failed while validating PayPal PDT." +msgstr "" + +#: includes/gateways/paypal-standard.php:1077 +msgid "PayPal PDT encountered an unexpected result, payment set to pending" +msgstr "" + +#: includes/gateways/paypal-standard.php:1164 +#: includes/gateways/paypal/refunds.php:43 +msgid "Refund transaction in PayPal" +msgstr "" + +#: includes/gateways/paypal-standard.php:1203 +#: includes/gateways/paypal/refunds.php:81 +msgid "Transaction not refunded in PayPal, as checkbox was not selected." +msgstr "" + +#: includes/gateways/paypal-standard.php:1326 +msgid "PayPal refund failed for unknown reason." +msgstr "" + +#. Translators: %1$s - amount refunded; %2$s - transaction ID. +#: includes/gateways/paypal-standard.php:1339 +msgid "%1$s refunded in PayPal. Transaction ID: %2$s" +msgstr "" + +#. Translators: %s - transaction ID. +#: includes/gateways/paypal-standard.php:1342 +msgid "PayPal refund transaction ID: %s" +msgstr "" + +#: includes/gateways/paypal-standard.php:1374 +msgid "PayPal refund failed: %s" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:32 +#: includes/gateways/paypal/admin/connect.php:408 +msgid "sandbox" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:32 +#: includes/gateways/paypal/admin/connect.php:408 +msgid "live" +msgstr "" + +#. Translators: %1$s opening tag; %2$s closing tag +#: includes/gateways/paypal/admin/connect.php:43 +msgid "%1$sPayPal Communication Error:%2$s We are having trouble communicating with PayPal at the moment. Please try again later, and if the issue persists, reach out to our support team." +msgstr "" + +#. Translators: %s - the store mode, either `sandbox` or `live` +#: includes/gateways/paypal/admin/connect.php:56 +msgid "Connect with PayPal in %s mode" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:71 +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:377 +msgid "Retrieving account information..." +msgstr "" + +#. Translators: %d - HTTP response code; %s - Response from the API +#: includes/gateways/paypal/admin/connect.php:183 +msgid "Unexpected response code: %d. Error: %s" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:190 +msgid "An unexpected error occurred." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:222 +msgid "Failure reconnecting to PayPal. Please try again" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:228 +msgid "Your account has been successfully reconnected, but an error occurred while creating a webhook." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:265 +msgid "Missing PayPal authentication information. Please try again." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:272 +msgid "Missing nonce. Please refresh the page and try again." +msgstr "" + +#. Translators: %d - HTTP response code +#: includes/gateways/paypal/admin/connect.php:310 +msgid "Unexpected response from PayPal while generating token. Response code: %d. Please try again." +msgstr "" + +#. Translators: %d - HTTP response code +#: includes/gateways/paypal/admin/connect.php:339 +msgid "Unexpected response from PayPal. Response code: %d. Please try again." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:347 +msgid "Successfully connected." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:352 +msgid "Your account has been successfully connected, but an error occurred while creating a webhook." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:386 +msgid "Re-Check Payment Status" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:387 +msgid "Sync Webhook" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:391 +msgid "Disconnect webhooks from PayPal" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:392 +msgid "Delete connection with PayPal" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:401 +msgid "API:" +msgstr "" + +#. Translators: %s - the connected mode, either `sandbox` or `live` +#: includes/gateways/paypal/admin/connect.php:411 +msgid "Your PayPal account is successfully connected in %s mode." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:426 +msgid "Payment Status:" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:430 +msgid "You need to address the following issues before you can start receiving payments:" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:438 +msgid "Ready to accept payments." +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:460 +msgid "Webhook:" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:468 +msgid "Create Webhooks" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:473 +msgid "Webhook successfully configured for the following events:" +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag; %3$s: opening line item/status/strong tags; %4$s closing strong tag; %5$s: closing list item tag +#: includes/gateways/paypal/admin/connect.php:498 +msgid "%3$sGateway Status: %4$s PayPal is not currently active. %1$sEnable PayPal%2$s in the general gateway settings to start using it.%5$s" +msgstr "" + +#: includes/gateways/paypal/admin/connect.php:682 +msgid "No merchant ID saved. Please reconnect to PayPal." +msgstr "" + +#: includes/gateways/paypal/admin/notices.php:49 +msgid "Enable the new PayPal gateway for Easy Digital Downloads" +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag +#: includes/gateways/paypal/admin/notices.php:54 +msgid "A new, improved PayPal experience is now available in Easy Digital Downloads. You can learn more about the new integration in %1$sour documentation%2$s." +msgstr "" + +#: includes/gateways/paypal/admin/notices.php:61 +msgid "Activate the New PayPal" +msgstr "" + +#: includes/gateways/paypal/admin/notices.php:62 +msgid "Dismiss Notice" +msgstr "" + +#: includes/gateways/paypal/admin/scripts.php:28 +msgid "An unexpected error occurred. Please refresh the page and try again." +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:55 +msgid "PayPal Settings" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:60 +#: includes/gateways/stripe/includes/admin/settings.php:31 +msgid "Connection Status" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:66 +msgid "Test Client ID" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:67 +msgid "Enter your test client ID." +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:74 +msgid "Test Client Secret" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:75 +msgid "Enter your test client secret." +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:82 +msgid "Live Client ID" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:83 +msgid "Enter your live client ID." +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:90 +msgid "Live Client Secret" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:91 +msgid "Enter your live client secret." +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:105 +msgid "Connect with PayPal" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:106 +msgid "Connecting your store with PayPal allows Easy Digital Downloads to automatically configure your store to securely communicate with PayPal.

    You may see \"Sandhills Development, LLC\", mentioned during the process—that is the company behind Easy Digital Downloads." +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:132 +msgid "View Documentation" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:136 +msgid "Get Help" +msgstr "" + +#: includes/gateways/paypal/admin/settings.php:146 +msgid "PayPal requires an SSL certificate to accept payments. You can learn more about obtaining an SSL certificate in our SSL setup article." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:56 +#: includes/gateways/paypal/checkout-actions.php:126 +msgid "Please connect your PayPal account in the gateway settings." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:57 +#: includes/gateways/paypal/checkout-actions.php:127 +msgid "Unexpected authentication error. Please contact a site administrator." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:121 +#: includes/gateways/paypal/exceptions/class-gateway-exception.php:48 +msgid "PayPal Gateway Error" +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:122 +msgid "Account not ready to accept payments." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:153 +#: includes/gateways/paypal/checkout-actions.php:313 +#: includes/gateways/paypal/scripts.php:105 +msgid "An unexpected error occurred. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:206 +msgid "PayPal Gateway Warning" +msgstr "" + +#. Translators: %s - Original order data sent to PayPal. +#: includes/gateways/paypal/checkout-actions.php:209 +msgid "PayPal could not complete the transaction with the itemized breakdown. Original order data sent: %s" +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:228 +#: includes/gateways/paypal/checkout-actions.php:258 +#: includes/gateways/paypal/checkout-actions.php:425 +msgid "An error occurred while communicating with PayPal. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:256 +#: includes/gateways/paypal/checkout-actions.php:423 +msgid "An authentication error occurred. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:290 +#: includes/gateways/paypal/checkout-actions.php:298 +#: includes/gateways/paypal/checkout-actions.php:305 +msgid "A validation error occurred. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:326 +msgid "Failed to process payment. Please try again." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:335 +msgid "Unable to complete your order with your chosen payment method. Please choose a new funding source." +msgstr "" + +#: includes/gateways/paypal/checkout-actions.php:414 +msgid "Your payment was declined. Please try a new payment method." +msgstr "" + +#: includes/gateways/paypal/class-account-status-validator.php:108 +msgid "Not connected." +msgstr "" + +#: includes/gateways/paypal/class-account-status-validator.php:133 +msgid "Missing merchant details from PayPal. Please reconnect and make sure you click the button to be redirected back to your site." +msgstr "" + +#: includes/gateways/paypal/class-account-status-validator.php:148 +msgid "Webhook not configured. Some actions may not work properly." +msgstr "" + +#: includes/gateways/paypal/class-account-status-validator.php:166 +msgid "Webhook is configured but missing an event. Click \"Sync Webhook\" to correct this." +msgid_plural "Webhook is configured but missing events. Click \"Sync Webhook\" to correct this." +msgstr[0] "" +msgstr[1] "" + +#: includes/gateways/paypal/class-merchant-account.php:132 +msgid "Your account is unable to receive payments. Please contact PayPal customer support." +msgstr "" + +#: includes/gateways/paypal/class-merchant-account.php:138 +msgid "Your PayPal email address needs to be confirmed." +msgstr "" + +#. Translators: %s - The ID of the PayPal credential +#: includes/gateways/paypal/class-paypal-api.php:150 +msgid "Missing PayPal credential: %s" +msgstr "" + +#. Translators: %d - HTTP response code. +#: includes/gateways/paypal/class-paypal-api.php:215 +msgid "Unexpected response code: %d" +msgstr "" + +#: includes/gateways/paypal/class-token.php:35 +msgid "Invalid token." +msgstr "" + +#: includes/gateways/paypal/deprecated.php:42 +msgid "Refund Transaction in PayPal" +msgstr "" + +#. Translators: %s - The error message +#: includes/gateways/paypal/deprecated.php:98 +msgid "Failed to refund transaction in PayPal. Error Message: %s" +msgstr "" + +#. Translators: %d - HTTP response code; %s - Error message +#: includes/gateways/paypal/exceptions/class-gateway-exception.php:51 +msgid "Response Code: %d; Message: %s" +msgstr "" + +#. Translators: %s - IPN Verification response +#: includes/gateways/paypal/ipn.php:127 +#: includes/gateways/paypal/ipn.php:135 +msgid "Invalid PayPal Commerce/Express IPN verification response. IPN data: %s" +msgstr "" + +#. Translators: %s - The payment data sent via the IPN +#: includes/gateways/paypal/ipn.php:221 +msgid "Invalid Currency Code" +msgstr "" + +#. Translators: %s - The payment data sent via the IPN +#: includes/gateways/paypal/ipn.php:221 +msgid "The currency code in an IPN request did not match the site currency code. Payment data: %s" +msgstr "" + +#. Translators: %s - The transaction ID of the failed payment +#: includes/gateways/paypal/ipn.php:237 +msgid "Transaction ID %s failed in PayPal" +msgstr "" + +#. Translators: %s - The collected outstanding balance of the subscription +#: includes/gateways/paypal/ipn.php:277 +msgid "Outstanding subscription balance of %s collected successfully." +msgstr "" + +#: includes/gateways/paypal/ipn.php:379 +#: includes/gateways/paypal/webhooks/events/class-payment-capture-refunded.php:53 +msgid "Amount: %1$s; Payment transaction ID: %2$s; Refund transaction ID: %3$s" +msgstr "" + +#: includes/gateways/paypal/ipn.php:391 +#: includes/gateways/paypal/webhooks/events/class-payment-capture-refunded.php:64 +msgid "Partial refund processed in PayPal." +msgstr "" + +#: includes/gateways/paypal/ipn.php:401 +#: includes/gateways/paypal/webhooks/events/class-payment-capture-refunded.php:72 +msgid "Full refund processed in PayPal." +msgstr "" + +#. Translators: %d - ID of the refund; %s - error message from PayPal +#: includes/gateways/paypal/refunds.php:107 +msgid "Failure when processing PayPal refund #%d: %s" +msgstr "" + +#: includes/gateways/paypal/refunds.php:149 +msgid "Missing transaction ID." +msgstr "" + +#. Translators: %d - The HTTP response code; %s - Full API response from PayPal +#: includes/gateways/paypal/refunds.php:175 +msgid "Unexpected response code: %d. Response: %s" +msgstr "" + +#. Translators: %s - API response from PayPal +#: includes/gateways/paypal/refunds.php:184 +msgid "Missing or unexpected refund status. Response: %s" +msgstr "" + +#. Translators: %1$s - amount refunded; %$2$s - transaction ID. +#: includes/gateways/paypal/refunds.php:197 +msgid "%1$s refunded in PayPal. Refund transaction ID: %2$s" +msgstr "" + +#. Translators: %s - ID of the refund in PayPal +#: includes/gateways/paypal/refunds.php:204 +msgid "Successfully refunded in PayPal. Refund transaction ID: %s" +msgstr "" + +#: includes/gateways/paypal/webhooks/events/class-payment-capture-completed.php:55 +msgid "Webhook Error" +msgstr "" + +#. Translators: %s is the webhook data +#: includes/gateways/paypal/webhooks/events/class-payment-capture-completed.php:58 +msgid "Invalid payment about in webhook response. Webhook data: %s" +msgstr "" + +#: includes/gateways/paypal/webhooks/events/class-payment-capture-completed.php:68 +msgid "Payment failed due to invalid amount in PayPal webhook. Expected amount: %s; PayPal amount: %s." +msgstr "" + +#: includes/gateways/paypal/webhooks/events/class-payment-capture-denied.php:31 +msgid "PayPal transaction denied." +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:86 +msgid "An SSL certificate is required to create a PayPal webhook." +msgstr "" + +#. Translators: %d - HTTP response code; %s - Full response from the API. +#: includes/gateways/paypal/webhooks/functions.php:127 +msgid "Invalid response code %d while creating webhook. Response: %s" +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:134 +msgid "Unexpected response from PayPal." +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:161 +msgid "Webhook not configured." +msgstr "" + +#. Translators: %d - HTTP response code; %s - Full response from the API. +#: includes/gateways/paypal/webhooks/functions.php:191 +msgid "Invalid response code %d while syncing webhook. Response: %s" +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:226 +msgid "Your store is currently not receiving webhook notifications, create the webhooks to reconnect." +msgstr "" + +#. Translators: %d - HTTP response code. +#: includes/gateways/paypal/webhooks/functions.php:231 +msgid "Invalid response code %d while retrieving webhook details." +msgstr "" + +#: includes/gateways/paypal/webhooks/functions.php:238 +msgid "Unexpected response from PayPal when retrieving webhook details." +msgstr "" + +#. Translators: %d - HTTP response code. +#: includes/gateways/paypal/webhooks/functions.php:274 +msgid "Invalid response code %d while deleting webhook." +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:63 +msgid "Preapproval cancelled" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:84 +msgid "Are you sure you want to cancel this order?" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:103 +msgid "The preapproved payment was successfully charged." +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:106 +msgid "The preapproved payment failed to be charged. View order details for further details." +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:109 +msgid "The preapproved payment was successfully cancelled." +msgstr "" + +#. translators: %1$s Stripe Connect error message. %2$s Retry URL. +#: includes/gateways/stripe/includes/admin/admin-actions.php:114 +msgid "There was an error connecting your Stripe account. Message: %1$s. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:145 +msgid "Used Existing Card:" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-actions.php:210 +msgid "Refund Charge in Stripe" +msgstr "" + +#: includes/gateways/stripe/includes/admin/admin-filters.php:95 +msgid "Process" +msgstr "" + +#: includes/gateways/stripe/includes/admin/class-notices-registry.php:92 +msgid "A message must be specified for each notice." +msgstr "" + +#: includes/gateways/stripe/includes/admin/notices.php:39 +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:676 +#: includes/gateways/stripe/includes/payment-methods/apple-pay.php:20 +msgid "Unable to locate registry" +msgstr "" + +#: includes/gateways/stripe/includes/admin/notices/edd-recurring-requirement.php:18 +#: includes/gateways/stripe/includes/admin/notices/edd-requirement.php:18 +#: includes/gateways/stripe/includes/admin/notices/php-requirement.php:21 +msgid "Credit card payments with Stripe are currently disabled." +msgstr "" + +#. translators: %1$s Opening strong tag, do not translate. %2$s Closing strong tag, do not translate. %3$s Opening code tag, do not translate. %4$s Closing code tag, do not translate. +#: includes/gateways/stripe/includes/admin/notices/edd-recurring-requirement.php:26 +msgid "To continue accepting credit card payments with Stripe please update %1$sEasy Digital Downloads - Recurring Payments%2$s to version %3$s2.10%4$s or higher." +msgstr "" + +#. translators: %1$s Opening strong tag, do not translate. %2$s Closing strong tag, do not translate. %3$s Opening code tag, do not translate. %4$s Closing code tag, do not translate. +#: includes/gateways/stripe/includes/admin/notices/edd-requirement.php:26 +msgid "To continue accepting credit card payments with Stripe please update %1$sEasy Digital Downloads%2$s to version %3$s3.1%4$s or higher." +msgstr "" + +#: includes/gateways/stripe/includes/admin/notices/edd-stripe-core.php:28 +msgid "Accept credit card payments with Stripe" +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. %3$s Opening anchor tag, do not translate. %4$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/notices/edd-stripe-core.php:34 +msgid "Easy Digital Downloads now lets you accept credit card payments using Stripe, including Apple Pay and Google Pay support. %1$sEnable Stripe%2$s now or %3$slearn more%4$s about the benefits of using Stripe." +msgstr "" + +#. translators: %1$s Future PHP version requirement. %2$s Current PHP version. %3$s Opening strong tag, do not translate. %4$s Closing strong tag, do not translate. %5$s Opening anchor tag, do not translate. %6$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/notices/php-requirement.php:29 +msgid "Easy Digital Downloads Stripe Payment Gateway requires PHP version %1$s or higher. It looks like you're using version %2$s, which means you will need to %3$supgrade your version of PHP to allow the plugin to continue to function%4$s. Newer versions of PHP are both faster and more secure. The version you're using %5$sno longer receives security updates%6$s, which is another great reason to update." +msgstr "" + +#: includes/gateways/stripe/includes/admin/notices/php-requirement.php:51 +msgid "Read More" +msgstr "" + +#: includes/gateways/stripe/includes/admin/notices/php-requirement.php:65 +msgid "Which version should I upgrade to?" +msgstr "" + +#. translators: %1$s Future PHP version requirement. +#: includes/gateways/stripe/includes/admin/notices/php-requirement.php:73 +msgid "In order to be compatible with future versions of the Stripe payment gateway, you should update your PHP version to at least %1$s; however we recommend using version 7.4 if possible to receive the full speed and security benefits provided to more modern and fully supported versions of PHP. However, some plugins may not be fully compatible with PHP 7.4, so more testing may be required." +msgstr "" + +#: includes/gateways/stripe/includes/admin/notices/php-requirement.php:84 +#: includes/gateways/stripe/includes/admin/settings.php:379 +#: includes/gateways/stripe/includes/admin/settings.php:448 +msgid "Need help upgrading? Ask your web host!" +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/notices/php-requirement.php:92 +msgid "Many web hosts can give you instructions on how/where to upgrade your version of PHP through their control panel, or may even be able to do it for you. %1$sRead more about updating PHP%2$s." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:11 +msgid "Stripe" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:38 +msgid "Test Publishable Key" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:39 +msgid "Enter your test publishable key, found in your Stripe Account Settings" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:46 +msgid "Test Secret Key" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:47 +msgid "Enter your test secret key, found in your Stripe Account Settings" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:54 +msgid "Live Publishable Key" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:55 +msgid "Enter your live publishable key, found in your Stripe Account Settings" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:62 +msgid "Live Secret Key" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:63 +msgid "Enter your live secret key, found in your Stripe Account Settings" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:71 +msgid "Webhooks" +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings.php:75 +msgid "In order for Stripe to function completely, you must configure your Stripe webhooks. Visit your %1$saccount dashboard%2$s to configure them. Please add a webhook endpoint for the URL below." +msgstr "" + +#. translators: %s Webhook URL. Do not translate. +#: includes/gateways/stripe/includes/admin/settings.php:83 +msgid "Webhook URL: %s" +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings.php:90 +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:62 +msgid "See our %1$sdocumentation%2$s for more information." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:98 +msgid "Billing Address Display" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:99 +msgid "Select how you would like to display the billing address fields on the checkout form.

    Notes:

    If taxes are enabled, this option cannot be changed from \"Full address\".

    If set to \"No address fields\", you must disable \"zip code verification\" in your Stripe account.

    " +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:103 +msgid "Full address" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:104 +msgid "Zip / Postal Code and Country only" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:105 +msgid "No address fields" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:110 +#: includes/gateways/stripe/includes/gateway-actions.php:53 +msgid "Statement Descriptor" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:111 +msgid "Choose how charges will appear on customer's credit card statements. Max 22 characters" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:116 +msgid "Restrict Stripe Assets" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:117 +msgid "Only load Stripe.com hosted assets on pages that specifically utilize Stripe functionality." +msgstr "" + +#. translators: 1. opening link tag; 2. closing link tag +#: includes/gateways/stripe/includes/admin/settings.php:121 +msgid "Stripe advises that their Javascript library be loaded on every page to take advantage of their advanced fraud detection rules. If you are not concerned with this, enable this setting to only load the Javascript when necessary. %1$sLearn more about Stripe's recommended setup.%2$s" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:134 +msgid "Elements Mode" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:135 +msgid "Toggle between using the legacy Card Elements Stripe integration and the new Payment Elements experience." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:138 +msgid "Card Element" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:139 +msgid "Payment Element" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:142 +msgid "Transitioning to Payment Elements" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:143 +msgid "You are seeing this option because your store has been using Card Elements prior to the EDD Stripe 2.9.0 update.

    To ensure that we do not affect your current checkout experience, you can use this setting to toggle between the Card Elements (legacy) and Payment Elements (updated version) to ensure that any customizations or theming you have done still function properly.

    Please be advised, that in a future version of the Stripe extension, we will deprecate the Card Elements, so take this time to update your store!" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:149 +#: includes/gateways/stripe/includes/admin/settings.php:162 +msgid "Prepaid Cards" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:150 +msgid "Allow prepaid cards as valid payment method." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings.php:165 +msgid "Prepaid card allowance can now be managed in your %1$sStripe Radar Rules%2$s." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:175 +msgid "Split Credit Card Form" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:176 +msgid "Use separate card number, expiration, and CVC fields in payment forms." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:183 +#: includes/gateways/stripe/includes/admin/settings.php:191 +msgid "Show Previously Used Cards" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:184 +msgid "Provides logged in customers with a list of previous used payment methods for faster checkout." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings.php:194 +msgid "Previously used cards are now managed by %1$sLink by Stripe%2$s, for even better conversions and security." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:212 +msgid "Debugging Settings" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:214 +msgid "The following settings are available while Easy Digital Downloads is in debug mode. They are not designed to be primary settings and should be used only while debugging or when instructed to be used by the Easy Digital Downloads Team." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:215 +msgid "There is no guarantee that these settings will remain available in future versions of Easy Digital Downloads. Easy Digital Downloads Debug Mode should be disabled once changes to these settings have been made." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:222 +msgid "Disable access to Card Elements" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:222 +msgid "Enable access to Card Elements" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:223 +msgid "Access to Legacy Card Elements is Enabled" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:223 +msgid "Access to Legacy Card Elements is Disabled" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:229 +msgid "Toggle Card Elements" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:240 +msgid "Card Elements is the legacy Stripe integration. Easy Digital Downloads has updated to use the more secure and reliable Payment Elements feature of Stripe. This toggle allows sites without access to Card Elements to enable or disable it." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings.php:250 +msgid "You have disabled the \"Test Mode\" option. Once you have saved your changes, please verify your Stripe connection, especially if you have not previously connected in with \"Test Mode\" disabled." +msgstr "" + +#. translators: %1$s PHP version requirement. %2$s Current PHP version. %3$s Opening strong tag, do not translate. %4$s Closing strong tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings.php:361 +#: includes/gateways/stripe/includes/admin/settings.php:433 +msgid "Processing credit cards with Stripe requires PHP version %1$s or higher. It looks like you're using version %2$s, which means you will need to %3$supgrade your version of PHP before acceping credit card payments%4$s." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings.php:385 +#: includes/gateways/stripe/includes/admin/settings.php:453 +msgid "Many web hosts can give you instructions on how/where to upgrade your version of PHP through their control panel, or may even be able to do it for you. If you need to change hosts, please see %1$sour hosting recommendations%2$s." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:124 +msgid "There was an error getting your Stripe credentials. Please %1$stry again%2$s. If you continue to have this problem, please contact support." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:330 +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:620 +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:682 +#: includes/gateways/stripe/includes/admin/site-health.php:94 +msgid "Connect with Stripe" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:340 +msgid "Connect with Stripe for pay as you go pricing: 2% per-transaction fee + Stripe fees." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:351 +msgid "Have questions about connecting with Stripe? See the %1$sdocumentation%2$s." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:390 +msgid "Manage API keys manually" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:400 +msgid "Hide API keys" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:406 +msgid "Although you can add your API keys manually, we recommend using Stripe Connect: an easier and more secure way of connecting your Stripe account to your website. Stripe Connect prevents issues that can arise when copying and pasting account details from Stripe into your Easy Digital Downloads payment gateway settings. With Stripe Connect you'll be ready to go with just a few clicks." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:424 +msgid "Unable to retrieve account information." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:442 +msgctxt "Stripe Connect mode" +msgid "test" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:443 +msgctxt "Stripe Connect mode" +msgid "live" +msgstr "" + +#. translators: %1$s Stripe payment mode. %2$s Opening anchor tag for reconnecting to Stripe, do not translate. %3$s Opening anchor tag for disconnecting Stripe, do not translate. %4$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:449 +msgid "Your Stripe account is connected in %1$s mode. %2$sReconnect in %1$s mode%4$s, or %3$sdisconnect this account%4$s." +msgstr "" + +#. translators: %1$s Opening bold tag, do not translate. %2$s Closing bold tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:471 +msgid "You are currently connected to a %1$stemporary%2$s Stripe test account, which can only be used for testing purposes. You cannot manage this account in Stripe." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:481 +msgid "Webhooks cannot be configured for recurring purchases with this account." +msgstr "" + +#. translators: %1$s Opening link tag, do not translate. %2$s Closing link tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:488 +msgid "%1$sRegister a Stripe account%2$s for full access." +msgstr "" + +#. translators: %1$s Opening anchor tag for disconnecting Stripe, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:499 +msgid "%1$sDisconnect this account%2$s." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:565 +msgid "Pay as you go pricing: 2% per-transaction fee + Stripe fees." +msgstr "" + +#. Translators: Replacements are for the html wrappers for the phrase Upgrade to Pro and should not be translated. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:571 +msgid "%1$sUpgrade to Pro%2$s and install the Stripe Pro Gateway to remove transaction fees." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:586 +msgid "Administrator (Owner)" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:597 +msgid "The API keys provided do not match the Stripe Connect account associated with this installation. If you have manually modified these values after connecting your account, please reconnect below or update your API keys." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:623 +msgid "It is highly recommended to Connect with Stripe for easier setup and improved security." +msgstr "" + +#. translators: %1$s Stripe payment mode. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:636 +msgid "Your manually managed %1$s mode API keys are valid." +msgstr "" + +#. translators: %1$s Stripe payment mode. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:652 +msgid "Your manually managed %1$s mode API keys are invalid." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:692 +#: src/Admin/Onboarding/Steps/PaymentMethods.php:49 +msgid "Start accepting payments with Stripe by connecting your account. Stripe Connect helps ensure easier setup and improved security." +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:703 +msgctxt "gateway test mode status" +msgid "enabled" +msgstr "" + +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:704 +msgctxt "gateway test mode status" +msgid "disabled" +msgstr "" + +#. translators: %s Test mode status. Enabled or disabled. +#: includes/gateways/stripe/includes/admin/settings/stripe-connect.php:713 +msgid "\"Test Mode\" has been %s. Please verify your Stripe connection status." +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:24 +msgid "Stripe Connect" +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:41 +msgid "You are securely connected to Stripe" +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:44 +msgid "Easy Digital Downloads: Stripe" +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:49 +msgid "Stripe Connect helps ensure easy setup and security." +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:62 +msgid "You are using the legacy Card Elements fields" +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:67 +msgid "Increase conversions, security, and reliability by using the Payment Elements integration for Stripe." +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:80 +msgid "Switch to Payment Elements" +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:84 +msgid "You are using manually managed Stripe API keys" +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:89 +msgid "By securely connecting your Easy Digital Downloads store with Stripe Connect, you'll get access to more reliable payments and use managed API keys which are more secure." +msgstr "" + +#: includes/gateways/stripe/includes/admin/site-health.php:115 +msgid "Easy Digital Downloads — Stripe" +msgstr "" + +#. translators: %s Upgrade link. +#: includes/gateways/stripe/includes/admin/upgrade-functions.php:85 +msgid "Easy Digital Downloads - Stripe Gateway needs to upgrade the customers database; click here to start the upgrade. Learn more about this upgrade" +msgstr "" + +#: includes/gateways/stripe/includes/admin/upgrade-functions.php:88 +msgid "About this upgrade:
    This upgrade will improve the reliability of associating purchase records with your existing customer records in Stripe by changing their Stripe Customer IDs to be stored locally on their EDD customer record, instead of their user record." +msgstr "" + +#: includes/gateways/stripe/includes/admin/upgrade-functions.php:90 +msgid "Advanced User?
    This upgrade can also be run via WPCLI with the following command:
    wp edd-stripe migrate_customer_ids" +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:22 +#: includes/gateways/stripe/includes/card-actions.php:141 +#: includes/gateways/stripe/includes/card-actions.php:208 +#: includes/gateways/stripe/includes/card-actions.php:258 +#: includes/gateways/stripe/includes/card-actions.php:311 +msgid "This feature is not available at this time." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:33 +#: includes/gateways/stripe/includes/card-actions.php:42 +#: includes/gateways/stripe/includes/card-actions.php:50 +#: includes/gateways/stripe/includes/card-actions.php:152 +#: includes/gateways/stripe/includes/card-actions.php:161 +#: includes/gateways/stripe/includes/card-actions.php:172 +#: includes/gateways/stripe/includes/card-actions.php:219 +#: includes/gateways/stripe/includes/card-actions.php:228 +#: includes/gateways/stripe/includes/card-actions.php:239 +msgid "Error updating card." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:115 +msgid "Card successfully updated." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:182 +msgid "Card successfully set as default." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:285 +msgid "Card successfully removed." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:322 +msgid "Unable to update your account at this time, please try again later" +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:333 +msgid "Missing card ID." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:342 +msgid "Error adding card." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:351 +msgid "Unable to retrieve customer." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:378 +msgid "Unable to create customer in Stripe." +msgstr "" + +#: includes/gateways/stripe/includes/card-actions.php:406 +msgid "Card successfully added." +msgstr "" + +#: includes/gateways/stripe/includes/class-edd-stripe-rate-limiting.php:430 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:42 +#: includes/gateways/stripe/includes/template-functions.php:24 +msgid "We are unable to process your payment at this time, please try again later or contact support." +msgstr "" + +#: includes/gateways/stripe/includes/class-edd-stripe.php:299 +#: includes/gateways/stripe/includes/template-functions.php:177 +msgid "Credit Card" +msgstr "" + +#: includes/gateways/stripe/includes/class-stripe-api.php:51 +msgid "Unable to process request: Unmet Stripe payment gateway requirements. Please contact the website administrator." +msgstr "" + +#. translators: %1$s Stripe API class name. %2$s Stripe API method name. +#: includes/gateways/stripe/includes/class-stripe-api.php:69 +msgid "Unable to call %1$s::%2$s" +msgstr "" + +#: includes/gateways/stripe/includes/compat.php:116 +#: includes/process-purchase.php:569 +#: includes/process-purchase.php:948 +msgid "The user information is invalid" +msgstr "" + +#: includes/gateways/stripe/includes/compat.php:225 +#: includes/process-purchase.php:44 +msgid "Missing nonce when processing checkout. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/05/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4" +msgstr "" + +#: includes/gateways/stripe/includes/compat.php:230 +#: includes/process-purchase.php:51 +msgid "Error processing purchase. Please reload the page and try again." +msgstr "" + +#. translators: %s: WP_ENVIRONMENT_TYPES +#: includes/gateways/stripe/includes/compat.php:419 +msgid "The %s constant is no longer supported." +msgstr "" + +#: includes/gateways/stripe/includes/emails.php:29 +msgid "Your Preapproved Payment Requires Action" +msgstr "" + +#: includes/gateways/stripe/includes/emails.php:30 +msgid "Payment Requires Action" +msgstr "" + +#: includes/gateways/stripe/includes/emails.php:32 +msgid "Dear {name}," +msgstr "" + +#: includes/gateways/stripe/includes/emails.php:33 +msgid "Your preapproved payment requires further action before your purchase can be completed. Please click the link below to take finalize your purchase" +msgstr "" + +#: includes/gateways/stripe/includes/functions.php:649 +msgid "Invalid order ID." +msgstr "" + +#. translators: %1$s the amount refunded; %2$s Stripe Refund ID +#: includes/gateways/stripe/includes/functions.php:717 +msgid "%1$s refunded in Stripe. Refund ID %2$s" +msgstr "" + +#: includes/gateways/stripe/includes/gateway-actions.php:11 +msgctxt "Pending preapproved payment" +msgid "Preapproval Pending" +msgstr "" + +#. translators: %s Payment status count +#: includes/gateways/stripe/includes/gateway-actions.php:17 +#: includes/gateways/stripe/includes/gateway-actions.php:26 +#: includes/gateways/stripe/includes/gateway-actions.php:35 +#: includes/post-types.php:429 +msgid "Active (%s)" +msgid_plural "Active (%s)" +msgstr[0] "" +msgstr[1] "" + +#: includes/gateways/stripe/includes/gateway-actions.php:20 +msgctxt "Preapproved payment" +msgid "Preapproved" +msgstr "" + +#: includes/gateways/stripe/includes/gateway-actions.php:29 +msgctxt "Cancelled payment" +msgid "Cancelled" +msgstr "" + +#: includes/gateways/stripe/includes/gateway-actions.php:51 +msgid "Outputs a line stating what charges will appear as on customer's credit card statements." +msgstr "" + +#: includes/gateways/stripe/includes/gateway-filters.php:61 +msgid "Please enter a name for the credit card." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:24 +msgid "There was an error processing your payment. Please try with a different payment method." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:25 +msgid "There was an error processing your payment. Please contact your card issuer for more information." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:28 +msgid "Payment processing cancelled; your order is not yet complete." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:29 +msgid "The card number is not a valid credit card number." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:30 +msgid "The card's expiration month is invalid." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:31 +msgid "The card's expiration year is invalid." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:32 +msgid "The card's security code is invalid." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:33 +msgid "The card number you provided is incorrect. Please check the number and try again." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:34 +msgid "The card number is incomplete." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:35 +msgid "The card's security code is incomplete." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:36 +msgid "The card's expiration date is incomplete." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:37 +msgid "The card's security code is incorrect." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:38 +msgid "The card's zip code failed validation." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:39 +msgid "The card's expiration year is in the past." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:40 +msgid "An error occurred while processing the card. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:41 +msgid "Invalid email address, please correct and try again." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:42 +msgid "Your purchase may require additional authentication. Please try again and confirm any authentication requests." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:43 +msgid "There was an error processing your payment. Please try again, and if you continue to have problems, contact your card issuer." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:44 +msgid "Your payment method is not authorized to make purchases in this currency. Please contact your card issuer." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:45 +msgid "The payment method you have provided is expired. Please try a different payment method." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:46 +msgid "There was an error processing your payment. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/i18n.php:131 +msgid "Subscriptions must be purchased individually. Please update your cart to only contain a single subscription." +msgstr "" + +#: includes/gateways/stripe/includes/integrations/wp-cli.php:49 +msgid "The Stripe customer ID migration has already been run. To do this anyway, use the --force argument." +msgstr "" + +#: includes/gateways/stripe/includes/integrations/wp-cli.php:86 +msgid "Migration complete." +msgstr "" + +#: includes/gateways/stripe/includes/integrations/wp-cli.php:88 +msgid "No user records were found that needed to be migrated." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:63 +msgid "Unable to locate payment method. Please try again with a new payment method." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:81 +msgid "Prepaid cards are not a valid payment method. Please try again with a new payment method." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:99 +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:116 +msgid "Unable to create customer. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:218 +msgid "Charges are no longer directly created in Stripe. Please read the following for more information: https://easydigitaldownloads.com/development/" +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:344 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:366 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:384 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:460 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:542 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:624 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:711 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:917 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1054 +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:705 +msgid "Stripe Error" +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:346 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:368 +msgid "There was an error while processing a Stripe payment. Payment data: %s" +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:426 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:436 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:495 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:505 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:576 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:587 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:658 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:668 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:746 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:757 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:769 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:781 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:798 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:830 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:951 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:962 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:972 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:991 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1039 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1085 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1096 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1109 +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:1143 +msgid "An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/card-elements-actions.php:902 +msgid "Unable to create payment." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/functions.php:218 +msgid "Charge not refunded in Stripe, as checkbox was not selected." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:331 +msgid "Stripe Error 002" +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:333 +msgid "There was an error while processing a Stripe payment. Order data: %s" +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:349 +msgid "Stripe Error 003" +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:384 +msgid "Error 1001: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:395 +msgid "Error 1002: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:407 +msgid "Error 1003: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:419 +msgid "Error 1004: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:436 +msgid "Error 1005: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:468 +msgid "Error 1006: An error occurred, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-actions/payment-elements-actions.php:683 +msgid "Error 1007: An error occurred completing the order, but your payment may have gone through. Please contact the site administrator." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/apple-pay.php:26 +msgid "Apple Pay domain verification error." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/apple-pay.php:172 +msgid "Unable to create domain association folder in domain root." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/apple-pay.php:179 +msgid "Unable to copy domain association file to domain .well-known directory." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/payment-methods/apple-pay.php:281 +msgid "Please %1$smanually add your domain%2$s %3$s to use Apple Pay." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/buy-now/ajax.php:19 +#: includes/gateways/stripe/includes/payment-methods/buy-now/ajax.php:26 +msgid "Unable to add item to cart." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/buy-now/checkout.php:81 +#: includes/gateways/stripe/includes/payment-methods/payment-request/checkout.php:274 +#: includes/process-purchase.php:471 +msgid "Please enter a valid email address" +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:40 +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:49 +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:98 +msgid "Apple Pay/Google Pay" +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. %3$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:54 +msgid "\"Express Checkout\" via Apple Pay, Google Pay, or Microsoft Pay digital wallets. By using Apple Pay, you agree to %1$sStripe%3$s and %2$sApple's%3$s terms of service." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:60 +msgid "Apple Pay is not available in Test Mode." +msgstr "" + +#. translators: %s Download noun +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:83 +msgid "Single %s" +msgstr "" + +#. translators: 1. Download noun; 2. shortcode tag wrapped in span +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:88 +msgid "%1$s Archive (includes %2$s shortcode)" +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:99 +msgid "Apple Pay and Google Pay support is now provided via the Payment Elements integration." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:129 +msgid "This feature is not available when taxes are enabled." +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/gateways/stripe/includes/payment-methods/payment-request/admin/settings.php:137 +msgid "See the %1$sExpress Checkout documentation%2$s for more information." +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/checkout.php:73 +msgid "Express Checkout (Apple Pay/Google Pay)" +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/checkout.php:74 +msgid "Express Checkout" +msgstr "" + +#. translators: %d: Quantity. +#: includes/gateways/stripe/includes/payment-methods/payment-request/functions.php:166 +#: includes/gateways/stripe/includes/payment-methods/payment-request/functions.php:223 +msgid " × %d" +msgstr "" + +#: includes/gateways/stripe/includes/payment-methods/payment-request/template.php:200 +msgctxt "payment request button divider" +msgid "or" +msgstr "" + +#: includes/gateways/stripe/includes/payment-receipt.php:52 +msgid "To finalize your preapproved purchase, please confirm your payment method." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:90 +msgid "Stripe publishable key missing. Please enter your publishable key in Settings." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:91 +msgid "Please fill out all required fields to continue your purchase." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:92 +msgid "Please agree to the terms to complete your purchase." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:93 +msgid "Please agree to the privacy policy to complete your purchase." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:94 +msgid "Unable to complete your request. Please try again." +msgstr "" + +#: includes/gateways/stripe/includes/scripts.php:99 +msgid "Please wait..." +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:41 +msgid "Payment Info" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:100 +msgid "Adding new payment methods is currently unavailable." +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:175 +msgid "Credit Card Number" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:201 +msgid "Expiration" +msgstr "" + +#. translators: %1$s Card type. %2$s Card last 4. +#: includes/gateways/stripe/includes/template-functions.php:291 +msgid "Update card billing address for %1$s •••• %2$s" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:319 +msgid "We are unable to process your payment at this time, please try again later or contacts support." +msgstr "" + +#. translators: %1$s Card type. %2$s Card last 4. +#: includes/gateways/stripe/includes/template-functions.php:354 +#: includes/gateways/stripe/includes/template-functions.php:443 +msgid "%1$s •••• %2$s" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:391 +#: includes/gateways/stripe/includes/template-functions.php:664 +msgid "Add New Card" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:420 +msgid "Payment method management is currently unavailable." +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:433 +msgid "Manage Payment Methods" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:462 +msgid "Expires" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:484 +msgid "Set as Default" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:502 +#: includes/gateways/stripe/includes/template-functions.php:806 +msgid "Billing Details" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:552 +msgid "ZIP Code" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:645 +#: includes/gateways/stripe/includes/template-functions.php:689 +msgid "Please Wait…" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:646 +#: includes/gateways/stripe/includes/template-functions.php:647 +msgid "Update Card" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:666 +msgid "Credit Card Details" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:690 +#: includes/gateways/stripe/includes/template-functions.php:691 +msgid "Add new card" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:809 +msgid "Billing Country" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:833 +msgid "Billing Zip / Postal Code" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:907 +#: includes/process-purchase.php:487 +msgid "Please enter your zip / postal code" +msgstr "" + +#: includes/gateways/stripe/includes/template-functions.php:912 +#: includes/process-purchase.php:499 +msgid "Please select your billing country" +msgstr "" + +#. translators: %s: The country name. +#: includes/gateways/stripe/includes/utils/regional-support/class-edd-stripe-region-base.php:114 +msgid "Based on your store's base country of %s, it is recommended to set your Billing Address Display to use the \"Full Address\" option to ensure payments are completed successfully." +msgstr "" + +#: includes/gateways/stripe/includes/webhooks.php:27 +msgid "Unable to find Event" +msgstr "" + +#. Translators: The charge ID from Stripe that is being refunded. +#: includes/gateways/stripe/includes/webhooks.php:73 +msgid "Charge %s has been fully refunded in Stripe." +msgstr "" + +#. Translators: The charge ID from Stripe that is being partially refunded. +#: includes/gateways/stripe/includes/webhooks.php:78 +msgid "Charge %s partially refunded in Stripe." +msgstr "" + +#. translators: %s Stripe Radar review opening reason. +#: includes/gateways/stripe/includes/webhooks.php:115 +msgid "Stripe Radar review opened with a reason of %s." +msgstr "" + +#. translators: %s Stripe Radar review closing reason. +#: includes/gateways/stripe/includes/webhooks.php:157 +msgid "Stripe Radar review closed with a reason of %s." +msgstr "" + +#: includes/install.php:290 +msgid "Purchase Confirmation" +msgstr "" + +#: includes/install.php:295 +msgid "Your transaction failed; please try again or contact site support." +msgstr "" + +#: includes/install.php:298 +msgid "Purchase History" +msgstr "" + +#. translators: 1: PHP file name, 2: EDD version number, 3: alternative file name +#: includes/misc-functions.php:531 +#: includes/misc-functions.php:646 +msgid "%1$s is deprecated since Easy Digital Downloads version %2$s! Use %3$s instead." +msgstr "" + +#. translators: 1: PHP file name, 2: EDD version number +#: includes/misc-functions.php:539 +#: includes/misc-functions.php:649 +msgid "%1$s is deprecated since Easy Digital Downloads version %2$s with no alternative available." +msgstr "" + +#: includes/misc-functions.php:580 +msgid "The %1$s argument of %2$s is deprecated since Easy Digital Downloads version %3$s! Please use %4$s instead." +msgstr "" + +#: includes/misc-functions.php:588 +msgid "The %1$s argument of %2$s is deprecated since Easy Digital Downloads version %3$s with no alternative available." +msgstr "" + +#. translators: 1: PHP file name, 2: EDD version number +#: includes/misc-functions.php:679 +msgid "Code within %1$s is deprecated since Easy Digital Downloads version %2$s. See message for further details." +msgstr "" + +#. translators: 1: PHP file name, 2: EDD version number, 3: alternative hook name +#: includes/misc-functions.php:776 +msgid "The %1$s hook is deprecated since Easy Digital Downloads version %2$s! Use the %3$s hook instead." +msgstr "" + +#. translators: 1: PHP file name, 2: EDD version number +#: includes/misc-functions.php:779 +msgid "The %1$s hook is deprecated since Easy Digital Downloads version %2$s with no alternative available." +msgstr "" + +#: includes/misc-functions.php:1241 +msgid "Store Bot" +msgstr "" + +#: includes/misc-functions.php:1461 +msgid "Verified" +msgstr "" + +#: includes/misc-functions.php:1462 +msgid "Spam" +msgstr "" + +#: includes/misc-functions.php:1463 +msgid "Deleted" +msgstr "" + +#: includes/misc-functions.php:1464 +msgid "Cancelled" +msgstr "" + +#: includes/misc-functions.php:1580 +msgid "Please define default parameters in the form of an array." +msgstr "" + +#: includes/misc-functions.php:1585 +msgid "Please define an SVG icon filename." +msgstr "" + +#: includes/orders/classes/class-refund-validator.php:259 +msgid "No items have been selected to refund." +msgstr "" + +#. Translators: %s - 0.00 formatted in store currency +#: includes/orders/classes/class-refund-validator.php:267 +msgid "The refund amount must be greater than %s." +msgstr "" + +#. Translators: %s - maximum refund amount as formatted currency +#: includes/orders/classes/class-refund-validator.php:277 +msgid "The maximum refund amount is %s." +msgstr "" + +#. Translators: %s is the type of amount being refunded (e.g. "subtotal" or "tax"). Not translatable at this time. +#: includes/orders/classes/class-refund-validator.php:349 +msgid "An unexpected error occurred while validating the maximum %s amount." +msgstr "" + +#. Translators: %1$s - type of amount being refunded (subtotal, tax, or total); %1$s - product name; %3$s - maximum amount allowed for refund +#: includes/orders/classes/class-refund-validator.php:377 +msgid "The maximum refund %1$s for the product \"%2$s\" is %3$s." +msgstr "" + +#. Translators: %1$s - type of amount being refunded (subtotal, tax, or total); %1$s - adjustment description; %3$s - maximum amount allowed for refund +#: includes/orders/classes/class-refund-validator.php:390 +msgid "The maximum refund %s for the adjustment \"%s\" is %s." +msgstr "" + +#: includes/orders/functions/orders.php:661 +msgid "Payment recovery processed" +msgstr "" + +#: includes/orders/functions/refunds.php:204 +msgid "Invalid order." +msgstr "" + +#: includes/orders/functions/refunds.php:208 +msgid "Order not refundable." +msgstr "" + +#: includes/orders/functions/refunds.php:222 +msgid "Refund not allowed on this order." +msgstr "" + +#: includes/orders/functions/refunds.php:271 +msgid "Invalid argument. Please check your amounts and try again." +msgstr "" + +#. translators: %1$s Old order status. %2$s New order status. +#: includes/orders/functions/transitions.php:30 +msgid "Status changed from %1$s to %2$s" +msgstr "" + +#: includes/orders/functions/types.php:84 +#: includes/orders/functions/ui.php:36 +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Order" +msgstr "" + +#: includes/orders/functions/types.php:92 +msgid "Refund" +msgstr "" + +#: includes/orders/functions/types.php:101 +msgid "Invoice" +msgstr "" + +#: includes/orders/functions/types.php:102 +#: src/Admin/Settings/Invoices.php:110 +msgid "Invoices" +msgstr "" + +#: includes/payments/actions.php:274 +msgid "After payment actions processed." +msgstr "" + +#: includes/payments/actions.php:350 +#: includes/payments/actions.php:411 +msgid "Error resuming payment." +msgstr "" + +#: includes/payments/actions.php:354 +msgid "Payment recovery triggered URL" +msgstr "" + +#: includes/payments/actions.php:432 +#: includes/payments/actions.php:466 +msgid "To complete this payment, please login to your account." +msgstr "" + +#: includes/payments/actions.php:434 +msgid "Lost Password" +msgstr "" + +#: includes/payments/class-edd-payment.php:1819 +msgid "Array key \"post_status\" is no longer a supported attribute for the \"edd_update_payment_status_fields\" filter. Please use \"status\" instead." +msgstr "" + +#: includes/payments/functions.php:536 +msgid "Processing" +msgstr "" + +#: includes/payments/functions.php:537 +msgid "Completed" +msgstr "" + +#: includes/payments/functions.php:538 +msgid "Refunded" +msgstr "" + +#: includes/payments/functions.php:539 +msgid "Partially Refunded" +msgstr "" + +#: includes/payments/functions.php:540 +msgid "Revoked" +msgstr "" + +#: includes/payments/functions.php:541 +msgid "Failed" +msgstr "" + +#: includes/payments/functions.php:542 +msgid "Abandoned" +msgstr "" + +#: includes/payments/functions.php:1932 +msgid "A store migration is in progress. Past orders will not appear in your purchase history until they have been updated." +msgstr "" + +#: includes/plugin-compatibility.php:73 +msgid "No Caching on Checkout?" +msgstr "" + +#: includes/plugin-compatibility.php:74 +msgid "Check this box in order to append a ?nocache parameter to the checkout URL to prevent caching plugins from caching the page." +msgstr "" + +#: includes/post-types.php:38 +msgctxt "download post type name" +msgid "%2$s" +msgstr "" + +#: includes/post-types.php:39 +msgctxt "singular download post type name" +msgid "%1$s" +msgstr "" + +#: includes/post-types.php:41 +msgid "Add New %1$s" +msgstr "" + +#: includes/post-types.php:42 +msgid "Edit %1$s" +msgstr "" + +#: includes/post-types.php:43 +msgid "New %1$s" +msgstr "" + +#: includes/post-types.php:44 +msgid "%2$s" +msgstr "" + +#: includes/post-types.php:45 +msgid "View %1$s" +msgstr "" + +#: includes/post-types.php:46 +msgid "Search %2$s" +msgstr "" + +#: includes/post-types.php:47 +msgid "No %2$s found" +msgstr "" + +#: includes/post-types.php:48 +msgid "No %2$s found in Trash" +msgstr "" + +#: includes/post-types.php:50 +msgctxt "download post type menu name" +msgid "%2$s" +msgstr "" + +#: includes/post-types.php:51 +msgid "%1$s Image" +msgstr "" + +#: includes/post-types.php:52 +msgid "Set %1$s Image" +msgstr "" + +#: includes/post-types.php:53 +msgid "Remove %1$s Image" +msgstr "" + +#: includes/post-types.php:54 +msgid "Use as %1$s Image" +msgstr "" + +#: includes/post-types.php:55 +msgid "%1$s Attributes" +msgstr "" + +#: includes/post-types.php:56 +msgid "Filter %2$s list" +msgstr "" + +#: includes/post-types.php:57 +msgid "%2$s list navigation" +msgstr "" + +#: includes/post-types.php:58 +msgid "%2$s list" +msgstr "" + +#: includes/post-types.php:87 +msgctxt "post type general name" +msgid "Payments" +msgstr "" + +#: includes/post-types.php:88 +msgctxt "post type singular name" +msgid "Payment" +msgstr "" + +#: includes/post-types.php:90 +msgid "Add New Payment" +msgstr "" + +#: includes/post-types.php:91 +msgid "Edit Payment" +msgstr "" + +#: includes/post-types.php:92 +msgid "New Payment" +msgstr "" + +#: includes/post-types.php:93 +msgid "All Payments" +msgstr "" + +#: includes/post-types.php:94 +msgid "View Payment" +msgstr "" + +#: includes/post-types.php:95 +msgid "Search Payments" +msgstr "" + +#: includes/post-types.php:96 +msgid "No Payments found" +msgstr "" + +#: includes/post-types.php:97 +msgid "No Payments found in Trash" +msgstr "" + +#: includes/post-types.php:99 +msgid "Payment History" +msgstr "" + +#: includes/post-types.php:117 +msgctxt "post type general name" +msgid "Discounts" +msgstr "" + +#: includes/post-types.php:118 +msgctxt "post type singular name" +msgid "Discount" +msgstr "" + +#: includes/post-types.php:122 +msgid "New Discount" +msgstr "" + +#: includes/post-types.php:124 +msgid "View Discount" +msgstr "" + +#: includes/post-types.php:126 +msgid "No Discounts found" +msgstr "" + +#: includes/post-types.php:127 +msgid "No Discounts found in Trash" +msgstr "" + +#: includes/post-types.php:170 +msgid "Download" +msgstr "" + +#: includes/post-types.php:222 +#: includes/post-types.php:231 +msgid "Enter %s name here" +msgstr "" + +#: includes/post-types.php:249 +msgctxt "taxonomy general name" +msgid "%s Categories" +msgstr "" + +#: includes/post-types.php:250 +msgctxt "taxonomy singular name" +msgid "%s Category" +msgstr "" + +#: includes/post-types.php:251 +msgid "Search %s Categories" +msgstr "" + +#: includes/post-types.php:252 +msgid "All %s Categories" +msgstr "" + +#: includes/post-types.php:253 +msgid "Parent %s Category" +msgstr "" + +#: includes/post-types.php:254 +msgid "Parent %s Category:" +msgstr "" + +#: includes/post-types.php:255 +msgid "Edit %s Category" +msgstr "" + +#: includes/post-types.php:256 +msgid "Update %s Category" +msgstr "" + +#: includes/post-types.php:257 +msgid "Add New %s Category" +msgstr "" + +#: includes/post-types.php:258 +msgid "New %s Category Name" +msgstr "" + +#: includes/post-types.php:284 +msgctxt "taxonomy general name" +msgid "%s Tags" +msgstr "" + +#: includes/post-types.php:285 +msgctxt "taxonomy singular name" +msgid "%s Tag" +msgstr "" + +#: includes/post-types.php:286 +msgid "Search %s Tags" +msgstr "" + +#: includes/post-types.php:287 +msgid "All %s Tags" +msgstr "" + +#: includes/post-types.php:288 +msgid "Parent %s Tag" +msgstr "" + +#: includes/post-types.php:289 +msgid "Parent %s Tag:" +msgstr "" + +#: includes/post-types.php:290 +msgid "Edit %s Tag" +msgstr "" + +#: includes/post-types.php:291 +msgid "Update %s Tag" +msgstr "" + +#: includes/post-types.php:292 +msgid "Add New %s Tag" +msgstr "" + +#: includes/post-types.php:293 +msgid "New %s Tag Name" +msgstr "" + +#: includes/post-types.php:295 +msgid "Choose from most used %s tags" +msgstr "" + +#: includes/post-types.php:382 +msgctxt "Refunded payment status" +msgid "Refunded" +msgstr "" + +#: includes/post-types.php:387 +msgid "Refunded (%s)" +msgid_plural "Refunded (%s)" +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:390 +msgctxt "Failed payment status" +msgid "Failed" +msgstr "" + +#: includes/post-types.php:395 +msgid "Failed (%s)" +msgid_plural "Failed (%s)" +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:398 +msgctxt "Revoked payment status" +msgid "Revoked" +msgstr "" + +#: includes/post-types.php:403 +msgid "Revoked (%s)" +msgid_plural "Revoked (%s)" +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:406 +msgctxt "Abandoned payment status" +msgid "Abandoned" +msgstr "" + +#: includes/post-types.php:411 +msgid "Abandoned (%s)" +msgid_plural "Abandoned (%s)" +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:414 +msgctxt "Processing payment status" +msgid "Processing" +msgstr "" + +#: includes/post-types.php:419 +msgid "Processing (%s)" +msgid_plural "Processing (%s)" +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:424 +msgctxt "Active discount code status" +msgid "Active" +msgstr "" + +#: includes/post-types.php:432 +msgctxt "Inactive discount code status" +msgid "Inactive" +msgstr "" + +#: includes/post-types.php:437 +msgid "Inactive (%s)" +msgid_plural "Inactive (%s)" +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:458 +#: includes/post-types.php:459 +msgid "%2$s updated. %1$sView %2$s%3$s." +msgstr "" + +#: includes/post-types.php:460 +msgid "%2$s published. %1$sView %2$s%3$s." +msgstr "" + +#: includes/post-types.php:461 +msgid "%2$s saved. %1$sView %2$s%3$s." +msgstr "" + +#: includes/post-types.php:462 +msgid "%2$s submitted. %1$sView %2$s%3$s." +msgstr "" + +#: includes/post-types.php:485 +msgid "%1$s %2$s updated." +msgid_plural "%1$s %3$s updated." +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:486 +msgid "%1$s %2$s not updated, somebody is editing it." +msgid_plural "%1$s %3$s not updated, somebody is editing them." +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:487 +msgid "%1$s %2$s permanently deleted." +msgid_plural "%1$s %3$s permanently deleted." +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:488 +msgid "%1$s %2$s moved to the Trash." +msgid_plural "%1$s %3$s moved to the Trash." +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:489 +msgid "%1$s %2$s restored from the Trash." +msgid_plural "%1$s %3$s restored from the Trash." +msgstr[0] "" +msgstr[1] "" + +#: includes/post-types.php:526 +msgid "Checkout Page" +msgstr "" + +#: includes/post-types.php:530 +#: src/Admin/Settings/Tabs/General.php:129 +msgid "Success Page" +msgstr "" + +#: includes/post-types.php:534 +#: src/Admin/Settings/Tabs/General.php:138 +msgid "Failed Transaction Page" +msgstr "" + +#: includes/post-types.php:538 +#: src/Admin/Settings/Tabs/General.php:147 +msgid "Purchase History Page" +msgstr "" + +#: includes/post-types.php:542 +#: src/Admin/Settings/Tabs/General.php:156 +msgid "Login Redirect Page" +msgstr "" + +#: includes/privacy-functions.php:27 +msgid "" +"\n" +"We collect information about you during the checkout process on our store. This information may include, but is not limited to, your name, billing address, shipping address, email address, phone number, credit card/payment details and any other details that might be requested from you for the purpose of processing your orders.\n" +"Handling this data also allows us to:\n" +"- Send you important account/order/service information.\n" +"- Respond to your queries, refund requests, or complaints.\n" +"- Process payments and to prevent fraudulent transactions. We do this on the basis of our legitimate business interests.\n" +"- Set up and administer your account, provide technical and/or customer support, and to verify your identity.\n" +"" +msgstr "" + +#: includes/privacy-functions.php:39 +msgid "Location and traffic data (including IP address and browser type) if you place an order, or if we need to estimate taxes and shipping costs based on your location." +msgstr "" + +#: includes/privacy-functions.php:40 +msgid "Product pages visited and content viewed while your session is active." +msgstr "" + +#: includes/privacy-functions.php:41 +msgid "Your comments and product reviews if you choose to leave them on our website." +msgstr "" + +#: includes/privacy-functions.php:42 +msgid "Account email/password to allow you to access your account, if you have one." +msgstr "" + +#: includes/privacy-functions.php:43 +msgid "If you choose to create an account with us, your name, address, and email address, which will be used to populate the checkout for future orders." +msgstr "" + +#: includes/privacy-functions.php:48 +msgid "Additionally we may also collect the following information:" +msgstr "" + +#: includes/privacy-functions.php:254 +msgid "No customer with ID %d" +msgstr "" + +#: includes/privacy-functions.php:299 +msgid "Customer could not be anonymized due to payments that could not be anonymized or deleted." +msgstr "" + +#: includes/privacy-functions.php:314 +msgid "Anonymized Customer" +msgstr "" + +#: includes/privacy-functions.php:333 +msgid "Customer anonymized successfully" +msgstr "" + +#: includes/privacy-functions.php:337 +msgid "Customer ID %d successfully anonymized." +msgstr "" + +#: includes/privacy-functions.php:361 +msgid "No order with ID %d." +msgstr "" + +#: includes/privacy-functions.php:398 +msgid "Order not modified, due to status: %s." +msgstr "" + +#: includes/privacy-functions.php:407 +msgid "Order %d with status %s deleted." +msgstr "" + +#: includes/privacy-functions.php:445 +msgid "Order ID %d successfully anonymized." +msgstr "" + +#: includes/privacy-functions.php:535 +#: includes/privacy-functions.php:582 +#: includes/privacy-functions.php:1150 +msgid "Customer Record" +msgstr "" + +#: includes/privacy-functions.php:540 +msgid "Billing Information" +msgstr "" + +#: includes/privacy-functions.php:551 +#: includes/privacy-functions.php:972 +#: includes/privacy-functions.php:1165 +msgid "API Access Logs" +msgstr "" + +#: includes/privacy-functions.php:590 +msgid "Primary Email" +msgstr "" + +#: includes/privacy-functions.php:602 +msgid "All Email Addresses" +msgstr "" + +#: includes/privacy-functions.php:612 +msgid "Agreed to Terms" +msgstr "" + +#: includes/privacy-functions.php:622 +msgid "Agreed to Privacy Policy" +msgstr "" + +#: includes/privacy-functions.php:751 +msgid "Order ID / Number" +msgstr "" + +#: includes/privacy-functions.php:755 +msgid "Order Date" +msgstr "" + +#: includes/privacy-functions.php:759 +msgid "Order Completed Date" +msgstr "" + +#: includes/privacy-functions.php:765 +msgid "Order Total" +msgstr "" + +#: includes/privacy-functions.php:769 +msgid "Order Items" +msgstr "" + +#: includes/privacy-functions.php:860 +msgid "Date of Download" +msgstr "" + +#: includes/privacy-functions.php:864 +msgid "Product Downloaded" +msgstr "" + +#: includes/privacy-functions.php:894 +#: includes/privacy-functions.php:1160 +msgid "File Download Logs" +msgstr "" + +#: includes/privacy-functions.php:950 +msgid "Request" +msgstr "" + +#: includes/privacy-functions.php:1037 +msgid "Possibly Delete Customer" +msgstr "" + +#: includes/privacy-functions.php:1104 +msgid "Customer for %s not deleted, due to remaining payments." +msgstr "" + +#: includes/privacy-functions.php:1118 +msgid "Customer for %s successfully deleted." +msgstr "" + +#: includes/privacy-functions.php:1129 +msgid "Customer for %s failed to be deleted." +msgstr "" + +#: includes/privacy-functions.php:1155 +msgid "Order Record" +msgstr "" + +#: includes/privacy-functions.php:1199 +msgid "Customer for %s has been anonymized." +msgstr "" + +#: includes/privacy-functions.php:1223 +msgid "No orders found for %s." +msgstr "" + +#: includes/privacy-functions.php:1224 +msgid "All eligible orders anonymized or deleted for %s." +msgstr "" + +#: includes/privacy-functions.php:1285 +msgid "All eligible file download logs anonymized or deleted for %s." +msgstr "" + +#: includes/privacy-functions.php:1334 +msgid "No User found for %s, no access logs to remove." +msgstr "" + +#: includes/privacy-functions.php:1351 +msgid "All API access logs deleted for %s." +msgstr "" + +#: includes/process-download.php:147 +msgid "Error 103: Error downloading file. Please contact support." +msgstr "" + +#: includes/process-download.php:147 +msgid "File download error" +msgstr "" + +#: includes/process-download.php:181 +msgid "Sorry, this file could not be downloaded." +msgstr "" + +#: includes/process-download.php:181 +msgid "Error Downloading File" +msgstr "" + +#: includes/process-download.php:285 +msgid "You do not have permission to download this file" +msgstr "" + +#: includes/process-download.php:286 +msgid "Purchase Verification Failed" +msgstr "" + +#: includes/process-download.php:1064 +msgid "Invalid file" +msgstr "" + +#: includes/process-download.php:1071 +msgid "The requested file could not be found. Error 404." +msgstr "" + +#: includes/process-download.php:1072 +msgid "File not found" +msgstr "" + +#: includes/process-purchase.php:31 +#: includes/scripts.php:198 +msgid "Your cart is empty" +msgstr "" + +#: includes/process-purchase.php:222 +msgid "The email address %s is already in use." +msgstr "" + +#: includes/process-purchase.php:241 +msgid "Missing nonce when processing login during checkout. Please read the following for more information: https://easydigitaldownloads.com/development/2018/07/09/important-update-to-ajax-requests-in-easy-digital-downloads-2-9-4" +msgstr "" + +#: includes/process-purchase.php:247 +msgid "Error processing login. Nonce failed." +msgstr "" + +#: includes/process-purchase.php:366 +msgid "The selected payment gateway is not enabled" +msgstr "" + +#: includes/process-purchase.php:423 +msgid "One or more of the discounts you entered is invalid" +msgstr "" + +#: includes/process-purchase.php:440 +msgid "You must agree to the terms of use" +msgstr "" + +#: includes/process-purchase.php:454 +msgid "You must agree to the privacy policy" +msgstr "" + +#: includes/process-purchase.php:475 +msgid "Please enter your first name" +msgstr "" + +#: includes/process-purchase.php:493 +msgid "Please enter your billing city" +msgstr "" + +#: includes/process-purchase.php:505 +msgid "Please enter billing state / region" +msgstr "" + +#: includes/process-purchase.php:564 +#: includes/process-purchase.php:674 +#: includes/process-purchase.php:800 +#: includes/users/register.php:80 +msgid "Invalid email" +msgstr "" + +#: includes/process-purchase.php:648 +msgid "Username already exists" +msgstr "" + +#: includes/process-purchase.php:653 +msgid "Invalid username. Only lowercase letters (a-z) and numbers are allowed" +msgstr "" + +#: includes/process-purchase.php:654 +#: includes/users/register.php:59 +#: includes/users/register.php:67 +msgid "Invalid username" +msgstr "" + +#: includes/process-purchase.php:664 +msgid "You must register or login to complete your purchase" +msgstr "" + +#: includes/process-purchase.php:678 +msgid "You cannot use that email address to signup at this time." +msgstr "" + +#: includes/process-purchase.php:688 +msgid "Email already used. Login or use a different email to complete your purchase." +msgstr "" + +#: includes/process-purchase.php:700 +#: includes/process-purchase.php:813 +msgid "Enter an email" +msgstr "" + +#: includes/process-purchase.php:710 +msgid "Passwords do not match" +msgstr "" + +#: includes/process-purchase.php:721 +msgid "Enter a password" +msgstr "" + +#: includes/process-purchase.php:723 +msgid "Confirm your password" +msgstr "" + +#: includes/process-purchase.php:750 +msgid "You must log in or register to complete your purchase" +msgstr "" + +#: includes/process-purchase.php:787 +msgid "You must be logged into an account to purchase" +msgstr "" + +#: includes/process-purchase.php:804 +msgid "You cannot use that email address at this time." +msgstr "" + +#: includes/process-purchase.php:1029 +msgid "The zip / postal code you entered for your billing address is invalid" +msgstr "" + +#: includes/process-purchase.php:1281 +msgid "An internal error has occurred, please try again or contact support." +msgstr "" + +#: includes/process-purchase.php:1300 +msgid "Your email address must be shorter than 100 characters." +msgstr "" + +#: includes/process-purchase.php:1332 +msgid "There was an error completing your purchase. Please try again." +msgstr "" + +#: includes/query-filters.php:51 +msgid "You do not have permission to view this file." +msgstr "" + +#: includes/refund-functions.php:26 +msgid "Refundable" +msgstr "" + +#: includes/refund-functions.php:27 +msgid "Non-Refundable" +msgstr "" + +#: includes/reports/class-init.php:211 +msgid "Legacy" +msgstr "" + +#: includes/reports/data/class-endpoint-view-registry.php:211 +msgid "All time" +msgstr "" + +#: includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php:162 +msgid "Monthly Sales Average" +msgstr "" + +#: includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php:163 +msgid "Monthly Earnings Average" +msgstr "" + +#: includes/reports/data/downloads/class-earnings-by-taxonomy-list-table.php:249 +msgid "No taxonomies found." +msgstr "" + +#: includes/reports/data/downloads/class-top-selling-downloads-list-table.php:58 +msgid "Net Earnings" +msgstr "" + +#: includes/reports/data/downloads/class-top-selling-downloads-list-table.php:155 +#: includes/reports/data/file-downloads/class-top-five-most-downloaded-list-table.php:169 +msgid "No downloads found." +msgstr "" + +#: includes/reports/data/payment-gateways/class-gateway-stats-list-table.php:79 +msgid "Refunds Issued" +msgstr "" + +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:75 +msgid "Country/Region" +msgstr "" + +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:76 +msgid "From" +msgstr "" + +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:77 +msgid "To" +msgstr "" + +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:78 +msgid "Gross" +msgstr "" + +#: includes/reports/data/taxes/class-tax-collected-by-location-list-table.php:181 +msgid "Global Rate" +msgstr "" + +#: includes/reports/reports-functions.php:328 +#: includes/reports/reports-functions.php:1478 +msgid "Exclude Taxes" +msgstr "" + +#: includes/reports/reports-functions.php:332 +msgid "Gateways" +msgstr "" + +#: includes/reports/reports-functions.php:340 +msgid "Regions" +msgstr "" + +#: includes/reports/reports-functions.php:344 +msgid "Countries" +msgstr "" + +#: includes/reports/reports-functions.php:348 +msgid "Currencies" +msgstr "" + +#: includes/reports/reports-functions.php:494 +msgid "Custom" +msgstr "" + +#: includes/reports/reports-functions.php:499 +msgid "Last 30 Days" +msgstr "" + +#: includes/reports/reports-functions.php:500 +msgid "Month to Date" +msgstr "" + +#: includes/reports/reports-functions.php:502 +msgid "Quarter to Date" +msgstr "" + +#: includes/reports/reports-functions.php:504 +msgid "Year to Date" +msgstr "" + +#: includes/reports/reports-functions.php:566 +msgid "Previous period" +msgstr "" + +#: includes/reports/reports-functions.php:567 +msgid "Previous month" +msgstr "" + +#: includes/reports/reports-functions.php:568 +msgid "Previous quarter" +msgstr "" + +#: includes/reports/reports-functions.php:569 +msgid "Previous year" +msgstr "" + +#: includes/reports/reports-functions.php:1283 +msgid "Chart time zone: %s" +msgstr "" + +#: includes/reports/reports-functions.php:1384 +msgid "compared to" +msgstr "" + +#: includes/reports/reports-functions.php:1647 +msgid "%s - Converted" +msgstr "" + +#: includes/reports/reports-functions.php:1784 +msgid "Try the Sales/Earnings iOS App!" +msgstr "" + +#: includes/scripts.php:165 +msgid "Please select a payment method" +msgstr "" + +#: includes/scripts.php:166 +msgid "Please enter a discount code" +msgstr "" + +#: includes/scripts.php:168 +msgid "Discount Applied" +msgstr "" + +#: includes/scripts.php:169 +msgid "Please enter an email address before applying a discount code" +msgstr "" + +#: includes/scripts.php:170 +msgid "Please enter a username before applying a discount code" +msgstr "" + +#: includes/scripts.php:171 +msgid "Please Wait..." +msgstr "" + +#: includes/scripts.php:197 +msgid "You have already added this item to your cart" +msgstr "" + +#: includes/scripts.php:199 +#: includes/template-functions.php:193 +msgid "Loading" +msgstr "" + +#: includes/scripts.php:505 +msgid "Add New Download" +msgstr "" + +#: includes/scripts.php:506 +msgid "Use This File" +msgstr "" + +#: includes/scripts.php:507 +msgid "Sorry, not available for variable priced products." +msgstr "" + +#: includes/scripts.php:508 +msgid "Are you sure you want to delete this item?" +msgstr "" + +#: includes/scripts.php:509 +msgid "Are you sure you want to delete this adjustment?" +msgstr "" + +#: includes/scripts.php:510 +msgid "Are you sure you want to delete this note?" +msgstr "" + +#: includes/scripts.php:511 +msgid "Are you sure you want to delete this tax rate?" +msgstr "" + +#: includes/scripts.php:512 +msgid "Are you sure you want to revoke this API key?" +msgstr "" + +#: includes/scripts.php:513 +msgid "Are you sure you want to regenerate this API key?" +msgstr "" + +#: includes/scripts.php:514 +msgid "Are you sure you want to resend the purchase receipt?" +msgstr "" + +#: includes/scripts.php:515 +msgid "Are you sure you want to disconnect the WordPress user from this customer record?" +msgstr "" + +#: includes/scripts.php:516 +msgid "Copy these links to your clipboard and give them to your customer" +msgstr "" + +#. translators: singular download label +#: includes/scripts.php:518 +msgid "Are you sure you want to delete this %s?" +msgstr "" + +#. translators: plural download label +#: includes/scripts.php:520 +msgid "Type to search %s" +msgstr "" + +#. translators: plural download label +#: includes/scripts.php:524 +msgid "Choose one or more %s" +msgstr "" + +#: includes/scripts.php:525 +msgid "You must have at least one price" +msgstr "" + +#: includes/scripts.php:526 +msgid "You must have at least one field" +msgstr "" + +#: includes/scripts.php:527 +msgid "Payments must contain at least one item" +msgstr "" + +#: includes/scripts.php:528 +msgid "No match for:" +msgstr "" + +#: includes/scripts.php:529 +msgid "Item price must be numeric" +msgstr "" + +#: includes/scripts.php:530 +msgid "Item tax must be numeric" +msgstr "" + +#: includes/scripts.php:531 +msgid "Quantity must be numeric" +msgstr "" + +#: includes/scripts.php:533 +msgid "You must choose a method." +msgstr "" + +#: includes/scripts.php:534 +msgid "Required fields not completed." +msgstr "" + +#: includes/scripts.php:535 +msgid "Are you sure you want to reset your store? This process is not reversible. Please be sure you have a recent backup." +msgstr "" + +#: includes/scripts.php:536 +msgid "We are sorry but your browser is not compatible with this kind of file upload. Please upgrade your browser." +msgstr "" + +#: includes/scripts.php:538 +msgid "Hide advanced settings" +msgstr "" + +#: includes/scripts.php:539 +msgid "There are no downloads attached to this payment" +msgstr "" + +#: includes/scripts.php:540 +msgid "Please wait …" +msgstr "" + +#: includes/scripts.php:541 +msgid "You must save your changes to send the test email." +msgstr "" + +#: includes/scripts.php:544 +msgid "Confirm" +msgstr "" + +#: includes/scripts.php:551 +msgid "The edd_use_35_media_ui filter is no longer supported." +msgstr "" + +#: includes/shortcodes.php:283 +msgid "Purchase All Items" +msgstr "" + +#. translators: plural download label +#: includes/shortcodes.php:615 +msgctxt "download post type name" +msgid "No %s found" +msgstr "" + +#: includes/shortcodes.php:706 +msgid "You must be logged in to view this payment receipt." +msgstr "" + +#: includes/shortcodes.php:718 +msgid "Receipt could not be retrieved, your purchase session has expired." +msgstr "" + +#: includes/shortcodes.php:802 +msgid "Your profile could not be updated. Please contact a site administrator." +msgstr "" + +#: includes/shortcodes.php:838 +msgid "The passwords you entered do not match. Please try again." +msgstr "" + +#: includes/shortcodes.php:849 +msgid "The email you entered is invalid. Please enter a valid email." +msgstr "" + +#: includes/shortcodes.php:860 +#: includes/users/register.php:76 +msgid "This email address is not available." +msgstr "" + +#: includes/shortcodes.php:995 +msgid "Error removing email address from profile. Please try again later." +msgstr "" + +#: includes/template-actions.php:19 +msgid "You need to log in to edit your profile." +msgstr "" + +#: includes/template-actions.php:32 +msgid "You are already logged in" +msgstr "" + +#. translators: the settings screen URL +#: includes/template-functions.php:64 +msgid "No checkout page has been configured. Visit Settings to set one." +msgstr "" + +#: includes/template-functions.php:89 +#: src/Admin/Settings/Tabs/Misc.php:107 +msgctxt "text shown on the Add to Cart Button when the product is already in the cart" +msgid "Checkout" +msgstr "" + +#: includes/template-functions.php:207 +msgid "Added to cart" +msgstr "" + +#. translators: the formatted tax rate +#: includes/template-functions.php:214 +msgid "Includes %1$s tax" +msgstr "" + +#. translators: the formatted tax rate +#: includes/template-functions.php:217 +msgid "Excluding %1$s tax" +msgstr "" + +#: includes/template-functions.php:472 +msgid "White" +msgstr "" + +#: includes/template-functions.php:476 +msgid "Gray" +msgstr "" + +#: includes/template-functions.php:480 +msgid "Blue" +msgstr "" + +#: includes/template-functions.php:484 +msgid "Red" +msgstr "" + +#: includes/template-functions.php:488 +msgid "Green" +msgstr "" + +#: includes/template-functions.php:492 +msgid "Yellow" +msgstr "" + +#: includes/template-functions.php:496 +msgid "Orange" +msgstr "" + +#: includes/template-functions.php:500 +msgid "Dark Gray" +msgstr "" + +#: includes/template-functions.php:504 +msgid "Inherit" +msgstr "" + +#: includes/template-functions.php:523 +msgid "Plain Text" +msgstr "" + +#: includes/upgrades/functions.php:142 +msgid "Tax Rates" +msgstr "" + +#: includes/upgrades/functions.php:158 +msgid "Customer Email Addresses" +msgstr "" + +#: includes/upgrades/functions.php:162 +msgid "Customer Notes" +msgstr "" + +#: includes/upgrades/functions.php:170 +msgid "Order Notes" +msgstr "" + +#. translators: Site name +#: includes/user-functions.php:585 +msgid "[%s] New User Registration" +msgstr "" + +#: includes/user-functions.php:586 +msgid "New user registration" +msgstr "" + +#. translators: the user email +#: includes/user-functions.php:590 +msgid "E-mail: %s" +msgstr "" + +#. translators: Site name +#: includes/user-functions.php:600 +msgid "[%s] Your username and password" +msgstr "" + +#: includes/user-functions.php:601 +msgid "Your account info" +msgstr "" + +#: includes/user-functions.php:605 +msgid "Password entered at checkout" +msgstr "" + +#: includes/user-functions.php:607 +msgid "Password entered at registration" +msgstr "" + +#. translators: %s: password message +#: includes/user-functions.php:611 +msgid "Password: %s" +msgstr "" + +#: includes/user-functions.php:616 +msgid "Click here to log in" +msgstr "" + +#. translators: %s: login URL +#: includes/user-functions.php:620 +msgid "To log in, visit: %s" +msgstr "" + +#: includes/user-functions.php:773 +#: includes/user-functions.php:774 +msgid "Verify your account" +msgstr "" + +#. translators: 1: User display name, 2: Site name, 3: Verification URL +#: includes/user-functions.php:777 +msgid "" +"Hello %1$s,\n" +"\n" +"\t\tYour account with %2$s needs to be verified before you can access your purchase history. Click here to verify your account.\n" +"\n" +"\t\tLink missing? Visit the following URL: %3$s" +msgstr "" + +#. translators: %s: URL to request a new verification link +#: includes/user-functions.php:908 +msgid "Sorry but your account verification link has expired. Click here to request a new verification URL." +msgstr "" + +#: includes/user-functions.php:941 +msgid "You must be logged in to verify your account." +msgstr "" + +#: includes/user-functions.php:945 +msgid "Your account has already been verified." +msgstr "" + +#: includes/user-functions.php:989 +msgid "Invalid verification token provided." +msgstr "" + +#: includes/user-functions.php:1119 +msgid "Public Key:" +msgstr "" + +#: includes/user-functions.php:1122 +msgid "Secret Key:" +msgstr "" + +#. translators: %s: iOS App URL +#: includes/user-functions.php:1148 +msgid "Easy Digital Downloads iOS App" +msgstr "" + +#: includes/user-functions.php:1156 +msgid "Add to iOS App" +msgstr "" + +#. translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. +#: includes/users/login.php:138 +msgid "Invalid username or password. %1$sReset Password%2$s" +msgstr "" + +#. translators: %s: Link to the referring page. +#: includes/users/lost-password.php:31 +msgid "Follow the instructions in the confirmation email you just received, then return to what you were doing." +msgstr "" + +#: includes/users/lost-password.php:123 +msgid "Your request could not be completed." +msgstr "" + +#: includes/users/lost-password.php:130 +msgid "You did it! Check your email for instructions on resetting your password." +msgstr "" + +#: includes/users/lost-password.php:176 +msgid "Someone has requested a password reset for the following account:" +msgstr "" + +#. translators: %s: Site name. +#: includes/users/lost-password.php:178 +msgid "Site Name: %s" +msgstr "" + +#: includes/users/lost-password.php:181 +msgid "If this was a mistake, ignore this email and nothing will happen." +msgstr "" + +#: includes/users/lost-password.php:182 +msgid "To reset your password, visit the following address:" +msgstr "" + +#. translators: %s: IP address of password reset requester. +#: includes/users/lost-password.php:198 +msgid "This password reset request originated from the IP address %s." +msgstr "" + +#: includes/users/lost-password.php:242 +msgid "Your password reset link has expired. Please request a new link below." +msgstr "" + +#: includes/users/lost-password.php:264 +#: includes/users/lost-password.php:269 +#: includes/users/lost-password.php:273 +#: includes/users/lost-password.php:279 +msgid "Invalid password reset request." +msgstr "" + +#: includes/users/lost-password.php:288 +#: includes/users/register.php:97 +msgid "The password cannot be a space or all spaces." +msgstr "" + +#: includes/users/lost-password.php:293 +#: includes/users/register.php:102 +msgid "The passwords do not match." +msgstr "" + +#: includes/users/lost-password.php:298 +msgid "Your password could not be reset." +msgstr "" + +#: includes/users/lost-password.php:307 +msgid "Your password was successfully reset." +msgstr "" + +#: includes/users/register.php:63 +msgid "Username already taken" +msgstr "" + +#: includes/users/register.php:84 +msgid "Invalid payment email" +msgstr "" + +#: includes/users/register.php:88 +msgid "Registration form validation failed." +msgstr "" + +#: includes/users/register.php:124 +msgid "Your account has been successfully created." +msgstr "" + +#: includes/widgets.php:38 +msgid "Downloads Cart" +msgstr "" + +#: includes/widgets.php:38 +msgid "Display the downloads shopping cart" +msgstr "" + +#: includes/widgets.php:91 +#: includes/widgets.php:202 +#: includes/widgets.php:387 +msgid "Title:" +msgstr "" + +#: includes/widgets.php:98 +msgid "Hide on Checkout Page" +msgstr "" + +#: includes/widgets.php:104 +msgid "Hide if cart is empty" +msgstr "" + +#: includes/widgets.php:148 +msgid "Downloads Categories / Tags" +msgstr "" + +#: includes/widgets.php:148 +msgid "Display the downloads categories or tags" +msgstr "" + +#: includes/widgets.php:206 +msgid "Taxonomy:" +msgstr "" + +#: includes/widgets.php:217 +msgid "Show Count:" +msgstr "" + +#: includes/widgets.php:221 +msgid "Hide Empty Categories:" +msgstr "" + +#: includes/widgets.php:243 +#: includes/widgets.php:364 +msgid "%s Details" +msgstr "" + +#: includes/widgets.php:245 +msgid "Display the details of a specific %s" +msgstr "" + +#: includes/widgets.php:392 +msgid "Display Type:" +msgstr "" + +#: includes/widgets.php:393 +msgid "Current" +msgstr "" + +#: includes/widgets.php:394 +msgid "Specific" +msgstr "" + +#: includes/widgets.php:400 +msgid "%s:" +msgstr "" + +#: includes/widgets.php:418 +msgid "%s ID" +msgstr "" + +#: includes/widgets.php:425 +msgid "Show %s Title" +msgstr "" + +#: includes/widgets.php:431 +#: includes/blocks/build/downloads/index.js:1 +msgid "Show Purchase Button" +msgstr "" + +#: includes/widgets.php:438 +#: includes/widgets.php:445 +msgid "Show %s" +msgstr "" + +#. translators: the plugin version +#: src/Admin/Extensions/Card.php:285 +msgid "Version: %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:279 +msgid "Log In to Your Account to Download %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:287 +msgid "Upgrade Today to Access %s!" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:303 +msgid "Activate %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:307 +msgid "Deactivate %s" +msgstr "" + +#. translators: The extension name. +#: src/Admin/Extensions/Extension.php:355 +msgid "Configure %s" +msgstr "" + +#. translators: the plural Downloads label. +#: src/Admin/Extensions/Extension.php:368 +msgid "View %s" +msgstr "" + +#: src/Admin/Extensions/ExtensionPage.php:41 +#: src/Admin/Extensions/ExtensionPage.php:46 +msgid "Search Extensions" +msgstr "" + +#: src/Admin/Extensions/ExtensionPage.php:93 +msgid "Available Extensions" +msgstr "" + +#: src/Admin/Extensions/ExtensionPage.php:107 +msgid "Refresh Extensions" +msgstr "" + +#. translators: the active pass name +#: src/Admin/Extensions/ExtensionPage.php:134 +msgid "Add functionality to your Easy Digital Downloads powered store with your %s." +msgstr "" + +#: src/Admin/Extensions/ExtensionPage.php:137 +msgid "Add functionality to your Easy Digital Downloads powered store." +msgstr "" + +#. translators: 1. opening anchor tag; 2. closing anchor tag. +#: src/Admin/Extensions/ExtensionPage.php:158 +msgid "Missing access to an extension? %1$sAdd your license key now%2$s." +msgstr "" + +#: src/Admin/Extensions/ExtensionPage.php:192 +msgid "Upgrade Now" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:74 +#: src/Admin/PassHandler/Settings.php:123 +#: src/Licensing/Settings.php:57 +msgid "Activating" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:75 +msgid "Installing" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:76 +#: src/Admin/Extensions/Extension_Manager.php:167 +msgid "Could not install the plugin. Please download and install it manually via Plugins > Add New." +msgstr "" + +#. translators: 1. opening anchor tag, do not translate; 2. closing anchor tag, do not translate +#: src/Admin/Extensions/Extension_Manager.php:79 +#: src/Admin/Extensions/Extension_Manager.php:170 +msgid "Could not install the extension. Please %1$sdownload it from your account%2$s and install it manually." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:84 +msgid "extensions found" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:85 +#: src/Admin/PassHandler/Settings.php:124 +#: src/Licensing/Settings.php:58 +msgid "Deactivating" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:145 +#: src/Admin/Extensions/Extension_Manager.php:249 +#: src/Admin/Extensions/Extension_Manager.php:329 +msgid "There was an error while performing your request." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:159 +msgid "Plugin installation is not available for you on this site." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:233 +msgid "Plugin installed." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:233 +msgid "Extension installed." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:255 +msgid "Plugin activation is not available for you on this site." +msgstr "" + +#. translators: "extension" or "plugin" as defined by $type +#: src/Admin/Extensions/Extension_Manager.php:272 +msgid "Could not activate the %s." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:295 +msgid "Plugin activated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:295 +msgid "Extension activated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:297 +#: src/Lite/Admin/PassHandler/Connect.php:91 +#: src/Lite/Admin/PassHandler/Connect.php:100 +#: src/Lite/Admin/PassHandler/Connect.php:146 +msgid "Plugin installed & activated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:297 +msgid "Extension installed & activated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:306 +#: src/Pro/Admin/Extensions/Buttons.php:24 +msgid "Activated" +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:335 +msgid "Plugin deactivation is not available for you on this site." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:354 +msgid "Plugin deactivated." +msgstr "" + +#: src/Admin/Extensions/Extension_Manager.php:354 +msgid "Extension deactivated." +msgstr "" + +#: src/Admin/Extensions/Legacy.php:53 +msgid "Inactive — part of EDD" +msgstr "" + +#: src/Admin/Onboarding/Notice.php:33 +msgid "Wait! We still haven't finished the setup." +msgstr "" + +#: src/Admin/Onboarding/Notice.php:34 +msgid "Our Quick Setup Wizard can help you get you ready to sell your first product in minutes. If you choose to exit now, you will need to manually configure key aspects of your store. You can always restart this wizard by going to Downloads > Tools, and clicking on the 'Restart Setup Wizard' button." +msgstr "" + +#: src/Admin/Onboarding/Notice.php:36 +msgid "Let's finish now!" +msgstr "" + +#: src/Admin/Onboarding/Notice.php:37 +msgid "No thanks, I'll do it all myself." +msgstr "" + +#: src/Admin/Onboarding/Steps/ConfigureEmails.php:48 +msgid "Purchase Receipt Email" +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:30 +msgid "The world’s most powerful and easy to use payment gateway." +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:33 +msgid "Stripe Features we can add:" +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:36 +msgid "Secure checkout" +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:37 +msgid "Accept all major credit cards" +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:38 +msgid "Supports subscriptions" +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:39 +msgid "Fraud prevention tools" +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:40 +msgid "Apple Pay & Google Pay" +msgstr "" + +#: src/Admin/Onboarding/Steps/PaymentMethods.php:41 +msgid "And more…" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:32 +msgid "Product details" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:33 +msgid "We'll get started with some basic information. Don't worry, you can add more details later. When you're finished here, the product will be saved as a draft so you can finish up later." +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:36 +msgid "Product Name" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:48 +msgid "Set image" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:48 +msgid "Set Image" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:54 +msgid "Pricing Options" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:58 +msgid "Single price" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:59 +msgid "Variable price" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:64 +msgid "Product Price" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:118 +msgid "Add your first file" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:124 +msgid "Ready to add your first downloadable file to your product? Great! These files will be protected and only available to people who purchase your product. Not ready yet? No problem, you can always add and update files later." +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:136 +msgid "Congratulations!" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:137 +msgid "You've set up your store and your first product has been created." +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:138 +msgid "Edit My Product" +msgstr "" + +#: src/Admin/Onboarding/Steps/Products.php:139 +msgid "Explore Extensions" +msgstr "" + +#. translators: the plugin name. +#: src/Admin/Onboarding/Steps/Tools.php:68 +msgid "%s is already active." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:74 +msgid "You already have a solution installed for this feature." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:87 +msgid "Based on your selection above, the following plugins will be installed:" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:91 +msgid "Some features were not able to be installed!" +msgstr "" + +#. translators: list of plugins that were not able to be installed or activated +#: src/Admin/Onboarding/Steps/Tools.php:96 +msgid "Don't worry, everything will still work without them! You can install %s later by going to Plugins > Add New." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:101 +msgid "Continue" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:107 +msgid "Plugins were successfully installed!" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:123 +msgid "Essential eCommerce Features" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:124 +msgid "Get all the essential eCommerce features to sell digital products with WordPress." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:135 +msgid "Optimize Checkout" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:136 +msgid "Improve the checkout experience by auto-creating user accounts for new customers." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:145 +msgid "Reliable Email Delivery" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:146 +msgid "Email deliverability is one of the most important services for an eCommerce store. Don’t leave your customers in the dark." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:158 +msgid "Analytics Tools" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:159 +msgid "Get the #1 analytics plugin to see useful information about your visitors right inside your WordPress dashboard." +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:175 +msgid "SEO" +msgstr "" + +#: src/Admin/Onboarding/Steps/Tools.php:176 +msgid "Get the tools used by millions of smart business owners to analyze and optimize their store’s traffic with SEO." +msgstr "" + +#: src/Admin/Onboarding/Tools.php:34 +#: src/Admin/Onboarding/Tools.php:37 +msgid "Restart the Setup Wizard" +msgstr "" + +#: src/Admin/Onboarding/Tools.php:36 +msgid "If you would like to revisit our setup wizard, you can at any time." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:125 +msgid "Setup" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:228 +msgid "Business" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:229 +msgid "Tell us a little bit about your business." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:230 +msgid "Where is your business located? This helps Easy Digital Downloads configure the checkout and receipt templates." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:234 +msgid "Payment Methods" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:235 +msgid "Start accepting payments today!" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:241 +msgid "Configure your Receipts" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:242 +msgid "Customize the purchase receipt that your customers will receive." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:247 +msgid "Conversion and Optimization tools" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:248 +msgid "We have selected our recommended tools and features to help boost conversions and optimize your digital store." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:253 +msgid "What are you going to sell?" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:254 +msgid "Let's get started creating your first awesome product." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:274 +msgid "Let's get started with your next great product." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:301 +msgid "Unknown Onboarding Step." +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:301 +msgid "Onboarding Wizard" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:443 +msgid "Welcome, and thanks for choosing us!" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:444 +msgid "Easy Digital Downloads setup is fast and easy. We'll walk you through the quick initial process. And don't worry. You can go back and change anything you do – at anytime. Nothing's permanent (unless you want it to be). So feel free to explore!" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:445 +msgid "Get Started" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:446 +msgid "Creators ❤️ Easy Digital Downloads" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:535 +msgid "Go Back" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:539 +msgid "Skip this step" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:540 +msgid "Save & Continue" +msgstr "" + +#: src/Admin/Onboarding/Wizard.php:545 +msgid "Close and Exit Without Saving" +msgstr "" + +#: src/Admin/PassHandler/Ajax.php:58 +#: src/Admin/PassHandler/Ajax.php:128 +#: src/Admin/PassHandler/Ajax.php:162 +#: src/Pro/Admin/PassHandler/Ajax.php:37 +msgid "You do not have permission to manage this pass." +msgstr "" + +#: src/Admin/PassHandler/Ajax.php:67 +msgid "Please enter a license key." +msgstr "" + +#: src/Admin/PassHandler/Ajax.php:146 +msgid "Your pass was successfully deactivated." +msgstr "" + +#: src/Admin/PassHandler/Ajax.php:172 +msgid "Pass key deleted." +msgstr "" + +#: src/Admin/PassHandler/Handler.php:109 +msgid "Activate License" +msgstr "" + +#: src/Admin/PassHandler/Handler.php:116 +msgid "Verify License Key" +msgstr "" + +#: src/Admin/PassHandler/Handler.php:169 +msgid "We could not reach the EDD server." +msgstr "" + +#: src/Admin/PassHandler/Settings.php:60 +msgid "License Key" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:70 +msgid "Paste license key" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:92 +msgid "We see that you have an active pass--just hit Verify to get started with (Pro)." +msgstr "" + +#: src/Admin/PassHandler/Settings.php:122 +msgid "Verifying" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:125 +msgid "Just a moment while we connect your site and upgrade you to (Pro)." +msgstr "" + +#: src/Admin/PassHandler/Settings.php:149 +msgid "Easy Digital Downloads (Pro) Key" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:150 +msgid "Go Pro With Easy Digital Downloads" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:167 +msgid "You're using Easy Digital Downloads — no license needed. Enjoy!" +msgstr "" + +#. translators: 1. opening link tag; do not translate; 2. closing link tag; do not translate. +#: src/Admin/PassHandler/Settings.php:182 +msgid "To unlock more features, consider %1$supgrading to Pro%2$s." +msgstr "" + +#: src/Admin/PassHandler/Settings.php:189 +msgid "As a valued EDD user you receive 50% off, automatically applied at checkout!" +msgstr "" + +#: src/Admin/PassHandler/Settings.php:190 +msgid "Already purchased? Simply enter your license key to enable EDD (Pro)." +msgstr "" + +#: src/Admin/Pass_Manager.php:363 +msgid "Personal Pass" +msgstr "" + +#: src/Admin/Pass_Manager.php:364 +msgid "Extended Pass" +msgstr "" + +#: src/Admin/Pass_Manager.php:365 +msgid "Professional Pass" +msgstr "" + +#: src/Admin/Pass_Manager.php:366 +msgid "All Access Pass" +msgstr "" + +#: src/Admin/Pass_Manager.php:367 +msgid "Lifetime All Access Pass" +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag +#: src/Admin/Promos/Notices/License_Upgrade_Notice.php:167 +msgid "You are using the free version of Easy Digital Downloads. %1$sPurchase a pass%2$s to get email marketing tools and recurring payments. Already have a Pass? %3$sActivate it now%4$s" +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag +#: src/Admin/Promos/Notices/License_Upgrade_Notice.php:184 +msgid "For access to additional Easy Digital Downloads extensions to grow your store, consider %1$spurchasing a pass%2$s." +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag +#: src/Admin/Promos/Notices/License_Upgrade_Notice.php:199 +msgid "You are using Easy Digital Downloads with a Personal Pass. Consider %1$supgrading%2$s to get recurring payments and more." +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag +#: src/Admin/Promos/Notices/License_Upgrade_Notice.php:216 +msgid "Grow your business and make more money with affiliate marketing. %1$sGet AffiliateWP%2$s" +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag +#: src/Admin/Promos/Notices/License_Upgrade_Notice.php:223 +msgid "Gain access to powerful insights to grow your traffic and revenue. %1$sGet MonsterInsights%2$s" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:38 +msgid "Thanks for your interest in Easy Digital Downloads (Pro)!" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:39 +msgid "After purchasing a license, just enter your license key on the EDD Settings page. This will let your site automatically upgrade to Easy Digital Downloads (Pro)!" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:40 +msgid "(Don't worry, all your products, orders, and settings will be preserved.)" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:69 +msgid "Pro Payment Gateways" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:70 +msgid "Email Marketing Integrations" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:71 +msgid "Sell Subscriptions" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:72 +msgid "Build Lead Magnets" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:73 +msgid "Advanced Bundle Features" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:74 +msgid "Automate Your Business" +msgstr "" + +#: src/Admin/Promos/Notices/Lite.php:103 +msgid "Have a question? Let us know!" +msgstr "" + +#: src/Admin/Promos/Notices/Notice.php:118 +msgid "Dismiss notice" +msgstr "" + +#: src/Admin/Promos/PromoHandler.php:135 +msgid "Missing notice ID." +msgstr "" + +#: src/Admin/Settings/EmailMarketing.php:64 +msgid "Email Marketing" +msgstr "" + +#. translators: the product name +#: src/Admin/Settings/EmailMarketing.php:80 +msgid "Get %s Today!" +msgstr "" + +#: src/Admin/Settings/EmailMarketing.php:95 +msgid "Looks like you have an email marketing extension installed, but we support more providers!" +msgstr "" + +#: src/Admin/Settings/Recurring.php:110 +msgid "Subscriptions" +msgstr "" + +#: src/Admin/Settings/Reviews.php:115 +msgid "Reviews" +msgstr "" + +#: src/Admin/Settings/Reviews.php:135 +msgid "Product Reviews" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:36 +msgid "Email Configuration" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:41 +msgid "Template" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:42 +msgid "Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" to see the new template." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:48 +msgid "Logo" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:49 +msgid "Upload or choose a logo to be displayed at the top of sales receipt emails. Displayed on HTML emails only." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:54 +msgid "From Name" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:55 +msgid "This should be your site or shop name. Defaults to Site Title if empty." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:62 +msgid "From Email" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:63 +msgid "This will act as the \"from\" and \"reply-to\" addresses." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:84 +msgid "Purchase Email Subject" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:85 +msgid "Enter the subject line for the purchase receipt email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:91 +msgid "Purchase Email Heading" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:92 +msgid "Enter the heading for the purchase receipt email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:99 +msgid "Text to email customers after completing a purchase. Personalize with HTML and {tag} markers." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:107 +msgid "Sale Notification Subject" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:108 +msgid "Enter the subject line for the sale notification email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:114 +msgid "Sale Notification Heading" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:115 +msgid "Enter the heading for the sale notification email." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:121 +msgid "Sale Notification" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:122 +msgid "Text to email as a notification for every completed purchase. Personalize with HTML and {tag} markers." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:128 +msgid "Sale Notification Emails" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:129 +msgid "Enter the email address(es) that should receive a notification anytime a sale is made. One per line." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:135 +msgid "Disable Admin Notifications" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:136 +msgid "Check this box if you do not want to receive sales notification emails." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:164 +msgid "The summary email is not yet scheduled. Save the settings to manually schedule it." +msgstr "" + +#. Translators: formatted date +#: src/Admin/Settings/Tabs/Emails.php:168 +msgid "The next summary email is scheduled to send on %s." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:174 +msgid "Email Frequency" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:179 +msgid "Weekly" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:180 +msgid "Monthly" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:185 +msgid "Email Recipient" +msgstr "" + +#. Translators: email +#: src/Admin/Settings/Tabs/Emails.php:190 +msgid "Administrator: %s" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:191 +#: src/Admin/Settings/Tabs/Emails.php:197 +msgid "Custom Recipients" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:198 +msgid "Enter the email address(es) that should receive Email Summaries. One per line." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:213 +msgid "Disable Email Summary" +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:214 +msgid "Learn more about Email Summaries." +msgstr "" + +#: src/Admin/Settings/Tabs/Emails.php:215 +msgid "Check this box to disable Email Summaries." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:40 +msgid "Active Gateways" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:41 +msgid "Choose the payment gateways you want to enable." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:47 +msgid "Default Gateway" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:48 +msgid "Choose the gateway your checkout will use by default.
    If you choose Automatic, the first enabled gateway from the Active Gateways will be used." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:54 +msgid "Payment Method Icons" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:55 +msgid "Display icons for the selected payment methods." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:55 +msgid "You will also need to configure your gateway settings if you are accepting credit cards." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:72 +msgid "Enforce SSL on Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:73 +msgid "Enforced" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:74 +msgid "Redirect all customers to the secure checkout page. You must have an SSL certificate installed to use this option." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:79 +#: src/Admin/Settings/Tabs/Gateways.php:82 +msgid "Redirect to Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:80 +msgid "Immediately redirect to checkout after adding an item to the cart?" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:83 +msgid "When enabled, once an item has been added to the cart, the customer will be redirected directly to your checkout page. This is useful for stores that sell single items." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:87 +#: src/Admin/Settings/Tabs/Gateways.php:90 +#: src/Admin/Settings/Tabs/Misc.php:114 +#: src/Admin/Settings/Tabs/Misc.php:116 +msgid "Require Login" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:88 +msgid "Require that users be logged-in to purchase files." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:91 +msgid "You can require that customers create and login to user accounts prior to purchasing from your store by enabling this option. When unchecked, users can purchase without being logged in by using their name and email address." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:95 +msgid "Show Register / Login Form" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:96 +msgid "Display the registration and login forms on the checkout page for non-logged-in users." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:100 +msgid "Registration and Login Forms" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:101 +msgid "Registration Form Only" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:102 +msgid "Login Form Only" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:108 +msgid "Enable Cart Saving" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:109 +msgid "Check this to enable cart saving on the checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:111 +msgid "Cart Saving" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:112 +msgid "Cart saving allows shoppers to create a temporary link to their current shopping cart so they can come back to it later, or share it with someone." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:116 +#: src/Admin/Settings/Tabs/Gateways.php:119 +msgid "Moderation" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:120 +msgid "It is sometimes necessary to temporarily prevent certain potential customers from checking out. Use these settings to control who can make purchases." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:125 +msgid "Emails placed in the box above will not be allowed to make purchases." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:125 +msgid "One per line, enter: email addresses, domains (@example.com), or TLDs (.gov)." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:127 +msgid "@example.com" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:137 +msgid "As a shop owner, sometimes refunds are necessary. Use these settings to decide how refunds will work in your shop." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:141 +msgid "Default Status" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:142 +msgid "This will be the store default. It can be changed at a per-product level." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:150 +msgid "Number of days (after a sale) when refunds can be processed.
    Default is 30 days. Set to 0 for infinity. It can be changed at a per-product level." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:175 +msgid "While test mode is enabled, no live transactions are processed.
    Use test mode in conjunction with the sandbox/test account for the payment gateways to test." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:189 +msgid "Forced Test Mode" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:190 +msgid "You currently cannot modify the Test Mode setting, as the 'EDD_TEST_MODE' constant has been defined as 'true' or the edd_is_test_mode filter is being forced to 'true'." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:206 +msgid "Enable SKU Entry" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:207 +msgid "Check this box to allow entry of product SKUs." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:208 +msgid "SKUs will be shown on purchase receipt and exported purchase histories." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:213 +msgid "Enable Sequental Numbering" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:214 +msgid "Check this box to enable sequential order numbers." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:215 +msgid "Does not impact previous orders. Future orders will be sequential." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:221 +msgid "Sequential Number Prefix" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:222 +msgid "A prefix to prepend to all sequential order numbers." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:227 +msgid "Sequential Number Postfix" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:228 +msgid "A postfix to append to all sequential order numbers." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:236 +msgid "Advanced Order Numbers" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:255 +msgid "Sequential Starting Number" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:256 +msgid "The number at which the sequence should begin." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:264 +msgid "Once sequential order numbering is active, the starting number cannot be updated to an order number that's smaller than the highest order number. Update this with care." +msgstr "" + +#. translators: %s: next order number, wrapped in code tags +#: src/Admin/Settings/Tabs/Gateways.php:268 +msgid "The next order number will be %s." +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:283 +msgid "Gain access to even more control over your order numbering!" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:285 +msgid "Track free orders in a separate sequential series" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:286 +msgid "Assign temporary numbers to incomplete orders" +msgstr "" + +#: src/Admin/Settings/Tabs/Gateways.php:287 +msgid "Abandoned orders do not interrupt the complete order series" +msgstr "" + +#. translators: %1$s: opening anchor tag, %2$s: closing anchor tag +#: src/Admin/Settings/Tabs/Gateways.php:309 +msgid "Access %1$sAdvanced Sequential Order Numbers%2$s today." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:39 +msgid "Business Info" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:42 +msgid "Business Information" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:43 +msgid "Easy Digital Downloads uses the following business information for things like pre-populating tax fields, and connecting third-party services with the same information." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:47 +msgid "Business Name" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:48 +msgid "The official (legal) name of your store. Defaults to Site Title if empty." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:55 +msgid "Business Type" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:56 +msgid "Choose \"Individual\" if you do not have an official/legal business ID, or \"Company\" if a registered business entity exists." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:59 +msgid "Individual" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:60 +msgid "Company" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:65 +msgid "Business Address" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:71 +msgid "Business Address (Extra)" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:77 +msgid "Business City" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:83 +msgid "Business Postal Code" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:90 +msgid "Business Country" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:95 +msgid "Select a country" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:102 +msgid "Business Region" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:106 +msgid "Select a region" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:115 +msgid "Page Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:116 +msgid "Easy Digital Downloads uses the pages below for handling the display of checkout, purchase confirmation, purchase history, and purchase failures. If pages are deleted or removed in some way, they can be recreated manually from the Pages menu. When re-creating the pages, enter the shortcode shown in the page content area." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:120 +msgid "Primary Checkout Page" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:121 +msgid "This is the checkout page where buyers will complete their purchases.
    The [download_checkout] shortcode must be on this page." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:130 +msgid "This is the page buyers are sent to after completing their purchases.
    The [edd_receipt] shortcode should be on this page." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:139 +msgid "This is the page buyers are sent to if their transaction is cancelled or fails." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:148 +msgid "This page shows a complete purchase history for the current user, including download links.
    The [purchase_history] shortcode should be on this page." +msgstr "" + +#. translators: %s: home URL +#: src/Admin/Settings/Tabs/General.php:159 +msgid "If a customer logs in using the [edd_login] shortcode, this is the page they will be redirected to.
    Note: override using the redirect shortcode attribute: [edd_login redirect=\"%s\"]." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:174 +msgid "Currency Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:175 +msgid "Different countries use different formatting for their currency. You will want to pick what most of your users will expect to use." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:180 +msgid "Choose your currency. Note that some payment gateways have currency restrictions." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:187 +msgid "Currency Position" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:188 +msgid "Choose the location of the currency sign." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:191 +msgid "Before ($10)" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:192 +msgid "After (10$)" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:197 +msgid "Thousandths Separator" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:198 +msgid "The symbol to separate thousandths. Usually , or .." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:207 +msgid "Decimal Separator" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:208 +msgid "The symbol to separate decimal points. Usually , or .." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:222 +msgid "API Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:223 +msgid "The Easy Digital Downloads REST API provides access to store data through our API endpoints. Enable this setting if you would like all user accounts to be able to generate their own API keys." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:227 +msgid "Allow User Keys" +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:228 +msgid "Check this box to allow all users to generate API keys." +msgstr "" + +#: src/Admin/Settings/Tabs/General.php:229 +msgid "Users who can manage_shop_settings are always allowed to generate keys." +msgstr "" + +#. translators: %s: API documentation URL +#: src/Admin/Settings/Tabs/General.php:236 +msgid "Visit the REST API documentation for further information." +msgstr "" + +#: src/Admin/Settings/Tabs/Marketing.php:36 +msgid "Abandoned Cart Recovery" +msgstr "" + +#: src/Admin/Settings/Tabs/Marketing.php:42 +msgid "Multiple Discounts" +msgstr "" + +#: src/Admin/Settings/Tabs/Marketing.php:43 +msgid "Allow customers to use multiple discounts on the same purchase?" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:37 +msgid "Debug Mode" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:39 +msgid "Check this box to enable Debug Mode." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:44 +msgid "Disable Styles" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:45 +msgid "Check this box to disable all included styling." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:46 +msgid "This includes buttons, checkout fields, product pages, and all other elements" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:48 +msgid "Disabling Styles" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:49 +msgid "If your theme has a complete custom CSS file for Easy Digital Downloads, you may wish to disable our default styles. This is not recommended unless you're sure your theme has a complete custom CSS." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:53 +msgid "Cart Item Quantities" +msgstr "" + +#. translators: %s is the plural label for downloads +#: src/Admin/Settings/Tabs/Misc.php:55 +msgid "Allow quantities to be adjusted when adding %s to the cart, and while viewing the checkout cart." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:60 +msgid "Remove Data on Uninstall" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:61 +msgid "Check this box if you would like EDD to completely remove all of its data when the plugin is deleted." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:68 +msgid "Default Button Style" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:69 +msgid "Choose the style you want to use for the buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:75 +msgid "Default Button Color" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:76 +msgid "Choose the color you want to use for the buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:83 +msgid "Complete Purchase Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:84 +msgid "The button label for completing a purchase." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:90 +msgid "Complete Free Purchase Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:91 +msgid "The button label for completing a free purchase." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:97 +msgid "Add to Cart Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:98 +msgid "Text shown on the Add to Cart Buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:104 +msgid "Checkout Button Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:105 +msgid "Text shown on the Add to Cart Button when the product is already in the cart." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:115 +msgid "Require a user to login before file download links deliver the file." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:117 +msgid "Download links expire after the link expiration setting, but you can restrict file downloads to only logged in users. Note: This may affect links from purchase receipts and customers if you have guest checkout enabled." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:122 +#: src/Admin/Settings/Tabs/Misc.php:125 +msgid "Download Method" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:123 +msgid "Select the file download method. Note, not all methods work on all servers." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:126 +msgid "Due to its consistency in multiple platforms and better file protection, 'forced' is the default method. Because Easy Digital Downloads uses PHP to process the file with the 'forced' method, larger files can cause problems with delivery, resulting in hitting the 'max execution time' of the server. If users are getting 404 or 403 errors when trying to access their purchased files when using the 'forced' method, changing to the 'redirect' method can help resolve this." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:128 +msgid "Forced" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:129 +msgid "Redirect" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:134 +msgid "Symbolically Link Files" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:135 +msgid "Check this if you are delivering really large files or having problems with file downloads completing." +msgstr "" + +#. translators: %s is the singular label for a download +#: src/Admin/Settings/Tabs/Misc.php:142 +msgid "The maximum number of times files can be downloaded for purchases. Can be overwritten for each %s." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:145 +msgid "File Download Limits" +msgstr "" + +#. translators: %s is the singular label for a download +#: src/Admin/Settings/Tabs/Misc.php:147 +msgid "Set the global default for the number of times a customer can download items they purchase. Using a value of 0 is unlimited. This can be defined on a %s-specific level as well. Download limits can also be reset for an individual purchase." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:151 +#: src/Admin/Settings/Tabs/Misc.php:153 +msgid "Download Link Expiration" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:152 +msgid "How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:154 +msgid "When a customer receives a link to their downloads via email, in their receipt, or in their purchase history, the link will only be valid for the timeframe (in hours) defined in this setting. Sending a new purchase receipt or visiting the account page will re-generate a valid link for the customer." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:162 +msgid "Disable Redownload" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:163 +msgid "Check this if you do not want to allow users to redownload items from their purchase history." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:179 +msgid "Buy Now Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:180 +msgid "Text shown on the Buy Now Buttons." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:190 +msgid "Buy Now Disabled" +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:191 +msgid "Buy Now buttons are only available for stores that have a single supported gateway active and that do not use taxes." +msgstr "" + +#: src/Admin/Settings/Tabs/Misc.php:210 +msgid "View the Log" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:39 +msgid "Privacy Policy Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:40 +msgid "Depending on legal and regulatory requirements, it may be necessary for your site to show a checkbox for agreement to a privacy policy." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:44 +#: src/Admin/Settings/Tabs/Privacy.php:79 +msgid "Agreement" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:45 +msgid "Check this box to show an \"Agree to Privacy Policy\" checkbox on checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:46 +msgid "Customers must agree to your privacy policy before purchasing." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:51 +#: src/Admin/Settings/Tabs/Privacy.php:95 +msgid "Agreement Label" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:52 +msgid "Label for the \"Agree to Privacy Policy\" checkbox." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:54 +msgid "I agree to the privacy policy" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:59 +msgid "Privacy Policy on Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:60 +msgid "Display the entire Privacy Policy at checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:60 +#: src/Admin/Settings/Tabs/Privacy.php:62 +msgid "Set your Privacy Policy here" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:62 +msgid "Display your Privacy Policy on checkout." +msgstr "" + +#. translators: %1$s: Opening anchor tag, %2$s: Closing anchor tag. +#: src/Admin/Settings/Tabs/Privacy.php:64 +msgid "Need help creating a Privacy Policy? We recommend %1$sTermageddon%2$s." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:74 +msgid "Terms & Agreements Settings" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:75 +msgid "Depending on legal and regulatory requirements, it may be necessary for your site to show checkbox for agreement to terms." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:80 +msgid "Check this box to show an \"Agree to Terms\" checkbox on checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:82 +msgid "Check this to show an agree to terms on checkout that users must agree to before purchasing." +msgstr "" + +#. translators: 1: Opening anchor tag, 2: Closing anchor tag. +#: src/Admin/Settings/Tabs/Privacy.php:86 +msgid "Need help creating a Terms of Agreement? We recommend using %1$sTermageddon%2$s." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:96 +msgid "Label for the \"Agree to Terms\" checkbox." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:97 +msgid "I agree to the terms" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:103 +msgid "Agreement Text" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:121 +msgid "Order Statuses" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:123 +msgid "When a user requests to be anonymized or removed from a site, these are the actions that will be taken on payments associated with their customer, by status." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:124 +msgid "What settings should I use?" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:125 +msgid "By default, Easy Digital Downloads sets suggested actions based on the Payment Status. These are purely recommendations, and you may need to change them to suit your store's needs. If you are unsure, you can safely leave these settings as is." +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:129 +msgid "Rules" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:131 +msgid "When a user wants their order history anonymized or removed, the following rules will be used:" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:136 +msgid "Do Nothing" +msgstr "" + +#: src/Admin/Settings/Tabs/Privacy.php:137 +msgid "Anonymize" +msgstr "" + +#. translators: %s: Tab class name. +#: src/Admin/Settings/Tabs/Tab.php:45 +msgid "The %s settings class is missing the required ID property." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:38 +msgid "Check this to enable taxes on purchases." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:40 +msgid "Enabling Taxes" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:41 +msgid "With taxes enabled, customers will be taxed based on the rates you define, and are required to input their address on checkout so rates can be calculated accordingly." +msgstr "" + +#. translators: %s - tax setup documentation URL. +#: src/Admin/Settings/Tabs/Taxes.php:47 +msgid "Visit the Tax setup documentation for further information.

    If you need VAT support, there are options listed on the documentation page.

    " +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:52 +msgid "Prices Include Tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:53 +msgid "This option affects how you enter prices." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:57 +msgid "Yes, I will enter prices inclusive of tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:58 +msgid "No, I will enter prices exclusive of tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:60 +msgid "Prices Inclusive of Tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:61 +msgid "When using prices inclusive of tax, you will be entering your prices as the total amount you want a customer to pay for the download, including tax. Easy Digital Downloads will calculate the proper amount to tax the customer for the defined total price." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:65 +msgid "Show Tax Rate on Prices" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:66 +msgid "Show" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:67 +msgid "Some countries require a notice that product prices include tax." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:72 +msgid "Show in Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:73 +msgid "Should prices on the checkout page be shown with or without tax?" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:77 +msgid "Including tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:78 +msgid "Excluding tax" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:80 +msgid "Taxes Displayed for Products on Checkout" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:81 +msgid "This option will determine whether the product price displays with or without tax on checkout." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:99 +msgid "Regional Rates" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:100 +msgid "Configure rates for each region you wish to collect sales tax in." +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:115 +msgid "Default Rate" +msgstr "" + +#: src/Admin/Settings/Tabs/Taxes.php:117 +msgid "This setting is no longer used in this version of Easy Digital Downloads. We have migrated any fallback tax rates for you to verify below. Click \"Save Changes\" to dismiss this notice." +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:69 +msgid "Improve Email Deliverability" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:88 +msgid "WP Mail SMTP allows you to easily set up WordPress to use a trusted provider to reliably send emails, including sales notifications." +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:120 +msgid "Install & Activate WP Mail SMTP" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:125 +msgid "Activate WP Mail SMTP" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:143 +msgid "Configure WP Mail SMTP" +msgstr "" + +#: src/Admin/Settings/WP_SMTP.php:147 +msgid "Run the WP Mail SMTP Setup Wizard" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:17 +msgid "EDD Checkout Page" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:22 +msgid "Protected Download Files" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:28 +msgid "Enabled Gateways" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:34 +msgid "Cron Events" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:56 +msgid "You have a checkout page set" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:61 +msgid "Your checkout page is set up and ready to process orders." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:69 +msgid "Your checkout page is missing" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:76 +msgid "Easy Digital Downloads requires a specific checkout page to be set to easily handle user interactions." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:87 +msgid "Fix the Checkout Page" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:91 +msgid "Your checkout page is using the legacy shortcode" +msgstr "" + +#. translators: %1$s - opening tag, %2$s - closing tag +#: src/Admin/SiteHealth/Direct.php:98 +msgid "Your checkout page is configured; however, it is currently using the legacy %1$s[download_checkout]%2$s shortcode. We recommend changing your checkout to use the EDD Checkout Block." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:108 +msgid "Edit Checkout Page" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:123 +msgid "Your download files are protected" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:128 +msgid "Your checkout page is a critical part of your store." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:151 +msgid "Your download files may not be protected" +msgstr "" + +#. translators: 1. opening link tag; 2. closing link tag +#: src/Admin/SiteHealth/Direct.php:158 +msgid "To ensure the best protection, you should use this doc to add this %1$sNGINX redirect rule%2$s." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:162 +msgid "If you have already done this, you can disregard this notice." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:174 +msgid "No need to worry, you are using the recommended 'Forced' download method, and customers should never see the direct path to the files. The following action is still recommended, however." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:182 +msgid "You currently are using the 'Redirect' download method, which may expose your downloadable products. Either switch to the 'Forced' method or enable 'Symlinks'." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:194 +msgid "Protect your files" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:200 +msgid "Your current download method creates a temporary copy of the file for the customer to download. After they successfully download it, it is removed, ensuring they never have direct access to your product files." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:213 +msgid "Your download files are currently not protected" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:237 +msgid "Miscellaneous Settings" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:254 +msgid "You have at least one gateway enabled" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:259 +msgid "Fantastic! You have enabled a gateway and can accept orders." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:279 +msgid "Please enable at least one gateway in order to allow customers to make purchases." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:298 +msgid "Scheduled events are running" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:303 +msgid "Easy Digital Downloads uses scheduled events in a number of ways to help maintain performance and stability." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:313 +msgid "Scheduled events are not running" +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:318 +msgid "Your site has cron events disabled. WordPress cron events should run at least every ten minutes for your store to manage order related events." +msgstr "" + +#: src/Admin/SiteHealth/Direct.php:319 +msgid "Some hosting providers disable cron events by default, in favor of their own solution to running WP_CRON. Please contact your hosting provider to confirm any necessary changes." +msgstr "" + +#: src/Admin/SiteHealth/Gateways.php:25 +msgid "Easy Digital Downloads — Gateways" +msgstr "" + +#: src/Admin/SiteHealth/General.php:43 +msgid "Easy Digital Downloads — General" +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:25 +msgid "Licensed Extensions" +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:39 +msgid "Your extensions are receiving updates" +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:44 +msgid "Your EDD extensions are all licensed and receiving updates." +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:53 +msgid "You are not receiving updates for some extensions" +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:58 +msgid "At least one of your extensions is missing a license key, or the license is expired. Your site may be missing critical software updates." +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:116 +msgid "Upgrade to EDD (Pro)" +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:127 +msgid "Enter a license key for EDD (Pro)" +msgstr "" + +#: src/Admin/SiteHealth/Licenses.php:136 +msgid "Enter a license key for an extension" +msgstr "" + +#: src/Admin/SiteHealth/Pages.php:26 +msgid "Easy Digital Downloads — Pages" +msgstr "" + +#: src/Admin/SiteHealth/Sessions.php:26 +msgid "Easy Digital Downloads — Sessions" +msgstr "" + +#: src/Admin/SiteHealth/Tables.php:26 +msgid "Easy Digital Downloads — Custom Tables" +msgstr "" + +#: src/Admin/SiteHealth/Taxes.php:26 +msgid "Easy Digital Downloads — Taxes" +msgstr "" + +#: src/Admin/SiteHealth/Templates.php:26 +msgid "Easy Digital Downloads — Customized Templates" +msgstr "" + +#: src/Licensing/Ajax.php:50 +#: src/Licensing/Ajax.php:136 +#: src/Licensing/Ajax.php:178 +msgid "You do not have permission to manage this extension." +msgstr "" + +#: src/Licensing/Ajax.php:61 +#: src/Lite/Admin/PassHandler/Connect.php:75 +#: src/Pro/Admin/PassHandler/Ajax.php:46 +msgid "No key provided." +msgstr "" + +#: src/Licensing/Ajax.php:92 +msgid "Your license key could not be activated." +msgstr "" + +#: src/Licensing/Ajax.php:162 +#: src/Licensing/Messages.php:130 +msgid "Your license key has been deactivated." +msgstr "" + +#: src/Licensing/Ajax.php:190 +msgid "License key deleted." +msgstr "" + +#: src/Licensing/Messages.php:89 +msgid "license key" +msgstr "" + +#. translators: the extension name. +#: src/Licensing/Messages.php:116 +msgid "This appears to be an invalid license key for %s." +msgstr "" + +#: src/Licensing/Messages.php:126 +msgid "The key you entered belongs to a bundle, please use the product specific license key." +msgstr "" + +#: src/Licensing/Messages.php:141 +msgid "Unlicensed: currently not receiving updates." +msgstr "" + +#: src/Licensing/Messages.php:156 +msgid "License key never expires." +msgstr "" + +#. translators: the license expiration date. +#: src/Licensing/Messages.php:162 +msgid "Your license key expires soon! It expires on %s." +msgstr "" + +#. translators: the license expiration date. +#: src/Licensing/Messages.php:169 +msgid "Your license key expires on %s." +msgstr "" + +#: src/Licensing/Messages.php:182 +msgid "Your license subscription is active and will automatically renew." +msgstr "" + +#. translators: the license subscription status. +#: src/Licensing/Messages.php:187 +msgid "Your license subscription is %s and will not automatically renew." +msgstr "" + +#. translators: 1. license expiration date. +#: src/Licensing/Messages.php:204 +msgid "Your license key expired on %1$s. Please renew your license key." +msgstr "" + +#: src/Licensing/Messages.php:209 +msgid "Your license key has expired. Please renew your license key." +msgstr "" + +#. translators: 1. license expiration date; 2. opening link tag; 3. closing link tag. +#: src/Licensing/Messages.php:228 +msgid "Your license key expired on %1$s. Please %2$srenew your license key%3$s." +msgstr "" + +#. translators: 1. opening link tag; 2. closing link tag. +#: src/Licensing/Messages.php:237 +msgid "Your license key has expired. Please %1$srenew your license key%2$s." +msgstr "" + +#: src/Licensing/Messages.php:252 +msgid "Your license key has been disabled." +msgstr "" + +#. translators: 1. opening link tag; 2. closing link tag. +#: src/Licensing/Messages.php:267 +msgid "Your license key has been disabled. Please %1$scontact support%2$s for more information." +msgstr "" + +#: src/Licensing/Messages.php:282 +msgid "Your license key has reached its activation limit." +msgstr "" + +#. translators: 1. opening link tag; 2 closing link tag. +#: src/Licensing/Messages.php:297 +msgid "Your license key has reached its activation limit. %1$sView possible upgrades%2$s now." +msgstr "" + +#: src/Licensing/Messages.php:312 +msgid "Your license key is not active for this URL." +msgstr "" + +#. translators: 1. opening link tag; 2. closing link tag. +#: src/Licensing/Messages.php:328 +msgid "Your license key is not active for this URL. Please %1$svisit your account page%2$s to manage your license keys." +msgstr "" + +#. translators: 1. the extension name; 2. opening link tag; 3. closing link tag. +#: src/Licensing/Messages.php:336 +msgid "Your %1$s license key is not active for this URL. Please %2$svisit your account page%3$s to manage your license keys." +msgstr "" + +#: src/Licensing/Messages.php:351 +msgid "Invalid license. Please verify it." +msgstr "" + +#. translators: 1. opening link tag; 2. closing link tag. +#: src/Licensing/Messages.php:364 +msgid "Invalid license. Please %1$svisit your account page%2$s and verify it." +msgstr "" + +#: src/Licensing/Messages.php:379 +msgid "pending" +msgstr "" + +#: src/Licensing/Messages.php:380 +msgid "active" +msgstr "" + +#: src/Licensing/Messages.php:381 +msgid "cancelled" +msgstr "" + +#: src/Licensing/Messages.php:382 +msgid "expired" +msgstr "" + +#: src/Licensing/Messages.php:383 +msgid "trialling" +msgstr "" + +#: src/Licensing/Messages.php:384 +msgid "failing" +msgstr "" + +#: src/Licensing/Messages.php:385 +msgid "completed" +msgstr "" + +#. translators: the all acess pass name. +#: src/Licensing/Traits/Controls.php:200 +msgid "Your %s gives you access to this extension." +msgstr "" + +#: src/Lite/Admin/PassHandler/Connect.php:52 +msgid "There was an error while installing an upgrade. Please download the plugin from easydigitaldownloads.com and install it manually." +msgstr "" + +#: src/Lite/Admin/PassHandler/Connect.php:114 +msgid "There was an error while installing an upgrade. Please check file system permissions and try again. Also, you can download the plugin from easydigitaldownloads.com and install it manually." +msgstr "" + +#: src/Lite/Admin/PassHandler/Connect.php:149 +msgid "Easy Digital Downloads (Pro) was installed, but needs to be activated on the Plugins page inside your WordPress admin." +msgstr "" + +#: src/Lite/Admin/PassHandler/Pointer.php:83 +msgid "Install the Pro Version!" +msgstr "" + +#: src/Lite/Admin/PassHandler/Pointer.php:84 +msgid "We see you already have an active pass. Click here to verify your license key and we'll connect you to install Easy Digital Downloads (Pro)." +msgstr "" + +#: src/Lite/Admin/PassHandler/Pointer.php:172 +msgid "You're eligible to install EDD (Pro)!" +msgstr "" + +#. translators: 1. opening anchor tag; 2. closing anchor tag +#: src/Lite/Admin/PassHandler/Pointer.php:175 +msgid "Good news! With your pass subscription, you can install the Pro version of Easy Digital Downloads. %1$sVisit the settings page%2$s to verify your license and access Pro only features." +msgstr "" + +#. Translators: %s - a length of time (e.g. "1 second") +#: src/Models/Notification.php:187 +msgid "%s ago" +msgstr "" + +#: src/Pro/Admin/Duplicator/Controls.php:48 +#: src/Pro/Admin/Duplicator/Controls.php:71 +#: src/Pro/Admin/Duplicator/Controls.php:92 +msgid "Duplicate" +msgstr "" + +#: src/Pro/Admin/Duplicator/Worker.php:45 +msgid "You do not have permission to duplicate this product." +msgstr "" + +#: src/Pro/Admin/Duplicator/Worker.php:49 +msgid "The product to duplicate is missing." +msgstr "" + +#. translators: the original product ID. +#: src/Pro/Admin/Duplicator/Worker.php:71 +msgid "Product creation failed, could not find original product: %s" +msgstr "" + +#: src/Pro/Admin/Duplicator/Worker.php:108 +msgid "(Copy)" +msgstr "" + +#: src/Pro/Admin/Extensions/Buttons.php:21 +msgid "Deactivated" +msgstr "" + +#: src/Pro/Admin/Extensions/Card.php:31 +msgid "Configure Settings" +msgstr "" + +#: src/Pro/Admin/Extensions/ExtensionPage.php:44 +msgid "You have not yet added a license key. A valid license key is required in order to use our extensions." +msgstr "" + +#: src/Pro/Admin/Extensions/ExtensionPage.php:46 +msgid "Enter License Key" +msgstr "" + +#: src/Pro/Admin/Extensions/ExtensionPage.php:47 +msgid "Purchase License" +msgstr "" + +#: src/Pro/Admin/Extensions/ExtensionPage.php:75 +msgid "Pro Available Extensions" +msgstr "" + +#. translators: 1. opening anchor tag; 2. closing anchor tag. +#: src/Pro/Admin/Extensions/ExtensionPage.php:91 +msgid "Missing an extension that you think you should be able to see? %1$sRefresh your extensions now%2$s." +msgstr "" + +#: src/Pro/Admin/Extensions/ExtensionPage.php:114 +msgid "Unlock More Extensions" +msgstr "" + +#. translators: 1. opening anchor tag; 2. closing anchor tag. +#: src/Pro/Admin/Extensions/ExtensionPage.php:127 +msgid "Want to get even more extensions? Upgrade your %1$sEasy Digital Downloads license%2$s and unlock the following extensions." +msgstr "" + +#: src/Pro/Admin/Extensions/ExtensionPage.php:153 +msgid "Log In to Download" +msgstr "" + +#: src/Pro/Admin/Extensions/ExtensionPage.php:161 +msgid "Install" +msgstr "" + +#: src/Pro/Admin/Extensions/Legacy.php:38 +msgid "Inactive — part of EDD (Pro)" +msgstr "" + +#: src/Pro/Admin/Extensions/Legacy.php:40 +msgid "Inactive — You've got EDD (Pro)!" +msgstr "" + +#: src/Pro/Admin/PassHandler/Ajax.php:79 +msgid "Congratulations; your pass was successfully activated!" +msgstr "" + +#: src/Pro/Admin/PassHandler/Ajax.php:103 +msgid "EDD (Pro)" +msgstr "" + +#. translators: the name of a licensed extension. +#: src/Pro/Admin/PassHandler/Ajax.php:114 +msgid "You have attempted to activate a license for %s instead of a pass. You will need to download the extension from your account." +msgstr "" + +#. translators: the name of a licensed extension. +#: src/Pro/Admin/PassHandler/Ajax.php:127 +msgid "You have attempted to activate a license for an extension instead of a pass. We have activated the license for %s, but you will need to download it and install it manually." +msgstr "" + +#. translators: the name of a licensed extension. +#: src/Pro/Admin/PassHandler/Ajax.php:135 +msgid "Download %s" +msgstr "" + +#. Translators: %1$s opening anchor tag; %2$s closing anchor tag +#: src/Pro/Admin/Promos/Notices/InactivePro.php:60 +msgid "You are using Easy Digital Downloads (Pro) without an active license key. %1$sEnter or activate your license key now.%2$s" +msgstr "" + +#: templates/history-downloads.php:14 +#: templates/history-purchases.php:13 +msgid "Your account has been successfully verified!" +msgstr "" + +#: templates/history-downloads.php:44 +msgid "Download Name" +msgstr "" + +#: templates/history-downloads.php:135 +msgid "You have not purchased any downloads" +msgstr "" + +#: templates/history-purchases.php:102 +msgid "You have not made any purchases." +msgstr "" + +#. translators: %s - success page URL +#: templates/payment-processing.php:5 +msgid "Your purchase is processing. This page will reload automatically in 8 seconds. If it does not, click here." +msgstr "" + +#: templates/shortcode-login.php:12 +msgid "Log into Your Account" +msgstr "" + +#: templates/shortcode-profile-editor.php:19 +msgid "Saved cart" +msgstr "" + +#. translators: %s: Restore cart URL +#: templates/shortcode-profile-editor.php:22 +msgid "You have a saved cart, click here to restore it." +msgstr "" + +#: templates/shortcode-profile-editor.php:28 +msgid "Your profile has been edited successfully." +msgstr "" + +#: templates/shortcode-profile-editor.php:41 +msgid "Change your Name" +msgstr "" + +#: templates/shortcode-profile-editor.php:54 +msgid "Display Name" +msgstr "" + +#: templates/shortcode-profile-editor.php:74 +msgid "Primary Email Address" +msgstr "" + +#: templates/shortcode-profile-editor.php:110 +msgid "Additional Email Addresses" +msgstr "" + +#: templates/shortcode-profile-editor.php:145 +msgid "Change your Billing Address" +msgstr "" + +#: templates/shortcode-profile-editor.php:148 +msgid "Line 1" +msgstr "" + +#: templates/shortcode-profile-editor.php:153 +msgid "Line 2" +msgstr "" + +#: templates/shortcode-profile-editor.php:201 +msgid "Change your Password" +msgstr "" + +#: templates/shortcode-profile-editor.php:204 +msgid "New Password" +msgstr "" + +#: templates/shortcode-profile-editor.php:209 +msgid "Re-enter Password" +msgstr "" + +#: templates/shortcode-profile-editor.php:226 +msgid "Save Changes" +msgstr "" + +#: templates/shortcode-register.php:15 +msgid "Register New Account" +msgstr "" + +#: templates/widget-cart-checkout.php:2 +#: templates/widget-cart-empty.php:3 +msgid "Subtotal:" +msgstr "" + +#: templates/widget-cart-checkout.php:3 +#: templates/widget-cart-empty.php:4 +msgid "Estimated Tax:" +msgstr "" + +#: templates/widget-cart-checkout.php:5 +#: templates/widget-cart-empty.php:6 +msgid "Total:" +msgstr "" + +#: templates/widget-cart-item.php:4 +msgid "remove" +msgstr "" + +#: templates/widget-cart.php:9 +msgid "Number of items in cart" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +msgid "Select a %s" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +msgid "Current %s" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +msgid "No Published %s Found" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +msgid "Create a New %s" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +msgid "Create Your First %s" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +msgid "View All %s" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +msgid "Select a %s:" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +msgid "Published %s" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +msgid "Show Price" +msgstr "" + +#: includes/blocks/build/buy-button/index.js:1 +msgid "Enable Buy Now to process a download order without going through the full checkout." +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +msgid "Mini Cart" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +#: includes/blocks/build/downloads/index.js:1 +msgid "Title" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +msgid "Hide When Empty" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +msgid "Hide on Checkout" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +msgid "Link Cart to Checkout" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +msgid "Show Number of Items in Cart" +msgstr "" + +#: includes/blocks/build/cart/index.js:1 +msgid "Show Cart Total" +msgstr "" + +#: includes/blocks/build/checkout/index.js:1 +msgid "This is an example of a cart with a product in it." +msgstr "" + +#: includes/blocks/build/confirmation/index.js:1 +#: includes/blocks/build/receipt/index.js:1 +msgid "Create at least one order to see an example of a receipt." +msgstr "" + +#: includes/blocks/build/confirmation/index.js:1 +#: includes/blocks/build/receipt/index.js:1 +msgid "Show Payment Key" +msgstr "" + +#: includes/blocks/build/confirmation/index.js:1 +#: includes/blocks/build/receipt/index.js:1 +msgid "Show Gateway" +msgstr "" + +#: includes/blocks/build/confirmation/index.js:1 +msgid "The editor will display a recent random order from your site." +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Center" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Left" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Right" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Ascending" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Descending" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Thumbnail" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Medium" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Large" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Wide" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Date Published" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Date Modified" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Random" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "No Content" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Full Content" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "No Image" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Before Title" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "After Title" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "All Categories" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Product Block Settings" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Decide how to display your products." +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Downloads per Page" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Show All Access Downloads" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Number of Columns" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Order By" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Show Pagination" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Download Term Settings" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Show Downloads From Categories" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Show Downloads From Tags" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Individual Product Settings" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +#: includes/blocks/build/terms/index.js:1 +msgid "Show Title" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Featured Image Location" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Should the featured image link to the product?" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Featured Image Size" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Featured Image Alignment" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Content" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Purchase Button Alignment" +msgstr "" + +#: includes/blocks/build/downloads/index.js:1 +msgid "Show Price on Button" +msgstr "" + +#: includes/blocks/build/login/index.js:1 +msgid "Once logged in, where should the user be directed? You can choose the current page, or a custom URL." +msgstr "" + +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +msgid "Redirect to Current Page" +msgstr "" + +#: includes/blocks/build/login/index.js:1 +#: includes/blocks/build/register/index.js:1 +msgid "Custom Redirect URL" +msgstr "" + +#: includes/blocks/build/login/index.js:1 +msgid "This form is a sample view of your login form. Logged in users will not see it." +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +msgid "This is an example of a user's order history." +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +msgid "Order History Settings" +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +msgid "Columns" +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +msgid "Orders per Page" +msgstr "" + +#: includes/blocks/build/order-history/index.js:1 +msgid "Do Not Show Renewal Orders" +msgstr "" + +#: includes/blocks/build/pro/currency-selector/index.js:1 +msgid "Dropdown" +msgstr "" + +#: includes/blocks/build/receipt/index.js:1 +msgid "The editor will display a sample random order from your site." +msgstr "" + +#: includes/blocks/build/register/index.js:1 +msgid "Once registered, where should the user be directed? You can choose the current page, or a custom URL." +msgstr "" + +#: includes/blocks/build/register/index.js:1 +msgid "This form is a sample view of your registration form. Logged in users will not see it." +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "%s Categories" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "%s Tags" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Count" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Term Block Settings" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Show Empty Categories" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Individual Term Settings" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Show Thumbnails" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Image Size" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Image Alignment" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Show Description" +msgstr "" + +#: includes/blocks/build/terms/index.js:1 +msgid "Show Count" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "This is an example of a user's available downloads." +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "Your store has disabled redownloading files, so your users will not be able to access their files from this block. You can change the \"Disable Redownload\" setting by visiting Downloads > Settings > Misc > File Downloads." +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "User Download Settings" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "Show a Search Form" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "This feature is available in EDD (Pro)." +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "Show Product Variations" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "If your product variations all use the same deliverable files, you may want to disable this." +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "Hide Products With No Files" +msgstr "" + +#: includes/blocks/build/user-downloads/index.js:1 +msgid "Text to show if there are no files" +msgstr "" + +#: includes/blocks/build/buy-button/block.json +msgctxt "block title" +msgid "EDD Buy Button" +msgstr "" + +#: includes/blocks/build/buy-button/block.json +msgctxt "block description" +msgid "Quickly add a \"buy now\" button for any EDD product." +msgstr "" + +#: includes/blocks/build/buy-button/block.json +#: includes/blocks/build/cart/block.json +#: includes/blocks/build/checkout/block.json +#: includes/blocks/build/confirmation/block.json +#: includes/blocks/build/downloads/block.json +#: includes/blocks/build/login/block.json +#: includes/blocks/build/order-history/block.json +#: includes/blocks/build/pro/currency-selector/block.json +#: includes/blocks/build/receipt/block.json +#: includes/blocks/build/register/block.json +#: includes/blocks/build/terms/block.json +#: includes/blocks/build/user-downloads/block.json +msgctxt "block keyword" +msgid "easy digital downloads" +msgstr "" + +#: includes/blocks/build/buy-button/block.json +#: includes/blocks/build/cart/block.json +#: includes/blocks/build/checkout/block.json +#: includes/blocks/build/confirmation/block.json +#: includes/blocks/build/downloads/block.json +#: includes/blocks/build/login/block.json +#: includes/blocks/build/order-history/block.json +#: includes/blocks/build/pro/currency-selector/block.json +#: includes/blocks/build/receipt/block.json +#: includes/blocks/build/register/block.json +#: includes/blocks/build/terms/block.json +#: includes/blocks/build/user-downloads/block.json +msgctxt "block keyword" +msgid "edd" +msgstr "" + +#: includes/blocks/build/buy-button/block.json +msgctxt "block keyword" +msgid "button" +msgstr "" + +#: includes/blocks/build/cart/block.json +msgctxt "block title" +msgid "EDD Cart" +msgstr "" + +#: includes/blocks/build/cart/block.json +msgctxt "block description" +msgid "Display a mini or full shopping cart outside of checkout for Easy Digital Downloads." +msgstr "" + +#: includes/blocks/build/cart/block.json +msgctxt "block keyword" +msgid "cart" +msgstr "" + +#: includes/blocks/build/checkout/block.json +msgctxt "block title" +msgid "EDD Checkout" +msgstr "" + +#: includes/blocks/build/checkout/block.json +msgctxt "block description" +msgid "Full checkout block for Easy Digital Downloads." +msgstr "" + +#: includes/blocks/build/checkout/block.json +msgctxt "block keyword" +msgid "checkout" +msgstr "" + +#: includes/blocks/build/confirmation/block.json +msgctxt "block title" +msgid "EDD Confirmation" +msgstr "" + +#: includes/blocks/build/confirmation/block.json +msgctxt "block description" +msgid "A brief confirmation screen to show to customers immediately after a successful purchase." +msgstr "" + +#: includes/blocks/build/confirmation/block.json +#: includes/blocks/build/order-history/block.json +#: includes/blocks/build/receipt/block.json +#: includes/blocks/build/user-downloads/block.json +msgctxt "block keyword" +msgid "orders" +msgstr "" + +#: includes/blocks/build/downloads/block.json +msgctxt "block title" +msgid "EDD Products" +msgstr "" + +#: includes/blocks/build/downloads/block.json +msgctxt "block description" +msgid "A block to show your Easy Digital Download products based on visual customizations and query parameters." +msgstr "" + +#: includes/blocks/build/downloads/block.json +#: includes/blocks/build/terms/block.json +msgctxt "block keyword" +msgid "downloads" +msgstr "" + +#: includes/blocks/build/login/block.json +msgctxt "block title" +msgid "EDD Login Form" +msgstr "" + +#: includes/blocks/build/login/block.json +msgctxt "block description" +msgid "Login form for Easy Digital Downloads." +msgstr "" + +#: includes/blocks/build/login/block.json +msgctxt "block keyword" +msgid "login" +msgstr "" + +#: includes/blocks/build/order-history/block.json +msgctxt "block title" +msgid "EDD Order History" +msgstr "" + +#: includes/blocks/build/order-history/block.json +msgctxt "block description" +msgid "Display the Easy Digital Downloads order history of a logged in user." +msgstr "" + +#: includes/blocks/build/pro/currency-selector/block.json +msgctxt "block title" +msgid "EDD Currency Selector" +msgstr "" + +#: includes/blocks/build/pro/currency-selector/block.json +msgctxt "block description" +msgid "Allows your customers to switch the store's currency." +msgstr "" + +#: includes/blocks/build/pro/currency-selector/block.json +msgctxt "block keyword" +msgid "currency" +msgstr "" + +#: includes/blocks/build/receipt/block.json +msgctxt "block title" +msgid "EDD Receipt" +msgstr "" + +#: includes/blocks/build/receipt/block.json +msgctxt "block description" +msgid "Show customers their detailed receipt. Supports guest orders." +msgstr "" + +#: includes/blocks/build/register/block.json +msgctxt "block title" +msgid "EDD Registration Form" +msgstr "" + +#: includes/blocks/build/register/block.json +msgctxt "block description" +msgid "Registration form for Easy Digital Downloads." +msgstr "" + +#: includes/blocks/build/register/block.json +msgctxt "block keyword" +msgid "registration" +msgstr "" + +#: includes/blocks/build/terms/block.json +msgctxt "block title" +msgid "EDD Download Terms" +msgstr "" + +#: includes/blocks/build/terms/block.json +msgctxt "block description" +msgid "Show categories, or tags for Easy Digital Download products." +msgstr "" + +#: includes/blocks/build/user-downloads/block.json +msgctxt "block title" +msgid "EDD User Downloads" +msgstr "" + +#: includes/blocks/build/user-downloads/block.json +msgctxt "block description" +msgid "Allows a user to access the Easy Digital Downloads products they have purchased." +msgstr "" diff --git a/languages/edd-ar.mo b/languages/edd-ar.mo deleted file mode 100644 index 6227455526e..00000000000 Binary files a/languages/edd-ar.mo and /dev/null differ diff --git a/languages/edd-ar.po b/languages/edd-ar.po deleted file mode 100644 index 72582a8fb25..00000000000 --- a/languages/edd-ar.po +++ /dev/null @@ -1,2262 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-20 17:46+0300\n" -"PO-Revision-Date: 2012-09-24 20:40+0300\n" -"Last-Translator: Maoeah \n" -"Language-Team: Easy Digital Downloads \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: \n" -"X-Poedit-KeywordsList: __;_e;_x;_n\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-Language: Arabic\n" -"X-Poedit-Country: SAUDI ARABIA\n" -"X-Poedit-SourceCharset: utf-8\n" -"X-Poedit-SearchPath-0: ..\n" -"X-Poedit-SearchPath-1: ../includes\n" -"X-Poedit-SearchPath-2: .\n" - -#: ../includes/admin-notices.php:30 -msgid "Discount code updated." -msgstr "رمز خصم محدث" - -#: ../includes/admin-notices.php:33 -msgid "There was a problem updating your discount code, please try again." -msgstr "حدثت مشكلة أثناء تحديث رمز خصمك، حاول مرة ثانية" - -#: ../includes/admin-notices.php:36 -msgid "The payment has been deleted." -msgstr "تم إلغاء الدفع" - -#: ../includes/admin-notices.php:39 -msgid "The purchase receipt has been resent." -msgstr "تم إعادة إرسال فاتورة الشراء" - -#: ../includes/admin-notices.php:44 -#, php-format -msgid "The payment history needs updated. %s" -msgstr "ينبغي تحديث تاريخ الدفع %s" - -#: ../includes/admin-notices.php:44 -msgid "Click to Upgrade" -msgstr "أنقر للترقية" - -#: ../includes/admin-notices.php:62 -msgid "There seems to be an issue with the server. Please try again in a few minutes." -msgstr "يبدو أن هناك مشكلة بالخادم ، نرجو المحاولة مرة أخرى بعد دقائق" - -#: ../includes/admin-pages.php:26 -msgid "Payment History" -msgstr "تاريخ الدفع" - -#: ../includes/admin-pages.php:27 -msgid "Discount Codes" -msgstr "رموز الخصم" - -#: ../includes/admin-pages.php:28 -msgid "Earnings and Sales Reports" -msgstr "تقارير المبيعات و الأرباح" - -#: ../includes/admin-pages.php:28 -msgid "Reports" -msgstr "تقارير" - -#: ../includes/admin-pages.php:29 -msgid "Easy Digital Download Settings" -msgstr "إعدادات إيزي ديجيتل داونلودز" - -#: ../includes/admin-pages.php:29 -msgid "Settings" -msgstr "الإعدادات" - -#: ../includes/admin-pages.php:30 -msgid "Easy Digital Download Add Ons" -msgstr "إضافات إيزي ديجيتل داونلودز" - -#: ../includes/admin-pages.php:30 -msgid "Add Ons" -msgstr "الإضافات" - -#: ../includes/ajax-functions.php:102 -msgid "This discount code has been used already" -msgstr "رمز الخصم هذا مستخدم" - -#: ../includes/ajax-functions.php:118 -msgid "The discount you entered is invalid" -msgstr "الخصم الذي أدخلته غير صالح" - -#: ../includes/cart-functions.php:405 -#, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "لقد نجحت باضافة % لسلتك" - -#: ../includes/cart-functions.php:406 -msgid "Checkout." -msgstr "الدفع و الخروج" - -#: ../includes/cart-template.php:46 -#: ../includes/cart-template.php:49 -msgid "Checkout" -msgstr "الدفع و الخروج" - -#: ../includes/cart-template.php:80 -msgid "remove" -msgstr "حذف" - -#: ../includes/cart-template.php:99 -msgid "Your cart is empty." -msgstr "سلتك فارغة" - -#: ../includes/checkout-template.php:101 -msgid "Personal Info" -msgstr "معلومات شخصية" - -#: ../includes/checkout-template.php:104 -msgid "Email address" -msgstr "البريد الإلكتروني" - -#: ../includes/checkout-template.php:105 -msgid "Email Address" -msgstr "البريد الإلكتروني" - -#: ../includes/checkout-template.php:109 -#: ../includes/checkout-template.php:110 -#: ../includes/checkout-template.php:343 -#: ../includes/checkout-template.php:344 -msgid "First Name" -msgstr "الإسم الأول" - -#: ../includes/checkout-template.php:113 -#: ../includes/checkout-template.php:347 -msgid "Last name" -msgstr "الاسم الأخير" - -#: ../includes/checkout-template.php:114 -#: ../includes/checkout-template.php:348 -msgid "Last Name" -msgstr "الإسم الأخير" - -#: ../includes/checkout-template.php:142 -msgid "Show Terms" -msgstr "عرض الشروط" - -#: ../includes/checkout-template.php:143 -msgid "Hide Terms" -msgstr "إخفاء الشروط" - -#: ../includes/checkout-template.php:146 -msgid "Agree to Terms?" -msgstr "موافق على الشروط؟" - -#: ../includes/checkout-template.php:166 -msgid "Go back" -msgstr "الرجوع" - -#: ../includes/checkout-template.php:170 -msgid "You must be logged in to complete your purchase" -msgstr "ينبغي أن تسجل دخولك لتشتري" - -#: ../includes/checkout-template.php:199 -msgid "Credit Card Info" -msgstr "معلومات البطاقة الإئتمانية" - -#: ../includes/checkout-template.php:201 -msgid "Card name" -msgstr "اسم البطاقة الإئتمانية" - -#: ../includes/checkout-template.php:202 -msgid "Name on the Card" -msgstr "الإسم الذي على البطاقة الإئتمانية" - -#: ../includes/checkout-template.php:205 -msgid "Card number" -msgstr "رقم البطاقة الإئتمانية" - -#: ../includes/checkout-template.php:206 -msgid "Card Number" -msgstr "رقم البطاقة الإئتمانية" - -#: ../includes/checkout-template.php:209 -msgid "Security code" -msgstr "الرمز السري" - -#: ../includes/checkout-template.php:210 -msgid "CVC" -msgstr "CVC الأرقام الثلاثة الأخير التي خلف البطاقة الإئتمانية" - -#: ../includes/checkout-template.php:216 -msgid "Month" -msgstr "شهر" - -#: ../includes/checkout-template.php:218 -msgid "Year" -msgstr "السنة" - -#: ../includes/checkout-template.php:219 -msgid "Expiration (MM/YYYY)" -msgstr "نهاية الصلاحية ( الشهر/السنة )" - -#: ../includes/checkout-template.php:249 -msgid "Address line 1" -msgstr "سطر العنوان 1" - -#: ../includes/checkout-template.php:250 -msgid "Billing Address" -msgstr "العنوان الذي ترسل إليه الفاتورة" - -#: ../includes/checkout-template.php:253 -msgid "Address line 2" -msgstr "سطر العنوان 2" - -#: ../includes/checkout-template.php:254 -msgid "Billing Address Line 2" -msgstr "العنوان الثاني الذي سترسل إليه الفاتورة" - -#: ../includes/checkout-template.php:257 -msgid "City" -msgstr "المدينة" - -#: ../includes/checkout-template.php:258 -msgid "Billing City" -msgstr "المدينة التي سترسل إليها الفاتورة" - -#: ../includes/checkout-template.php:269 -msgid "Billing Country" -msgstr "البلد الذي سترسل إليه الفاتورة" - -#: ../includes/checkout-template.php:272 -msgid "State / Province" -msgstr "المنطقة/الولاية" - -#: ../includes/checkout-template.php:289 -msgid "Billing State / Province" -msgstr "المنطقةالتي سترسل إليها الفاتورة" - -#: ../includes/checkout-template.php:292 -msgid "Zip / Postal code" -msgstr "الرمز البريدي" - -#: ../includes/checkout-template.php:293 -msgid "Billing Zip / Postal Code" -msgstr "رمز البريد الذي سيرسل الفاتورة" - -#: ../includes/checkout-template.php:320 -msgid "Already have an account?" -msgstr "لديك حساب مسبقاً؟" - -#: ../includes/checkout-template.php:320 -msgid "Login" -msgstr "أدخل" - -#: ../includes/checkout-template.php:322 -msgid "Create an account" -msgstr "انشاء حساب" - -#: ../includes/checkout-template.php:322 -msgid "(optional)" -msgstr "(اختياري)" - -#: ../includes/checkout-template.php:325 -#: ../includes/checkout-template.php:326 -#: ../includes/checkout-template.php:373 -msgid "Username" -msgstr "اسم المستخدم:" - -#: ../includes/checkout-template.php:329 -#: ../includes/checkout-template.php:330 -#: ../includes/checkout-template.php:377 -msgid "Password" -msgstr "كلمة المرور:" - -#: ../includes/checkout-template.php:333 -msgid "Confirm password" -msgstr "تأكيد كلمة المرور" - -#: ../includes/checkout-template.php:334 -msgid "Password Again" -msgstr "كلمة المرور مرة أخرى" - -#: ../includes/checkout-template.php:339 -#: ../includes/checkout-template.php:340 -msgid "Email" -msgstr "البريد الإلكتروني" - -#: ../includes/checkout-template.php:369 -msgid "Login to your account" -msgstr "الدخول إلى حسابك" - -#: ../includes/checkout-template.php:372 -msgid "Your username" -msgstr "اسمك" - -#: ../includes/checkout-template.php:376 -msgid "Your password" -msgstr "كلمتك السرية" - -#: ../includes/checkout-template.php:384 -msgid "Need to create an account?" -msgstr "تريد أنشاء حساب؟" - -#: ../includes/checkout-template.php:386 -msgid "Register" -msgstr "التسجيل" - -#: ../includes/checkout-template.php:386 -msgid "or checkout as a guest." -msgstr "أو الدفع و الخروج كزائر." - -#: ../includes/checkout-template.php:415 -msgid "Choose Your Payment Method" -msgstr "اختيار طريقة الدفع" - -#: ../includes/checkout-template.php:443 -msgid "Enter discount" -msgstr "أدخل الخصم" - -#: ../includes/checkout-template.php:445 -msgid "Discount" -msgstr "الخصم" - -#: ../includes/checkout-template.php:447 -msgid "Apply Discount" -msgstr "تطبيق الخصم" - -#: ../includes/checkout-template.php:473 -msgid "Next" -msgstr "التالي" - -#: ../includes/checkout-template.php:497 -#: ../includes/dashboard-columns.php:58 -msgid "Purchase" -msgstr "شراء" - -#: ../includes/dashboard-columns.php:26 -msgid "Name" -msgstr "الاسم" - -#: ../includes/dashboard-columns.php:27 -msgid "Categories" -msgstr "الفئة" - -#: ../includes/dashboard-columns.php:28 -msgid "Tags" -msgstr "العلامات" - -#: ../includes/dashboard-columns.php:29 -#: ../includes/dashboard-columns.php:231 -msgid "Price" -msgstr "السعر" - -#: ../includes/dashboard-columns.php:30 -msgid "Sales" -msgstr "المبيعات" - -#: ../includes/dashboard-columns.php:31 -msgid "Earnings" -msgstr "الأرباح" - -#: ../includes/dashboard-columns.php:32 -msgid "Short Code" -msgstr "الكود القصير" - -#: ../includes/dashboard-columns.php:33 -msgid "Date" -msgstr "التاريخ" - -#: ../includes/dashboard-columns.php:189 -msgid "Show all categories" -msgstr "اظهر جميع الفئات" - -#: ../includes/dashboard-columns.php:200 -msgid "Show all tags" -msgstr "اظهر جميع العلامات " - -#: ../includes/dashboard-columns.php:228 -#, php-format -msgid "%s Data" -msgstr "بيانات النسبة %" - -#: ../includes/email-functions.php:47 -msgid "Purchase Receipt" -msgstr "فاتورة الشراء" - -#: ../includes/email-functions.php:62 -msgid "Hello" -msgstr "مرحباً" - -#: ../includes/email-functions.php:62 -msgid "A download purchase has been made" -msgstr "تم شراء التحميل" - -#: ../includes/email-functions.php:63 -msgid "Downloads sold:" -msgstr "التحميلات المباعة:" - -#: ../includes/email-functions.php:76 -msgid "Purchased by: " -msgstr "اشتراه:" - -#: ../includes/email-functions.php:77 -msgid "Amount: " -msgstr "المبلغ:" - -#: ../includes/email-functions.php:78 -msgid "Payment Method: " -msgstr "طرق الدفع:" - -#: ../includes/email-functions.php:79 -msgid "Thank you" -msgstr "شكراً" - -#: ../includes/email-functions.php:82 -msgid "New download purchase" -msgstr "شراء تحميل جديد" - -#: ../includes/email-template.php:23 -msgid "Default Template" -msgstr "القالب الإفتراضي" - -#: ../includes/email-template.php:24 -msgid "No template, plain text only" -msgstr "لا قالب، فقط نص عادي" - -#: ../includes/email-template.php:115 -msgid "Sample Product Title" -msgstr "اسم عينة المنتج" - -#: ../includes/email-template.php:118 -msgid "Sample Download File Name" -msgstr "اسم عينة ملف التحميل" - -#: ../includes/email-template.php:118 -msgid "Optional notes about this download." -msgstr "ملاحظات اختيارية عن هذا التحميل" - -#: ../includes/email-template.php:129 -msgid "These are some sample notes added to a product." -msgstr "هناك عينة ملاحظات أضيفت للمنتج" - -#: ../includes/email-template.php:168 -msgid "Purchase Receipt Preview" -msgstr "شراء معاينة الفاتورة" - -#: ../includes/email-template.php:168 -msgid "Preview Purchase Receipt" -msgstr "معاينة فاتورة المشتريات" - -#: ../includes/email-template.php:209 -msgid "Dear" -msgstr "عزيزي" - -#: ../includes/email-template.php:210 -msgid "Thank you for your purchase. Please click on the link(s) below to download your files." -msgstr "شكراً لك على شرائك، نرجو أن تضغط على الرابط أدناه لتحميل الملف." - -#: ../includes/error-tracking.php:30 -msgid "Error" -msgstr "خطأ" - -#: ../includes/export-functions.php:39 -msgid "ID" -msgstr "المعرف" - -#: ../includes/export-functions.php:43 -msgid "Products" -msgstr "المنتج" - -#: ../includes/export-functions.php:44 -msgid "Discounts," -msgstr "الخصم" - -#: ../includes/export-functions.php:45 -msgid "Amount paid" -msgstr "مبلغ الدفع" - -#: ../includes/export-functions.php:46 -msgid "Payment method" -msgstr "طريقة الدفع" - -#: ../includes/export-functions.php:47 -msgid "Key" -msgstr "مفتاح" - -#: ../includes/export-functions.php:49 -msgid "User" -msgstr "المستخدم" - -#: ../includes/export-functions.php:50 -msgid "Status" -msgstr "الحالة" - -#: ../includes/export-functions.php:111 -#: ../includes/export-functions.php:119 -msgid "none" -msgstr "لا شئ" - -#: ../includes/export-functions.php:127 -msgid "guest" -msgstr "ضيف" - -#: ../includes/export-functions.php:135 -msgid "No payments recorded yet" -msgstr "لم يتم تسجيل أي مدفوعات" - -#: ../includes/export-functions.php:169 -msgid "Export not allowed for non-administrators." -msgstr "لا يسمح التصدير لغير المسئولين" - -#: ../includes/gateway-functions.php:28 -msgid "Test Payment" -msgstr "اختبار الدفع" - -#: ../includes/graphing.php:42 -#, php-format -msgid "%s Performance in Sales" -msgstr "أداء المبيعات بالنسبة" - -#: ../includes/graphing.php:77 -msgid "Download" -msgstr "حملّ" - -#: ../includes/graphing.php:88 -#, php-format -msgid "%s Performance in Earnings" -msgstr "أداء الأرباح بالنسبة" - -#: ../includes/graphing.php:136 -msgid "Earnings per month" -msgstr "الأرباح شهرياً" - -#: ../includes/graphing.php:168 -msgid "Day" -msgstr "يوم" - -#: ../includes/graphing.php:189 -#, php-format -msgid "Earnings per day for last %s days" -msgstr "الأرباح اليومية لآخر %s أيام" - -#: ../includes/graphing.php:236 -msgid "Sales per month" -msgstr "المبيعات شهرياً" - -#: ../includes/install.php:42 -msgid "Purchase Confirmation" -msgstr "توكيد الشراء" - -#: ../includes/install.php:43 -msgid "Thank you for your purchase!" -msgstr "شكراً لك على تسوقك عندنا" - -#: ../includes/install.php:53 -msgid "Purchase History" -msgstr "تاريخ الشراء" - -#: ../includes/login-register.php:45 -msgid "Log into Your Account" -msgstr "الدخول إلى حسابك" - -#: ../includes/login-register.php:61 -msgid "Lost Password" -msgstr "فقدان الكلمة السرية" - -#: ../includes/login-register.php:62 -msgid "Lost Password?" -msgstr "فقدت الكلمة السرية؟" - -#: ../includes/login-register.php:69 -msgid "You are already logged in" -msgstr "لقد دخلت مسبقاً" - -#: ../includes/login-register.php:92 -msgid "The password you entered is incorrect" -msgstr "كلمة المرور التي أدخلتها غير صحيحة" - -#: ../includes/login-register.php:95 -msgid "The username you entered does not exist" -msgstr "اسم المستخدم الذي أدخلته ليس موجود" - -#: ../includes/metabox.php:22 -#, php-format -msgid "%1$s Configuration" -msgstr "الترتيب 1$ %" - -#: ../includes/metabox.php:23 -msgid "Product Notes" -msgstr "ملاحظة المنتج" - -#: ../includes/metabox.php:24 -#, php-format -msgid "%1$s Stats" -msgstr "احصائيات 1$ %" - -#: ../includes/metabox.php:25 -msgid "Purchase Log" -msgstr "سجل أداء الشراء" - -#: ../includes/metabox.php:26 -msgid "File Download Log" -msgstr "سجل أداء ملف التحميل" - -#: ../includes/metabox.php:84 -msgid "Pricing" -msgstr "التسعير" - -#: ../includes/metabox.php:98 -msgid "Enable variable pricing" -msgstr "تمكين التسعير المتغير" - -#: ../includes/metabox.php:113 -#: ../includes/metabox.php:115 -#: ../includes/metabox.php:135 -#: ../includes/metabox.php:137 -msgid "9.99" -msgstr "9.99" - -#: ../includes/metabox.php:120 -#: ../includes/metabox.php:142 -msgid "Option Name" -msgstr "اسم الخيار" - -#: ../includes/metabox.php:149 -#: ../includes/metabox.php:220 -msgid "Add New" -msgstr "أضف جديد" - -#: ../includes/metabox.php:159 -msgid "for" -msgstr "لأجل" - -#: ../includes/metabox.php:181 -msgid "Download Files" -msgstr "تحميل الملفات" - -#: ../includes/metabox.php:184 -msgid "File Name" -msgstr "اسم الملف" - -#: ../includes/metabox.php:185 -msgid "File URL" -msgstr "رابط الملف" - -#: ../includes/metabox.php:196 -#: ../includes/metabox.php:215 -msgid "file name" -msgstr "اسم الملف" - -#: ../includes/metabox.php:197 -#: ../includes/metabox.php:216 -msgid "file url" -msgstr "رابط الملف" - -#: ../includes/metabox.php:199 -msgid "All Prices" -msgstr "جميع الأسعار" - -#: ../includes/metabox.php:206 -#: ../includes/metabox.php:217 -msgid "Upload File" -msgstr "رفع ملف" - -#: ../includes/metabox.php:220 -msgid "Upload the downloadable files." -msgstr "رفع الملفات القابلة للتحميل" - -#: ../includes/metabox.php:241 -msgid "Purchase Text" -msgstr "نص الشراء" - -#: ../includes/metabox.php:243 -msgid "Add the text you would like displayed for the purchase text" -msgstr "اضف النص الذي تريده لنص الشراء" - -#: ../includes/metabox.php:262 -msgid "Link Style" -msgstr "نمط الرابط" - -#: ../includes/metabox.php:264 -msgid "Button" -msgstr "زر" - -#: ../includes/metabox.php:265 -msgid "Text" -msgstr "نص" - -#: ../includes/metabox.php:266 -msgid "Choose the style of the purchase link" -msgstr "اختار نمط رابط الشراء" - -#: ../includes/metabox.php:287 -msgid "Button Color" -msgstr "لون الزر" - -#: ../includes/metabox.php:295 -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "اختار لون رابط الشراء، إذا كنت قد اخترت الزر " - -#: ../includes/metabox.php:313 -msgid "Disable the purchase button?" -msgstr "تعطيل زر الشراء؟" - -#: ../includes/metabox.php:316 -msgid "Check this if you do not want the purchase button displayed." -msgstr "علم على هذا إن كنت لا تريد عرض الزر" - -#: ../includes/metabox.php:338 -msgid "Notes" -msgstr "ملاحظات" - -#: ../includes/metabox.php:341 -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "خيار النمط أعلاه لا يعكس نمط الرمز القصير. الرمز القصير يسمح لك بوضع زر الشراء لهذا التحميل بأي مكان على الموقع" - -#: ../includes/metabox.php:347 -msgid "This short code can be placed anywhere on your site" -msgstr "هذا الرمز القصير يمكن وضعه في أي مكان في موقعك" - -#: ../includes/metabox.php:381 -msgid "Special notes or instructions for this product. These notes will be added to the purchase receipt." -msgstr "ملاحظات أو تعليمات خاصة لهذا المنتج. هذه الملاحظات ستضاف لفاتورة الشراء" - -#: ../includes/metabox.php:469 -msgid "Sales:" -msgstr "المبيعات:" - -#: ../includes/metabox.php:475 -msgid "Earnings:" -msgstr "الأرباح:" - -#: ../includes/metabox.php:511 -msgid "Sales Log" -msgstr "أداء المبيعات" - -#: ../includes/metabox.php:513 -msgid "Each sale for this download is listed below." -msgstr "كل بيع لهذا التحميل يتم سرده أدناه" - -#: ../includes/metabox.php:527 -#: ../includes/metabox.php:619 -msgid "Date:" -msgstr "التاريخ:" - -#: ../includes/metabox.php:531 -msgid "Buyer:" -msgstr "المشتري:" - -#: ../includes/metabox.php:535 -msgid "Purchase ID:" -msgstr "هوية الشراء:" - -#: ../includes/metabox.php:543 -msgid "No sales yet" -msgstr "ليس هناك مبيعات بعد" - -#: ../includes/metabox.php:559 -#: ../includes/metabox.php:656 -msgid "Previous" -msgstr "السابق" - -#: ../includes/metabox.php:600 -msgid "Download Log" -msgstr "أداء التحميل" - -#: ../includes/metabox.php:602 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "كل مرة يتم فيها تحميل ملف، فهو مسجل بالأسفل" - -#: ../includes/metabox.php:623 -msgid "Downloaded by:" -msgstr "تم تحميلة بواسطة:" - -#: ../includes/metabox.php:627 -msgid "IP Address:" -msgstr "عنوان الآيبي:" - -#: ../includes/metabox.php:631 -msgid "File: " -msgstr "ملف:" - -#: ../includes/metabox.php:640 -msgid "No file downloads yet yet" -msgstr "ليس هناك ملفات تحميل بعد بعد" - -#: ../includes/misc-functions.php:185 -msgid "US Dollars ($)" -msgstr "دولار أمريكي ($)" - -#: ../includes/misc-functions.php:186 -msgid "Euros (€)" -msgstr "يورو (€)" - -#: ../includes/misc-functions.php:187 -msgid "Pounds Sterling (£)" -msgstr "جنيه استرليني (£)" - -#: ../includes/misc-functions.php:188 -msgid "Australian Dollars ($)" -msgstr "دولار أسترالي ($)" - -#: ../includes/misc-functions.php:189 -msgid "Brazilian Real ($)" -msgstr "ريال برازيلي ($)" - -#: ../includes/misc-functions.php:190 -msgid "Canadian Dollars ($)" -msgstr "دولار كندي ($)" - -#: ../includes/misc-functions.php:191 -msgid "Czech Koruna" -msgstr "الكورونا التشيكية" - -#: ../includes/misc-functions.php:192 -msgid "Danish Krone" -msgstr "الكورونا الدنماركية" - -#: ../includes/misc-functions.php:193 -msgid "Hong Kong Dollar ($)" -msgstr "دولار هوج كونج ($)" - -#: ../includes/misc-functions.php:194 -msgid "Hungarian Forint" -msgstr "الفرونت الهنغاري" - -#: ../includes/misc-functions.php:195 -msgid "Israeli Shekel" -msgstr "الشيكل الإسرائيلي" - -#: ../includes/misc-functions.php:196 -msgid "Japanese Yen (¥)" -msgstr "ين ياباني (¥)" - -#: ../includes/misc-functions.php:197 -msgid "Malaysian Ringgits" -msgstr "رينغيت ماليزي" - -#: ../includes/misc-functions.php:198 -msgid "Mexican Peso ($)" -msgstr "بيزو مكسيكي ($)" - -#: ../includes/misc-functions.php:199 -msgid "New Zealand Dollar ($)" -msgstr "الدولار النيوزلندي ($)" - -#: ../includes/misc-functions.php:200 -msgid "Norwegian Krone" -msgstr "الكرونا النرويجية" - -#: ../includes/misc-functions.php:201 -msgid "Philippine Pesos" -msgstr "البيزو الفلبيني" - -#: ../includes/misc-functions.php:202 -msgid "Polish Zloty" -msgstr "الزلوتي البولندي" - -#: ../includes/misc-functions.php:203 -msgid "Singapore Dollar ($)" -msgstr "الدولار السنغافوري ($)" - -#: ../includes/misc-functions.php:204 -msgid "Swedish Krona" -msgstr "الكرونا السويدية" - -#: ../includes/misc-functions.php:205 -msgid "Swiss Franc" -msgstr "الفرنك السويسر" - -#: ../includes/misc-functions.php:206 -msgid "Taiwan New Dollars" -msgstr "الدولارات التايوانية الجديدة" - -#: ../includes/misc-functions.php:207 -msgid "Thai Baht" -msgstr "البات التايلندي" - -#: ../includes/misc-functions.php:208 -msgid "Indian Rupee" -msgstr "الربية الهندية" - -#: ../includes/misc-functions.php:209 -msgid "Turkish Lira" -msgstr "الليرة التركية" - -#: ../includes/misc-functions.php:210 -msgid "Iranian Rial" -msgstr "ريال إيراني" - -#: ../includes/payment-functions.php:295 -msgid "Pending" -msgstr "في الانتظار" - -#: ../includes/payment-functions.php:296 -msgid "Complete" -msgstr "تم" - -#: ../includes/payment-functions.php:297 -msgid "Refunded" -msgstr "إستعادة المال" - -#: ../includes/pdf-reports.php:36 -msgid "to" -msgstr "إلى" - -#: ../includes/pdf-reports.php:41 -#: ../includes/pdf-reports.php:52 -msgid "Sales and earnings reports for the current year for all products" -msgstr "تقرير مبيعات و أرباح السنة الحالية لجميع المنتجات" - -#: ../includes/pdf-reports.php:42 -#: ../includes/pdf-reports.php:43 -msgid "Easy Digital Downloads" -msgstr "Easy Digital Downloads ( التحميل الرقمي السهل )" - -#: ../includes/pdf-reports.php:57 -msgid "Date Range: " -msgstr "نطاق التاريخ:" - -#: ../includes/pdf-reports.php:61 -msgid "Table View" -msgstr "مشاهدة الجدول" - -#: ../includes/pdf-reports.php:65 -msgid "Product Name" -msgstr "اسم المنتج" - -#: ../includes/pdf-reports.php:69 -msgid "Number of Sales" -msgstr "عدد المبيعات" - -#: ../includes/pdf-reports.php:70 -msgid "Earnings to Date" -msgstr "تاريخ الأرباح" - -#: ../includes/pdf-reports.php:112 -msgid "No Downloads found." -msgstr "ليس هناك تحميلات" - -#: ../includes/pdf-reports.php:119 -msgid "Graph View" -msgstr "مشاهدة الرسم البياني" - -#: ../includes/pdf-reports.php:212 -msgid "Sales and Earnings by Month for all Products" -msgstr "مبيعات و أرباح كل المنتجات خلال شهر" - -#: ../includes/pdf-reports.php:226 -msgid "Jan" -msgstr "يناير" - -#: ../includes/pdf-reports.php:227 -msgid "Feb" -msgstr "فبراير" - -#: ../includes/pdf-reports.php:228 -msgid "Mar" -msgstr "مارس" - -#: ../includes/pdf-reports.php:229 -msgid "Apr" -msgstr "أبريل" - -#: ../includes/pdf-reports.php:230 -msgid "May" -msgstr "مايو" - -#: ../includes/pdf-reports.php:231 -msgid "June" -msgstr "يونيو" - -#: ../includes/pdf-reports.php:232 -msgid "July" -msgstr "يوليو" - -#: ../includes/pdf-reports.php:233 -msgid "Aug" -msgstr "أغسطس" - -#: ../includes/pdf-reports.php:234 -msgid "Sept" -msgstr "سبتمبر" - -#: ../includes/pdf-reports.php:235 -msgid "Oct" -msgstr "أوكتوبر" - -#: ../includes/pdf-reports.php:236 -msgid "Nov" -msgstr "نوفمبر" - -#: ../includes/pdf-reports.php:237 -msgid "Dec" -msgstr "ديسمبر" - -#: ../includes/post-types.php:44 -#, php-format -msgid "Add New %1$s" -msgstr "أضف %1$ جديد" - -#: ../includes/post-types.php:45 -#, php-format -msgid "Edit %1$s" -msgstr "تحرير %1$" - -#: ../includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "%1$ جديد" - -#: ../includes/post-types.php:47 -#, php-format -msgid "All %2$s" -msgstr "الكل %2$" - -#: ../includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "عرض %1$" - -#: ../includes/post-types.php:49 -#, php-format -msgid "Search %2$s" -msgstr "بحث %2$" - -#: ../includes/post-types.php:50 -#, php-format -msgid "No %2$s found" -msgstr "لم يتم العثور على %2$" - -#: ../includes/post-types.php:51 -#, php-format -msgid "No %2$s found in Trash" -msgstr "لم يتم العثور على %2$ في سلة المحذوفات" - -#: ../includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "%2$" - -#: ../includes/post-types.php:79 -msgid "Payments" -msgstr "المدفوعات" - -#: ../includes/post-types.php:80 -msgid "Payment" -msgstr "الدفع" - -#: ../includes/post-types.php:82 -msgid "Add New Payment" -msgstr "أضف دفع جديد" - -#: ../includes/post-types.php:83 -msgid "Edit Payment" -msgstr "تحرير الدفع" - -#: ../includes/post-types.php:84 -msgid "New Payment" -msgstr "دفع جديد" - -#: ../includes/post-types.php:85 -msgid "All Payments" -msgstr "كل المدفوعات" - -#: ../includes/post-types.php:86 -msgid "View Payment" -msgstr "عرض الدفع" - -#: ../includes/post-types.php:87 -msgid "Search Payments" -msgstr "البحث في المدفوعات" - -#: ../includes/post-types.php:88 -msgid "No Payments found" -msgstr "لم يتم العثور على المدفوعات" - -#: ../includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "لم يتم العثور على مدفوعات في سلة المحذوفات" - -#: ../includes/post-types.php:126 -msgid "Downloads" -msgstr "التحميلات" - -#: ../includes/post-types.php:174 -msgid "Category" -msgstr "الفئة" - -#: ../includes/post-types.php:175 -msgid "Search Categories" -msgstr "بحث في الفئة" - -#: ../includes/post-types.php:176 -msgid "All Categories" -msgstr "كل الفئات" - -#: ../includes/post-types.php:177 -msgid "Parent Category" -msgstr "الفئة الرئيسية" - -#: ../includes/post-types.php:178 -msgid "Parent Category:" -msgstr "الفئة الرئيسية:" - -#: ../includes/post-types.php:179 -msgid "Edit Category" -msgstr "تحرير الفئة" - -#: ../includes/post-types.php:180 -msgid "Update Category" -msgstr "تحديث الفئة" - -#: ../includes/post-types.php:181 -msgid "Add New Category" -msgstr "أضافة فئة جديدة" - -#: ../includes/post-types.php:182 -msgid "New Category Name" -msgstr "اسم فئة جديدة" - -#: ../includes/post-types.php:199 -msgid "Tag" -msgstr "علامة" - -#: ../includes/post-types.php:200 -msgid "Search Tags" -msgstr "البحث في العلامات" - -#: ../includes/post-types.php:201 -msgid "All Tags" -msgstr "كل العلامات" - -#: ../includes/post-types.php:202 -msgid "Parent Tag" -msgstr "العلامة الرئيسية" - -#: ../includes/post-types.php:203 -msgid "Parent Tag:" -msgstr "العلامة الرئيسية:" - -#: ../includes/post-types.php:204 -msgid "Edit Tag" -msgstr "تحرير العلامة" - -#: ../includes/post-types.php:205 -msgid "Update Tag" -msgstr "تحديث العلامة" - -#: ../includes/post-types.php:206 -msgid "Add New Tag" -msgstr "إضافة علامة جديدة" - -#: ../includes/post-types.php:207 -msgid "New Tag Name" -msgstr "اسم علامة جديدة" - -#: ../includes/post-types.php:239 -#: ../includes/post-types.php:240 -msgid "Download updated." -msgstr "تحميل محدث" - -#: ../includes/post-types.php:241 -msgid "Download published." -msgstr "تحميل منشور" - -#: ../includes/post-types.php:242 -msgid "Download saved." -msgstr "تحميل محفوظ" - -#: ../includes/post-types.php:243 -msgid "Download submitted." -msgstr "تم عرض التحميل" - -#: ../includes/process-download.php:266 -#: ../includes/process-download.php:283 -msgid "Sorry but this file does not exist." -msgstr "عفواً لكن هذا الملف غير موجود" - -#: ../includes/process-download.php:294 -msgid "You do not have permission to download this file" -msgstr "غير مصرح لك بتحميل هذا الملف" - -#: ../includes/process-download.php:294 -msgid "Purchase Verification Failed" -msgstr "فشل التحقق من الشراء" - -#: ../includes/process-purchase.php:206 -msgid "The selected gateway is not active" -msgstr "البوابة التي اخترتها ليست نشطة" - -#: ../includes/process-purchase.php:210 -msgid "No gateway has been selected" -msgstr "لم يتم اختيار البوابة" - -#: ../includes/process-purchase.php:259 -msgid "You must agree to the terms of use" -msgstr "يجب أن توافق على شروط الاستخدام" - -#: ../includes/process-purchase.php:289 -msgid "Please enter a valid email address." -msgstr "رجاءً أدخل نوان بريد إلكتروني صحيح" - -#: ../includes/process-purchase.php:303 -msgid "The user information is invalid." -msgstr "معلومات المستخدم غير صحيحة" - -#: ../includes/process-purchase.php:349 -msgid "Username already taken" -msgstr "هذا الاسم قد اختير مسبقاً" - -#: ../includes/process-purchase.php:355 -msgid "Invalid username" -msgstr "اسم مستخدم غير صالح" - -#: ../includes/process-purchase.php:366 -msgid "You must register or login to complete your purchase" -msgstr "يجب أن تسجل او تدخل لتشتري" - -#: ../includes/process-purchase.php:378 -#: ../includes/process-purchase.php:522 -msgid "Invalid email" -msgstr "بريد إلكتروني غير صالح" - -#: ../includes/process-purchase.php:384 -msgid "Email already used" -msgstr "البريد الإلكتروني موجود مسبقاً" - -#: ../includes/process-purchase.php:396 -#: ../includes/process-purchase.php:529 -msgid "Enter an email" -msgstr "أدخل البريد الإلكتروني" - -#: ../includes/process-purchase.php:407 -msgid "Passwords don't match" -msgstr "كلمة المرور غير متطابقة" - -#: ../includes/process-purchase.php:422 -#: ../includes/process-purchase.php:487 -msgid "Enter a password" -msgstr "أدخل كلمة المرور" - -#: ../includes/process-purchase.php:427 -msgid "Enter the password confirmation" -msgstr "أدخل توكيد كلمة السر" - -#: ../includes/process-purchase.php:454 -msgid "You must login or register to complete your purchase" -msgstr "يجب أن تسجل أو تدخل لتتم عملية الشراء" - -#: ../includes/register-settings.php:41 -msgid "Test Mode" -msgstr "اختبار الوضع" - -#: ../includes/register-settings.php:42 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "في وضع التجربة لن تتم معالجة أي عمليات مباشرة. لاستخدام وضع التجربة بشكل تام, يجب أن يكون لديك حساب sandbox / تجربة لبوابة الدفع التي تجربها" - -#: ../includes/register-settings.php:47 -msgid "Checkout Page" -msgstr "صفحة الدفع و الخروج" - -#: ../includes/register-settings.php:48 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "هذه صفحة الدفع و الخروج حيث يستكمل المشترين شرائهم" - -#: ../includes/register-settings.php:54 -msgid "Success Page" -msgstr "صفحة نجاح العملية" - -#: ../includes/register-settings.php:55 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "هذه الصفحة التي يُرسل لها المشترين بعد إتمام عملية الشراء" - -#: ../includes/register-settings.php:61 -msgid "Download Links on Success Page" -msgstr "تحميل الروابط على صفحة اتمام العملية بنجاح" - -#: ../includes/register-settings.php:62 -msgid "Show a list of all download links on the success page after completing a purchase?" -msgstr "هل تريد عرض قائمة بجميع روابط التحميل في صفحة تمت العملية بنجاح بعد اتمام الشراء؟" - -#: ../includes/register-settings.php:67 -msgid "Currency Settings" -msgstr "إعدادات العملة" - -#: ../includes/register-settings.php:68 -msgid "Configure the currency options" -msgstr "تكوين خيارات العملة" - -#: ../includes/register-settings.php:73 -msgid "Currency" -msgstr "العملة" - -#: ../includes/register-settings.php:74 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "اختر العملة. لاحظ أن بعض بوابات الدفع لديها قيود للعملة" - -#: ../includes/register-settings.php:80 -msgid "Currency Position" -msgstr "مكان العملة" - -#: ../includes/register-settings.php:81 -msgid "Choose the location of the currency sign." -msgstr "اختر مكان علامة العملة" - -#: ../includes/register-settings.php:84 -msgid "Before - $10" -msgstr "قبل - 10$" - -#: ../includes/register-settings.php:85 -msgid "After - 10$" -msgstr "بعد - $10" - -#: ../includes/register-settings.php:90 -msgid "Thousands Separator" -msgstr "فواصل الألوف" - -#: ../includes/register-settings.php:91 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "الرمز ( بالعادة , أو . ) لفصل الألوف" - -#: ../includes/register-settings.php:98 -msgid "Decimal Separator" -msgstr "الفاصل العشري" - -#: ../includes/register-settings.php:99 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "بالعادة الرمز ( , أو . ) للفصل بين النقاط العشرية" - -#: ../includes/register-settings.php:110 -msgid "Payment Gateways" -msgstr "بوابات الدفع الألكتروني" - -#: ../includes/register-settings.php:111 -msgid "Choose the payment gateways you want to enable." -msgstr "اختر بوابات الدفع التي تريد تمكينها" - -#: ../includes/register-settings.php:117 -msgid "Accepted Payment Method Icons" -msgstr "أيقونات طرق الدفع المقبولة" - -#: ../includes/register-settings.php:118 -msgid "Display icons for the selected payment methods" -msgstr "عرض أيقونات طرق الدفع المحددة" - -#: ../includes/register-settings.php:118 -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "ينبغي أن تضبط إعدادات بوابة الدفع الخاصة بك إن كنت تقبل بطاقات الائتمان" - -#: ../includes/register-settings.php:131 -msgid "PayPal Settings" -msgstr "إعدادات paypal" - -#: ../includes/register-settings.php:132 -msgid "Configure the PayPal settings" -msgstr "ضبط اعدادات paypal" - -#: ../includes/register-settings.php:137 -msgid "PayPal Email" -msgstr "البريد الإلكتروني للـ paypal" - -#: ../includes/register-settings.php:138 -msgid "Enter your PayPal account's email" -msgstr "ادخل حسابك الإلكتروني في الـ paypal" - -#: ../includes/register-settings.php:144 -msgid "Alternate PayPal Purchase Verification" -msgstr "بديل التحقق من الشراء في الـ PayPal" - -#: ../includes/register-settings.php:145 -msgid "If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal." -msgstr "إن لم يؤشر على أن الدفع قد تم, فتحقق من هذه الخانة. علماً أن هذا سيعيد المشترين إلى موقعك من الـ Paypal" - -#: ../includes/register-settings.php:150 -msgid "Disable PayPal IPN Verification" -msgstr "تعطيل التحقق من PayPal IPN" - -#: ../includes/register-settings.php:151 -msgid "If payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases." -msgstr "إن لم يؤشر على أن الدفع قد تم. فعلم على هذه الخانة، و أشر هذه الخانة. هذا يفرض على الموقع بأن يستخدم طرق أقل أمناً للتحقق من المشتريات" - -#: ../includes/register-settings.php:160 -msgid "Email Template" -msgstr "قالب البريد الألكتروني" - -#: ../includes/register-settings.php:161 -msgid "Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" to see the new template." -msgstr "اختر قالباً. اضغط /\"حفظ التغييرات/\" ثم / \"عرض فاتورة المشتريات /\" لرؤية القالب الجديد" - -#: ../includes/register-settings.php:173 -msgid "From Name" -msgstr "من الاسم" - -#: ../includes/register-settings.php:174 -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "اسم المكان الذي أصدرت منه فاتورة مشترياتك. يحتمل أن يكون اسم موقعك او محلك التجاري" - -#: ../includes/register-settings.php:179 -msgid "From Email" -msgstr "من البريد الإلكتروني" - -#: ../includes/register-settings.php:180 -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "البريد الإلكتروني لإرسال فاتورة المشتريات منه. و هذا يمثل / \"من/\" و / \"رد على/\" العنوان" - -#: ../includes/register-settings.php:185 -msgid "Purchase Email Subject" -msgstr "موضوع رسالة الشراء" - -#: ../includes/register-settings.php:186 -msgid "Enter the subject line for the purchase receipt email" -msgstr "ادخل سطر موضوع رسالة فاتورة الشراء " - -#: ../includes/register-settings.php:192 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "أدخل عنوان البريد الإلكتروني الذي يرسل للمستخدمين بعد عملية شراء ناجحة. HTML مقبول. قالب علامات مقبول" - -#: ../includes/register-settings.php:193 -msgid "A list of download URLs for each download purchased" -msgstr "قائمة بروابط التحميل لكل تحميل تم شراءه" - -#: ../includes/register-settings.php:194 -msgid "The buyer's name" -msgstr "اسم المشتري" - -#: ../includes/register-settings.php:195 -msgid "The date of the purchase" -msgstr "تاريخ الشراء" - -#: ../includes/register-settings.php:196 -msgid "The total price of the purchase" -msgstr "السعر الإجمالي للشراء" - -#: ../includes/register-settings.php:197 -msgid "The unique ID number for this purchase receipt" -msgstr "رقم المعرف لفاتورة الشراء" - -#: ../includes/register-settings.php:198 -msgid "The method of payment used for this purchase" -msgstr "طريقة الدفع المستخدمة لهذا الشراء" - -#: ../includes/register-settings.php:199 -msgid "Your site name" -msgstr "اسم موقعك" - -#: ../includes/register-settings.php:208 -msgid "Disable Styles" -msgstr "تعطيل الأنماط" - -#: ../includes/register-settings.php:209 -msgid "Check this to disable all included styling" -msgstr "علم على هذا لتعطيل كل الأنماط" - -#: ../includes/register-settings.php:214 -msgid "Checkout Button Color" -msgstr "لون زر الدفع و الخروج" - -#: ../includes/register-settings.php:215 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "اختر لون الزر الذي تريد استخدامه لزر الدفع و الخروج" - -#: ../includes/register-settings.php:225 -msgid "Disable Ajax" -msgstr "عطل الأجاكس Ajax" - -#: ../includes/register-settings.php:226 -msgid "Check this to disable AJAX for the shopping cart." -msgstr "علم على هذا لتعطيل Ajax سلة التسوق" - -#: ../includes/register-settings.php:231 -msgid "Enable jQuery Validation" -msgstr "شغيل فاعلية jQuery " - -#: ../includes/register-settings.php:232 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "علم هذا لتشغيل فاعلية jQuery في استمارة الدفع و الخروج" - -#: ../includes/register-settings.php:237 -msgid "Disable Guest Checkout" -msgstr "تعطيل دفع و خروج الزائر" - -#: ../includes/register-settings.php:238 -msgid "Require that users be logged-in to purchase files." -msgstr "أطلب أن يكون المستخدمين مسجلين لشراء الملفات" - -#: ../includes/register-settings.php:243 -msgid "Show Register / Login Form?" -msgstr "اضهار استمارة التسجيل/الدخول؟" - -#: ../includes/register-settings.php:244 -msgid "Display the registration and login forms on the checkout page for non-logged-in users." -msgstr "اعرض استمارة التسجيل و الدخول في صفحة الدفع و الخروج لغير المشتركين" - -#: ../includes/register-settings.php:249 -msgid "Download Link Expiration" -msgstr "انتهاء صلاحية الرابط" - -#: ../includes/register-settings.php:250 -msgid "How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours." -msgstr "إلى متى سيظل رابط التحميل فعال؟ المدة الإفتراضية 24 ساعة من وقت شراءه. ادخل الوقت بالساعات" - -#: ../includes/register-settings.php:256 -msgid "Disable Redownload?" -msgstr "عطل إعادة التحميل؟" - -#: ../includes/register-settings.php:257 -msgid "Check this if you do not want to allow users to redownload items from their purchase history." -msgstr "علو على هذا إن لم تكن تريد للمستخدمين أن يعيدو التحميل من صفحة تاريخ مشترياتهم" - -#: ../includes/register-settings.php:262 -msgid "Terms of Agreement" -msgstr "شروط الاتفاقية" - -#: ../includes/register-settings.php:268 -msgid "Agree to Terms" -msgstr "الموافقة على الشروط" - -#: ../includes/register-settings.php:269 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing." -msgstr "علو على هذا لاظهار الموافقة على شروط الاتفاقية في صفحة الدفع و الخروج التي ينبغي على المستخدمين الموافقة عليها قبل الشراء" - -#: ../includes/register-settings.php:274 -msgid "Agree to Terms Label" -msgstr "ملصق شروط الاتفاقية" - -#: ../includes/register-settings.php:275 -msgid "Label shown next to the agree to terms check box." -msgstr "التسمية التي تظهر بجانب خانة الإختيار من الموافقة على الشروط" - -#: ../includes/register-settings.php:281 -msgid "Agreement Text" -msgstr "نص الإتفاقية" - -#: ../includes/register-settings.php:282 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "إن علمت على شروط الموافقة، فأدخل شروط الإتفاقية هنا" - -#: ../includes/register-settings.php:287 -msgid "Complete Purchase Text" -msgstr "نص الشراء الكامل" - -#: ../includes/register-settings.php:288 -msgid "The button label for completing a purchase." -msgstr "زر التسمية لإكمال عملية الشراء" - -#: ../includes/register-settings.php:314 -msgid "General Settings" -msgstr "إعدادات عامة" - -#: ../includes/register-settings.php:340 -msgid "Payment Gateway Settings" -msgstr "إعدادات بوابة الدفع" - -#: ../includes/register-settings.php:366 -msgid "Email Settings" -msgstr "إعدادات البريد الإلكتروني" - -#: ../includes/register-settings.php:392 -msgid "Style Settings" -msgstr "إعدادات النمط (ستايل)" - -#: ../includes/register-settings.php:419 -msgid "Misc Settings" -msgstr "إعدادات منوعة" - -#: ../includes/register-settings.php:746 -msgid "Settings Updated" -msgstr "الإعدادات المحدثة" - -#: ../includes/scripts.php:40 -msgid "Please enter a discount code" -msgstr "يرجى ادخال رمز الخصم" - -#: ../includes/scripts.php:41 -msgid "Discount Applied" -msgstr "الخصم التطبيقي" - -#: ../includes/scripts.php:42 -msgid "Please enter an email address before applying a discount code" -msgstr "يرجى ادخال عنوان البريد الإلكتروني قبل تطبيق رمز الخصم" - -#: ../includes/scripts.php:44 -msgid "You have already added this item to your cart" -msgstr "لقد سبق و أن اضفت هذا المنتج إلى سلتك" - -#: ../includes/scripts.php:45 -msgid "Your cart is empty" -msgstr "سلتك فارغة" - -#: ../includes/scripts.php:46 -msgid "Loading" -msgstr "تحميل" - -#: ../includes/scripts.php:123 -msgid "Add New Download" -msgstr "أضف تحميل جديد" - -#: ../includes/scripts.php:124 -msgid "Use This File" -msgstr "استخدم هذا الملف" - -#: ../includes/scripts.php:125 -msgid "Sorry, not available for variable priced products." -msgstr "عذراً، غير متوفر لمنتج متغير سعره" - -#: ../includes/scripts.php:126 -msgid "Are you sure you wish to delete this payment?" -msgstr "هل أنت متأكد من حذفك لهذا الدفع" - -#: ../includes/shortcodes.php:194 -msgid "Purchase All Items" -msgstr "شراء كل المواد" - -#: ../includes/shortcodes.php:336 -#, php-format -msgid "No %s found" -msgstr "لم يتم العثور على %s" - -#: ../includes/template-functions.php:48 -#, php-format -msgid "No checkout page has been configured. Visit Settings to set one." -msgstr "لم يتم تكوين صفحة الدفع و الخروج. اذهب إلى إعدادات to set one لتكوين واحدة" - -#: ../includes/template-functions.php:167 -msgid "added to your cart" -msgstr "أضيف إلى سلتك" - -#: ../includes/template-functions.php:263 -msgid "Gray" -msgstr "رمادي" - -#: ../includes/template-functions.php:264 -msgid "Pink" -msgstr "وردي" - -#: ../includes/template-functions.php:265 -msgid "Blue" -msgstr "أزرق" - -#: ../includes/template-functions.php:266 -msgid "Green" -msgstr "أخضر" - -#: ../includes/template-functions.php:267 -msgid "Teal" -msgstr "فيروزي" - -#: ../includes/template-functions.php:268 -msgid "Black" -msgstr "أسود" - -#: ../includes/template-functions.php:269 -msgid "Dark Gray" -msgstr "رمادي غامق" - -#: ../includes/template-functions.php:270 -msgid "Orange" -msgstr "برتقالي" - -#: ../includes/template-functions.php:271 -msgid "Purple" -msgstr "بنفسجي" - -#: ../includes/template-functions.php:272 -msgid "Slate" -msgstr "حجري" - -#: ../includes/template-functions.php:295 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "سبق و اشتريت هذه المادة، لكن يمكنك شرائها مرة أخرى." - -#: ../includes/thickbox.php:29 -#: ../includes/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "إدراج %" - -#: ../includes/thickbox.php:30 -msgid "Insert Download" -msgstr "إدراج تحميل" - -#: ../includes/thickbox.php:65 -msgid "You must choose a download" -msgstr "ينبغي أن تختار تحميل" - -#: ../includes/thickbox.php:88 -#, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "استخدم النموذج أدناه لإدراج كود قصير للشراء %" - -#: ../includes/thickbox.php:91 -#, php-format -msgid "Choose a %s" -msgstr "اختيار %" - -#: ../includes/thickbox.php:100 -msgid "Choose a style" -msgstr "اختيار النمط" - -#: ../includes/thickbox.php:111 -msgid "Choose a button color" -msgstr "اختيار لون الأزرار" - -#: ../includes/thickbox.php:120 -msgid "Link text . . ." -msgstr "ابط النص..." - -#: ../includes/thickbox.php:124 -msgid "Cancel" -msgstr "إلغاء" - -#: ../includes/thickbox.php:126 -msgid "Button Styles" -msgstr "نمط الأزرار" - -#: ../includes/widgets.php:39 -msgid "Downloads Cart" -msgstr "سلة التحميلات" - -#: ../includes/widgets.php:39 -msgid "Display the downloads shopping cart" -msgstr "تعطيل سلة تسوق التحميلات" - -#: ../includes/widgets.php:82 -#: ../includes/widgets.php:162 -msgid "Title:" -msgstr "الاسم:" - -#: ../includes/widgets.php:87 -msgid "Show Quantity:" -msgstr "اظهار الكمية:" - -#: ../includes/widgets.php:112 -msgid "Downloads Categories / Tags" -msgstr "فئات/علامات التحميلات" - -#: ../includes/widgets.php:112 -msgid "Display the downloads categories or tags" -msgstr "عرض فئات أو علامات التحميلات" - -#: ../includes/widgets.php:167 -msgid "Taxonomy:" -msgstr "التصنيف:" - -#: ../includes/widgets.php:193 -msgid "Display a user's purchase history" -msgstr "عرض تاريخ شراء المستخدم" - -#: ../includes/widgets.php:232 -msgid "No downloadable files found." -msgstr "لم يُعثر على ملفات قابلة للتحميل" - -#: ../includes/widgets.php:259 -msgid "Title" -msgstr "الاسم" - -#: ../includes/admin-pages/add-ons.php:69 -msgid "Add Ons for Easy Digital Downloads" -msgstr "إضافات Easy Digital Downloads " - -#: ../includes/admin-pages/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "هذه الإضافات تعزز وظائف Easy Digital Downloads" - -#: ../includes/admin-pages/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "تصفح جميع الملحقات" - -#: ../includes/admin-pages/discount-codes.php:40 -#: ../includes/admin-pages/discount-codes.php:53 -msgid "Code" -msgstr "رمز" - -#: ../includes/admin-pages/discount-codes.php:41 -#: ../includes/admin-pages/discount-codes.php:54 -msgid "Amount" -msgstr "الكمية" - -#: ../includes/admin-pages/discount-codes.php:42 -#: ../includes/admin-pages/discount-codes.php:55 -msgid "Uses" -msgstr "استخدام" - -#: ../includes/admin-pages/discount-codes.php:43 -#: ../includes/admin-pages/discount-codes.php:56 -msgid "Max Uses" -msgstr "أقصى استخدام" - -#: ../includes/admin-pages/discount-codes.php:44 -#: ../includes/admin-pages/discount-codes.php:57 -msgid "Start Date" -msgstr "تاريخ البدء" - -#: ../includes/admin-pages/discount-codes.php:45 -#: ../includes/admin-pages/discount-codes.php:58 -msgid "Expiration" -msgstr "انتهاء الصلاحية" - -#: ../includes/admin-pages/discount-codes.php:47 -#: ../includes/admin-pages/discount-codes.php:60 -msgid "Actions" -msgstr "الإجراءات" - -#: ../includes/admin-pages/discount-codes.php:82 -#: ../includes/admin-pages/discount-codes.php:84 -msgid "unlimited" -msgstr "غير محدود" - -#: ../includes/admin-pages/discount-codes.php:93 -msgid "No start date" -msgstr "لا يوجد تاريخ للبدء" - -#: ../includes/admin-pages/discount-codes.php:100 -msgid "Expired" -msgstr "انتهت صلاحيته" - -#: ../includes/admin-pages/discount-codes.php:102 -msgid "no expiration" -msgstr "لا يوجد انتهاء للصلاحية" - -#: ../includes/admin-pages/discount-codes.php:108 -#: ../includes/admin-pages/payments-history.php:183 -msgid "Edit" -msgstr "تحرير" - -#: ../includes/admin-pages/discount-codes.php:110 -msgid "Deactivate" -msgstr "تعطيل" - -#: ../includes/admin-pages/discount-codes.php:112 -msgid "Activate" -msgstr "تفعيل" - -#: ../includes/admin-pages/discount-codes.php:114 -#: ../includes/admin-pages/payments-history.php:185 -msgid "Delete" -msgstr "إلغاء" - -#: ../includes/admin-pages/discount-codes.php:119 -msgid "No discount codes have been created." -msgstr "لم ينشأ رمز للخصم" - -#: ../includes/admin-pages/payments-history.php:90 -msgid "All" -msgstr "الكل" - -#: ../includes/admin-pages/payments-history.php:95 -msgid "Completed" -msgstr "تم" - -#: ../includes/admin-pages/payments-history.php:106 -msgid "Export" -msgstr "تصدير" - -#: ../includes/admin-pages/payments-history.php:110 -msgid "Payment mode" -msgstr "حالة الدفع" - -#: ../includes/admin-pages/payments-history.php:112 -msgid "Live" -msgstr "مباشر" - -#: ../includes/admin-pages/payments-history.php:113 -msgid "Test" -msgstr "تجربة" - -#: ../includes/admin-pages/payments-history.php:123 -msgid "Payments per page" -msgstr "المدفوعات في كل صفحة" - -#: ../includes/admin-pages/payments-history.php:125 -msgid "Show" -msgstr "اعرض" - -#: ../includes/admin-pages/payments-history.php:131 -msgid "Showing payments for: " -msgstr "عرض المدفوعات لـ:" - -#: ../includes/admin-pages/payments-history.php:131 -msgid "clear" -msgstr "مسح" - -#: ../includes/admin-pages/payments-history.php:184 -msgid "Resend Purchase Receipt" -msgstr "إعادة إرسال فاتورة الشراء" - -#: ../includes/admin-pages/payments-history.php:197 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "تفاصيل دفع المشتريات" - -#: ../includes/admin-pages/payments-history.php:197 -msgid "View Order Details" -msgstr "عرض تفاصيل الطلب" - -#: ../includes/admin-pages/payments-history.php:205 -msgid "Purchased File" -msgstr "ملف الشراء" - -#: ../includes/admin-pages/payments-history.php:205 -msgid "Purchased Files" -msgstr "الملفات التي تم شراءها" - -#: ../includes/admin-pages/payments-history.php:249 -msgid "Date and Time:" -msgstr "الوقت و التاريخ:" - -#: ../includes/admin-pages/payments-history.php:250 -msgid "Discount used:" -msgstr "الخصم المستخدم" - -#: ../includes/admin-pages/payments-history.php:251 -msgid "Total:" -msgstr "إجمالي:" - -#: ../includes/admin-pages/payments-history.php:254 -msgid "Buyer's Personal Details:" -msgstr "تفاصيل المشتري الشخصية:" - -#: ../includes/admin-pages/payments-history.php:256 -msgid "Name:" -msgstr "الاسم:" - -#: ../includes/admin-pages/payments-history.php:257 -msgid "Email:" -msgstr "البريد الإلكتروني:" - -#: ../includes/admin-pages/payments-history.php:266 -msgid "Payment Method:" -msgstr "طرق الدفع:" - -#: ../includes/admin-pages/payments-history.php:271 -msgid "Purchase Key" -msgstr "مفتاح الشراء" - -#: ../includes/admin-pages/payments-history.php:274 -msgid "Close" -msgstr "مغلق" - -#: ../includes/admin-pages/payments-history.php:304 -msgid "Total Earnings:" -msgstr "إجمالي الأرباح:" - -#: ../includes/admin-pages/reports.php:41 -msgid "Download Sales and Earnings PDF Report for all Products" -msgstr "تقارير PDF لمبيعات و أرباح كل المنتجات" - -#: ../includes/admin-pages/reports.php:42 -msgid "Download a CSV Customers List" -msgstr "تحميل قائمة العملاء CSV" - -#: ../includes/admin-pages/reports.php:44 -msgid "Please Note: Transactions created while in test mode are not included on this page or in the PDF reports." -msgstr "ملاحظة: العمليات التي تتم في حالة التجربة ليست موجودة في هذه الصفحة أو في تقارير PDF" - -#: ../includes/admin-pages/settings-old.php:12 -msgid "Pages" -msgstr "الصفحات" - -#: ../includes/admin-pages/settings-old.php:14 -#: ../includes/admin-pages/settings.php:37 -msgid "Misc" -msgstr "منوع" - -#: ../includes/admin-pages/settings-old.php:25 -msgid "Purchase Form" -msgstr "نموذج شراء" - -#: ../includes/admin-pages/settings-old.php:31 -#: ../includes/admin-pages/settings-old.php:36 -msgid "Purchase Page" -msgstr "صفحة الشراء" - -#: ../includes/admin-pages/settings-old.php:49 -#: ../includes/admin-pages/settings-old.php:78 -msgid "No pages found" -msgstr "لم يتم العثور على صفحات" - -#: ../includes/admin-pages/settings-old.php:53 -msgid "Choose the page that holds the checkout form short code." -msgstr "اختيار الصفحة التي تحمل شكل الرمز القصير للدفع و الخروج" - -#: ../includes/admin-pages/settings-old.php:82 -msgid "Choose the page users are directed to after a successful purchase." -msgstr "اختارصفحة يرسل إليها المشترين بعد إتمام عملية الشراء بنجاح" - -#: ../includes/admin-pages/settings-old.php:110 -msgid "Check this to use the plugin in test mode." -msgstr "ضع علامة على هذا لتستخدم الإضافة في حالة التجربة" - -#: ../includes/admin-pages/settings-old.php:124 -#: ../includes/admin-pages/settings-old.php:129 -msgid "Gateways" -msgstr "بوابات الدفع " - -#: ../includes/admin-pages/settings-old.php:140 -msgid "Check each of the payment gateways you would like to enable. Configure the selected gateways below." -msgstr "jتحقق من كل بوابات الدفع التي ترغب بتمكينها. اضبط البوابة المختارة أدناه" - -#: ../includes/admin-pages/settings-old.php:163 -msgid "Enter your PayPal Email." -msgstr "ادخل حساب بريدك الإلكتروني في الـ paypal" - -#: ../includes/admin-pages/settings-old.php:177 -msgid "Miscellaneous" -msgstr "منوعات" - -#: ../includes/admin-pages/settings-old.php:183 -#: ../includes/admin-pages/settings-old.php:188 -msgid "Enable Ajax" -msgstr "تمكين الأجاكس Ajax" - -#: ../includes/admin-pages/settings-old.php:192 -msgid "Check this to enable AJAX for the shopping cart." -msgstr "علم على هذا لتمكين Ajax لسلة التسوق" - -#: ../includes/admin-pages/settings-old.php:199 -#: ../includes/admin-pages/settings-old.php:204 -msgid "Logged-In Only" -msgstr "تسجيل الدخول فقط" - -#: ../includes/admin-pages/settings-old.php:261 -msgid "Choose your currency." -msgstr "اختار عملتك" - -#: ../includes/admin-pages/settings-old.php:261 -msgid "Note" -msgstr "ملاحظة" - -#: ../includes/admin-pages/settings-old.php:261 -msgid "If you use Stripe, you MUST use USD." -msgstr "إن كنت تستخدم شريط الرسوم، فعليك استخدام USD" - -#: ../includes/admin-pages/settings-old.php:288 -msgid "Messages" -msgstr "رسائل" - -#: ../includes/admin-pages/settings-old.php:294 -#: ../includes/admin-pages/settings-old.php:299 -msgid "Payment Confirmation" -msgstr "توكيد الدفع" - -#: ../includes/admin-pages/settings-old.php:303 -msgid "Enter the message displayed after a user makes a successful purchase. HTML is accepted." -msgstr "أدخل العبارة التي ستظهر للمستخدم بعد عملية شراء ناجحة. HTML مقبول" - -#: ../includes/admin-pages/settings-old.php:313 -msgid "Save Options" -msgstr "حفظ الخيارات" - -#: ../includes/admin-pages/settings.php:33 -msgid "General" -msgstr "عام" - -#: ../includes/admin-pages/settings.php:35 -msgid "Emails" -msgstr "عناوين البريد الإلكتروني" - -#: ../includes/admin-pages/settings.php:36 -msgid "Styles" -msgstr "أنماط" - -#: ../includes/admin-pages/forms/add-discount.php:12 -msgid "Add New Discount" -msgstr "أضف خصم جديد" - -#: ../includes/admin-pages/forms/add-discount.php:22 -#: ../includes/admin-pages/forms/edit-discount.php:27 -msgid "The name of this discount" -msgstr "اسم هذا الخصم" - -#: ../includes/admin-pages/forms/add-discount.php:31 -#: ../includes/admin-pages/forms/edit-discount.php:36 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "أدخل رمز لهذا الخصم، مثل 10%" - -#: ../includes/admin-pages/forms/add-discount.php:36 -#: ../includes/admin-pages/forms/edit-discount.php:41 -msgid "Type" -msgstr "نوع" - -#: ../includes/admin-pages/forms/add-discount.php:40 -#: ../includes/admin-pages/forms/edit-discount.php:45 -msgid "Percentage" -msgstr "النسبة " - -#: ../includes/admin-pages/forms/add-discount.php:41 -#: ../includes/admin-pages/forms/edit-discount.php:46 -msgid "Flat amount" -msgstr "المبلغ المبسط" - -#: ../includes/admin-pages/forms/add-discount.php:43 -#: ../includes/admin-pages/forms/edit-discount.php:48 -msgid "The kind of discount to apply for this discount." -msgstr "نوع الخصم ليطبق على هذا الخصم" - -#: ../includes/admin-pages/forms/add-discount.php:52 -#: ../includes/admin-pages/forms/edit-discount.php:57 -msgid "The amount of this discount code." -msgstr "مبلغ رمز الخصم هذا" - -#: ../includes/admin-pages/forms/add-discount.php:57 -#: ../includes/admin-pages/forms/edit-discount.php:62 -msgid "Start date" -msgstr "تاريخ البدء" - -#: ../includes/admin-pages/forms/add-discount.php:61 -#: ../includes/admin-pages/forms/edit-discount.php:66 -msgid "Enter the start date for this discount code in the format of mm/dd/yyyy. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "ادخل تاريخ بدء كود الخصم هذا على شكل سنوات-شهور- أيام. دعه فارغ إن لم ترد تحديد تاريخ البدء. إن أدخلته فالخصم سيستخدم فقط بعد أو في هذا التاريخ" - -#: ../includes/admin-pages/forms/add-discount.php:66 -#: ../includes/admin-pages/forms/edit-discount.php:71 -msgid "Expiration date" -msgstr "تاريخ نهاية الصلاحية" - -#: ../includes/admin-pages/forms/add-discount.php:70 -#: ../includes/admin-pages/forms/edit-discount.php:75 -msgid "Enter the expiration date for this discount code in the format of mm/dd/yyyy. For no expiration, leave blank" -msgstr "ادخل تاريخ نهاية صلاحية كود الخصم هذا على شكل سنوات-شهور- أيام. دعه فارغ إن لم ترغب بتحديد تاريخ نهاية الصلاحية" - -#: ../includes/admin-pages/forms/add-discount.php:75 -#: ../includes/admin-pages/forms/edit-discount.php:89 -msgid "Minimum Amount" -msgstr "الحد الأدنى للمبلغ" - -#: ../includes/admin-pages/forms/add-discount.php:79 -#: ../includes/admin-pages/forms/edit-discount.php:93 -msgid "The minimum amount that must be purchased before this discount can be used. Leave blank for no minimum." -msgstr " الحد الأدنى الذي يجب شراؤه قبل هذا الخصم.. دعه فارغ في حال عدم وجود حد أدنى" - -#: ../includes/admin-pages/forms/add-discount.php:88 -#: ../includes/admin-pages/forms/edit-discount.php:84 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "الحد الأقصى لعدد مرات استخدام هذا الخصم. دعه فارغ في حالة إن كان غير محدود" - -#: ../includes/admin-pages/forms/add-discount.php:96 -msgid "Add Discount Code" -msgstr "أضف كود الخصم" - -#: ../includes/admin-pages/forms/edit-discount.php:13 -msgid "Something went wrong." -msgstr "حث خطأ ما" - -#: ../includes/admin-pages/forms/edit-discount.php:17 -msgid "Edit Discount" -msgstr "تحرير الخصم" - -#: ../includes/admin-pages/forms/edit-discount.php:17 -#: ../includes/admin-pages/forms/edit-payment.php:16 -msgid "Go Back" -msgstr "الرجوع" - -#: ../includes/admin-pages/forms/edit-discount.php:102 -msgid "Active" -msgstr "فعال" - -#: ../includes/admin-pages/forms/edit-discount.php:103 -msgid "Inactive" -msgstr "غير فعال" - -#: ../includes/admin-pages/forms/edit-discount.php:105 -msgid "The status of this discount code." -msgstr "حالة كود الخصم هذا" - -#: ../includes/admin-pages/forms/edit-discount.php:115 -msgid "Update Discount Code" -msgstr "تحديث كود الخصم" - -#: ../includes/admin-pages/forms/edit-payment.php:22 -msgid "Buyer's Email" -msgstr "البريد الإلكتروني للمشتري" - -#: ../includes/admin-pages/forms/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "إن احتجت ذلك، يمكنك تحديث البريد الإلكتروني الخاص بالمشتري" - -#: ../includes/admin-pages/forms/edit-payment.php:31 -msgid "Downloads Purchased" -msgstr "التحميلات التي اشتريت" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "أضف تحميل لتشتري #%" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -msgid "Add download to purchase" -msgstr "أضف تحميل للشراء" - -#: ../includes/admin-pages/forms/edit-payment.php:48 -msgid "Payment Status" -msgstr "حالة الدفع" - -#: ../includes/admin-pages/forms/edit-payment.php:64 -msgid "Send Purchase Receipt" -msgstr "أرسل فاتورة شراء" - -#: ../includes/admin-pages/forms/edit-payment.php:68 -msgid "Check this box to send the purchase receipt, including all download links." -msgstr "علم في المربع لإرسال فاتورة الشراء، بالإضافة لجميع الروابط المحملة" - -#: ../includes/admin-pages/forms/edit-payment.php:78 -msgid "Update Payment" -msgstr "تحديق الدفع" - -#: ../includes/admin-pages/forms/edit-payment.php:91 -msgid "Add Selected Downloads" -msgstr "إضافة التحميلات المختارة" - -#: ../includes/gateways/paypal.php:271 -msgid "Invalid IPN" -msgstr "IPN غير صالح" - -#: ../includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "اسم المادة" - -#: ../includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "سعر المادة" - -#: ../includes/templates/checkout_cart.php:49 -msgid "Total" -msgstr "الإجمالي" - -#: ../includes/templates/history-downloads.php:10 -msgid "Download Name" -msgstr "اسم التحميل" - -#: ../includes/templates/history-downloads.php:12 -#: ../includes/templates/history-purchases.php:14 -msgid "Files" -msgstr "ملفات" - -#: ../includes/templates/history-downloads.php:68 -msgid "You have not purchased any downloads" -msgstr "لم تشتري أي تحميلات" - -#: ../includes/templates/history-purchases.php:11 -msgid "Purchase ID" -msgstr "معرف الشراء" - -#: ../includes/templates/history-purchases.php:61 -msgid "You have not made any purchases" -msgstr "لم تقم بشراء أي شئ" - -#~ msgid "Payment Info" -#~ msgstr "معلومات الدفع" -#~ msgid "price name" -#~ msgstr "اسم السعر" -#~ msgid "Add New Price Option" -#~ msgstr "اضف اختيار سعر جديد" -#~ msgid "Enter the download price. Do not include a currency symbol" -#~ msgstr "ادخل سعر للتحميل. لا تدرج رمز العملة" -#~ msgid "Add to Cart" -#~ msgstr "إضافة لسلة التسوق" -#~ msgid "No downloads found" -#~ msgstr "لم يتم العثور على تحميلات" -#~ msgid "Deleted" -#~ msgstr "تم الحذف" - diff --git a/languages/edd-de_DE.mo b/languages/edd-de_DE.mo deleted file mode 100644 index 94b482eb881..00000000000 Binary files a/languages/edd-de_DE.mo and /dev/null differ diff --git a/languages/edd-de_DE.po b/languages/edd-de_DE.po deleted file mode 100644 index a23f5f9186b..00000000000 --- a/languages/edd-de_DE.po +++ /dev/null @@ -1,2923 +0,0 @@ -# This German Language File: Copyright (C) 2012 by David Decker of deckerweb.de & genesisthemes.de -# This file is distributed under the same license as the Easy Digital Downloads Plugin package. -# -# Weitere deutsche Sprachdateien fuer Easy Digital Downloads und Erweiterungen -# sowie WordPress-Plugins und -Themes sind hier zu finden: -# --> http://deckerweb.de/sprachdateien/ -# -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads Plugin (SIE-Version)\n" -"Report-Msgid-Bugs-To: https://github.com/pippinsplugins/Easy-Digital-Downloads/issues\n" -"POT-Creation-Date: 2012-04-14 16:16+0200\n" -"PO-Revision-Date: 2012-09-16 13:08+0100\n" -"Last-Translator: David Decker \n" -"Language-Team: DECKERWEB \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Poedit-Language: German\n" -"X-Poedit-Country: GERMANY\n" -"X-Poedit-SourceCharset: utf-8\n" -"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2\n" -"X-Textdomain-Support: yes\n" -"X-Poedit-SearchPath-0: .\n" - -#@ edd -#: includes/admin-notices.php:30 -msgid "Discount code updated." -msgstr "Gutschein-Code aktualisiert." - -#@ edd -#: includes/admin-notices.php:33 -msgid "There was a problem updating your discount code, please try again." -msgstr "Es gab ein Problem beim Aktualisieren Ihres Gutschein-Codes. Bitte versuchen Sie es erneut." - -#@ edd -#: includes/admin-pages/discount-codes.php:34 -#: includes/admin-pages.php:27 -msgid "Discount Codes" -msgstr "Gutschein-Codes" - -#@ edd -#: includes/admin-pages/discount-codes.php:39 -#: includes/admin-pages/discount-codes.php:52 -#: includes/admin-pages/forms/add-discount.php:18 -#: includes/admin-pages/forms/edit-discount.php:23 -#: includes/dashboard-columns.php:26 -msgid "Name" -msgstr "Name" - -#@ edd -#: includes/admin-pages/discount-codes.php:40 -#: includes/admin-pages/discount-codes.php:53 -#: includes/admin-pages/forms/add-discount.php:27 -#: includes/admin-pages/forms/edit-discount.php:32 -msgid "Code" -msgstr "Code" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:36 -#: includes/admin-pages/forms/edit-discount.php:41 -msgid "Type" -msgstr "Art" - -#@ edd -#: includes/admin-pages/discount-codes.php:41 -#: includes/admin-pages/discount-codes.php:54 -#: includes/admin-pages/forms/add-discount.php:48 -#: includes/admin-pages/forms/edit-discount.php:53 -#: includes/templates/history-purchases.php:13 -msgid "Amount" -msgstr "Betrag" - -#@ edd -#: includes/admin-pages/discount-codes.php:42 -#: includes/admin-pages/discount-codes.php:55 -msgid "Uses" -msgstr "Verwendung" - -#@ edd -#: includes/admin-pages/discount-codes.php:43 -#: includes/admin-pages/discount-codes.php:56 -#: includes/admin-pages/forms/add-discount.php:84 -#: includes/admin-pages/forms/edit-discount.php:80 -msgid "Max Uses" -msgstr "Max. Verwendung" - -#@ edd -#: includes/admin-pages/discount-codes.php:44 -#: includes/admin-pages/discount-codes.php:57 -msgid "Start Date" -msgstr "Startdatum" - -#@ edd -#: includes/admin-pages/discount-codes.php:45 -#: includes/admin-pages/discount-codes.php:58 -msgid "Expiration" -msgstr "Enddatum" - -#@ edd -#: includes/admin-pages/discount-codes.php:46 -#: includes/admin-pages/discount-codes.php:59 -#: includes/admin-pages/forms/edit-discount.php:98 -#: includes/admin-pages/payments-history.php:149 -#: includes/admin-pages/payments-history.php:161 -#: includes/export-functions.php:50 -msgid "Status" -msgstr "Status" - -#@ edd -#: includes/admin-pages/discount-codes.php:47 -#: includes/admin-pages/discount-codes.php:60 -#: includes/templates/checkout_cart.php:8 -msgid "Actions" -msgstr "Aktionen" - -#@ edd -#: includes/admin-pages/discount-codes.php:82 -#: includes/admin-pages/discount-codes.php:84 -msgid "unlimited" -msgstr "unbegrenzt" - -#@ edd -#: includes/admin-pages/discount-codes.php:93 -msgid "No start date" -msgstr "Kein Startdatum" - -#@ edd -#: includes/admin-pages/discount-codes.php:100 -msgid "Expired" -msgstr "Verfallen" - -#@ edd -#: includes/admin-pages/discount-codes.php:102 -msgid "no expiration" -msgstr "Verfällt nicht" - -#@ edd -#: includes/admin-pages/discount-codes.php:108 -#: includes/admin-pages/payments-history.php:183 -msgid "Edit" -msgstr "Bearbeiten" - -#@ edd -#: includes/admin-pages/discount-codes.php:110 -msgid "Deactivate" -msgstr "Deaktivieren" - -#@ edd -#: includes/admin-pages/discount-codes.php:112 -msgid "Activate" -msgstr "Aktivieren" - -#@ edd -#: includes/admin-pages/discount-codes.php:114 -#: includes/admin-pages/payments-history.php:185 -msgid "Delete" -msgstr "Löschen" - -#@ edd -#: includes/admin-pages/discount-codes.php:119 -msgid "No discount codes have been created." -msgstr "Bisher wurden keine Gutschein-Codes erstellt." - -#@ edd -#: includes/admin-pages/forms/add-discount.php:12 -msgid "Add New Discount" -msgstr "Neuen Gutschein hinzufügen" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:22 -#: includes/admin-pages/forms/edit-discount.php:27 -msgid "The name of this discount" -msgstr "Der Name dieses Gutscheins" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:31 -#: includes/admin-pages/forms/edit-discount.php:36 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "Geben Sie einen Code für diesen Gutschein ein, zum Beispiel 10PROZENT" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:40 -#: includes/admin-pages/forms/edit-discount.php:45 -msgid "Percentage" -msgstr "Prozentsatz" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:41 -#: includes/admin-pages/forms/edit-discount.php:46 -msgid "Flat amount" -msgstr "Pauschale" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:43 -#: includes/admin-pages/forms/edit-discount.php:48 -msgid "The kind of discount to apply for this discount." -msgstr "Die Art des Rabatts, die für diesen Gutschein verwendet wird." - -#@ edd -#: includes/admin-pages/forms/add-discount.php:52 -#: includes/admin-pages/forms/edit-discount.php:57 -msgid "The amount of this discount code." -msgstr "Der Rabattbetrag des Gutschein-Codes." - -#@ edd -#: includes/admin-pages/forms/add-discount.php:57 -#: includes/admin-pages/forms/edit-discount.php:62 -msgid "Start date" -msgstr "Startdatum" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:61 -#: includes/admin-pages/forms/edit-discount.php:66 -msgid "Enter the start date for this discount code in the format of yyyy-mm-dd. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "Geben Sie das Startdatum für diesen Gutschein-Code im Format JJJJ-MM-TT ein. Um kein Startdatum zu verwenden, einfach das Feld frei lassen. Falls jedoch ein Startdatum eingegeben wurde, kann der Gutschein nur an diesem Datum oder danach verwendet werden." - -#@ edd -#: includes/admin-pages/forms/add-discount.php:66 -#: includes/admin-pages/forms/edit-discount.php:71 -msgid "Expiration date" -msgstr "Verfallsdatum" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:70 -#: includes/admin-pages/forms/edit-discount.php:75 -msgid "Enter the expiration date for this discount code in the format of yyyy-mm-dd. For no expiration, leave blank" -msgstr "Geben Sie das Verfallsdatum (Enddatum) für diesen Gutschein-Code im Format JJJJ-MM-TT ein. Um kein Verfallsdatum zu verwenden, einfach das Feld frei lassen." - -#@ edd -#: includes/admin-pages/forms/add-discount.php:88 -#: includes/admin-pages/forms/edit-discount.php:84 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "Wie oft der Gutschein-Code maximal verwendet werden darf. Das Feld frei lassen für unbegrenzte Verwendung." - -#@ edd -#: includes/admin-pages/forms/add-discount.php:96 -msgid "Add Discount Code" -msgstr "Gutschein-Code hinzufügen" - -#@ edd -#: includes/admin-pages/forms/edit-discount.php:13 -msgid "Something went wrong." -msgstr "Etwas ging schief." - -#@ edd -#@ default -#: includes/admin-pages/forms/edit-discount.php:13 -#: includes/error-tracking.php:30 -#: includes/process-download.php:266 -#: includes/process-download.php:283 -msgid "Error" -msgstr "Fehler" - -#@ edd -#: includes/admin-pages/forms/edit-discount.php:17 -msgid "Edit Discount" -msgstr "Gutschein bearbeiten" - -#@ edd -#: includes/admin-pages/forms/edit-discount.php:17 -#: includes/admin-pages/forms/edit-payment.php:16 -msgid "Go Back" -msgstr "Zurückgehen" - -#@ edd -#: includes/admin-pages/forms/edit-discount.php:102 -msgid "Active" -msgstr "Aktiv" - -#@ edd -#: includes/admin-pages/forms/edit-discount.php:103 -msgid "Inactive" -msgstr "Inaktiv" - -#@ edd -#: includes/admin-pages/forms/edit-discount.php:105 -msgid "The status of this discount code." -msgstr "Der Status dieses Gutschein-Codes." - -#@ edd -#: includes/admin-pages/forms/edit-discount.php:115 -msgid "Update Discount Code" -msgstr "Gutschein-Code aktualisieren" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:16 -#: includes/post-types.php:83 -msgid "Edit Payment" -msgstr "Zahlung bearbeiten" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:31 -msgid "Downloads Purchased" -msgstr "Gekaufte Downloads" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "Download zum Kauf #%s hinzufügen" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:43 -msgid "Add download to purchase" -msgstr "Download zum Kauf hinzufügen" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:78 -msgid "Update Payment" -msgstr "Zahlung aktualisieren" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:91 -msgid "Add Selected Downloads" -msgstr "Ausgewählte Downloads hinzufügen" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:92 -#: includes/admin-pages/payments-history.php:274 -msgid "Close" -msgstr "Schliessen" - -#@ edd -#: includes/admin-pages/payments-history.php:85 -#: includes/admin-pages.php:26 -#: includes/post-types.php:91 -msgid "Payment History" -msgstr "Bisherige Zahlungen" - -#@ edd -#: includes/admin-pages/payments-history.php:110 -msgid "Payment mode" -msgstr "Zahlungsmodus" - -#@ edd -#: includes/admin-pages/payments-history.php:112 -msgid "Live" -msgstr "Live" - -#@ edd -#: includes/admin-pages/payments-history.php:113 -msgid "Test" -msgstr "Testmodus" - -#@ edd -#: includes/admin-pages/payments-history.php:123 -msgid "Payments per page" -msgstr "Zahlungen pro Seite" - -#@ edd -#: includes/admin-pages/payments-history.php:125 -msgid "Show" -msgstr "Zeigen" - -#@ edd -#: includes/admin-pages/payments-history.php:137 -#: includes/admin-pages/payments-history.php:155 -#: includes/export-functions.php:39 -msgid "ID" -msgstr "ID" - -#@ edd -#: includes/admin-pages/payments-history.php:139 -#: includes/admin-pages/payments-history.php:156 -#: includes/checkout-template.php:387 -#: includes/checkout-template.php:388 -#: includes/export-functions.php:40 -msgid "Email" -msgstr "E-Mail" - -#@ edd -#: includes/export-functions.php:47 -msgid "Key" -msgstr "Schlüssel" - -#@ edd -#: includes/admin-pages/payments-history.php:140 -#: includes/admin-pages/payments-history.php:157 -#: includes/export-functions.php:43 -msgid "Products" -msgstr "Produkte" - -#@ edd -#: includes/admin-pages/payments-history.php:142 -#: includes/admin-pages/payments-history.php:158 -#: includes/dashboard-columns.php:29 -#: includes/dashboard-columns.php:231 -#: includes/pdf-reports.php:66 -msgid "Price" -msgstr "Preis" - -#@ edd -#: includes/admin-pages/payments-history.php:145 -#: includes/admin-pages/payments-history.php:159 -#: includes/dashboard-columns.php:33 -#: includes/export-functions.php:48 -#: includes/templates/history-purchases.php:12 -msgid "Date" -msgstr "Datum" - -#@ edd -#: includes/admin-pages/payments-history.php:147 -#: includes/admin-pages/payments-history.php:160 -#: includes/export-functions.php:49 -msgid "User" -msgstr "Benutzer" - -#@ edd -#: includes/admin-pages/payments-history.php:197 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "Kauf-Details für Zahlung #%s" - -#@ edd -#: includes/admin-pages/payments-history.php:197 -msgid "View Order Details" -msgstr "Bestelldetails ansehen" - -#@ edd -#: includes/admin-pages/payments-history.php:205 -msgid "Purchased File" -msgstr "Gekaufte Datei" - -#@ edd -#: includes/admin-pages/payments-history.php:205 -msgid "Purchased Files" -msgstr "Gekaufte Dateien" - -#@ edd -#: includes/admin-pages/payments-history.php:250 -msgid "Discount used:" -msgstr "Verwendeter Gutschein:" - -#@ edd -#: includes/admin-pages/payments-history.php:250 -#: includes/export-functions.php:111 -#: includes/export-functions.php:119 -msgid "none" -msgstr "keiner" - -#@ edd -#: includes/admin-pages/payments-history.php:251 -msgid "Total:" -msgstr "Gesamt:" - -#@ edd -#: includes/admin-pages/payments-history.php:254 -msgid "Buyer's Personal Details:" -msgstr "Persönliche Details des Käufers:" - -#@ edd -#: includes/admin-pages/payments-history.php:256 -msgid "Name:" -msgstr "Name:" - -#@ edd -#: includes/admin-pages/payments-history.php:257 -msgid "Email:" -msgstr "E-Mail:" - -#@ edd -#: includes/admin-pages/payments-history.php:184 -msgid "Resend Purchase Receipt" -msgstr "Bestellbestätigung erneut senden" - -#@ edd -#: includes/admin-pages/payments-history.php:298 -#: includes/export-functions.php:135 -msgid "No payments recorded yet" -msgstr "Bisher keine Zahlungen eingegangen." - -#@ edd -#: includes/admin-pages/reports.php:28 -#: includes/admin-pages.php:28 -msgid "Reports" -msgstr "Statistiken" - -#@ edd -#: includes/admin-pages/settings-old.php:11 -#: includes/pdf-reports.php:42 -#: includes/pdf-reports.php:43 -msgid "Easy Digital Downloads" -msgstr "Einfache Digitale Downloads" - -#@ edd -#: includes/admin-pages/settings-old.php:12 -msgid "Pages" -msgstr "Seiten" - -#@ edd -#: includes/admin-pages/settings-old.php:13 -#: includes/admin-pages/settings-old.php:118 -#: includes/admin-pages/settings.php:34 -#: includes/register-settings.php:110 -msgid "Payment Gateways" -msgstr "Zahlungsweisen" - -#@ edd -#: includes/admin-pages/settings-old.php:14 -#: includes/admin-pages/settings.php:37 -msgid "Misc" -msgstr "Sonstiges" - -#@ edd -#: includes/admin-pages/settings-old.php:25 -msgid "Purchase Form" -msgstr "Kauf-Formular" - -#@ edd -#: includes/admin-pages/settings-old.php:31 -#: includes/admin-pages/settings-old.php:36 -msgid "Purchase Page" -msgstr "Kauf-Seite" - -#@ edd -#: includes/admin-pages/settings-old.php:49 -#: includes/admin-pages/settings-old.php:78 -msgid "No pages found" -msgstr "Keine Seiten gefunden" - -#@ edd -#: includes/admin-pages/settings-old.php:53 -msgid "Choose the page that holds the checkout form short code." -msgstr "Wählen Sie die Seite, welche den Formular-Code für den Bezahlvorgang enthält." - -#@ edd -#: includes/admin-pages/settings-old.php:60 -#: includes/admin-pages/settings-old.php:65 -#: includes/register-settings.php:54 -msgid "Success Page" -msgstr "Erfolg-Seite" - -#@ edd -#: includes/admin-pages/settings-old.php:82 -msgid "Choose the page users are directed to after a successful purchase." -msgstr "Wählen Sie die Seite, zu der die Benutzer/ Kunden nach einem erfolgreichen Kauf geleitet werden." - -#@ edd -#: includes/admin-pages/settings-old.php:95 -#: includes/admin-pages/settings-old.php:101 -#: includes/admin-pages/settings-old.php:106 -#: includes/register-settings.php:41 -msgid "Test Mode" -msgstr "Testmodus" - -#@ edd -#: includes/admin-pages/settings-old.php:110 -msgid "Check this to use the plugin in test mode." -msgstr "Wählen Sie diese Einstellung, um das Plugin im Testmodus zu verwenden." - -#@ edd -#: includes/admin-pages/settings-old.php:124 -#: includes/admin-pages/settings-old.php:129 -msgid "Gateways" -msgstr "Zahlungsweisen" - -#@ edd -#: includes/admin-pages/settings-old.php:140 -msgid "Check each of the payment gateways you would like to enable. Configure the selected gateways below." -msgstr "Wählen Sie alle gewünschten Zahlungsweisen, die Sie aktivieren wollen. Richten Sie die entsprechenden Zahlungsarten (Gateways) unten ein." - -#@ edd -#: includes/admin-pages/settings-old.php:148 -#: includes/register-settings.php:131 -msgid "PayPal Settings" -msgstr "PayPal Einstellungen" - -#@ edd -#: includes/admin-pages/settings-old.php:154 -#: includes/admin-pages/settings-old.php:159 -#: includes/register-settings.php:137 -msgid "PayPal Email" -msgstr "PayPal E-Mail" - -#@ edd -#: includes/admin-pages/settings-old.php:163 -msgid "Enter your PayPal Email." -msgstr "Geben Sie Ihre PayPal E-Mail-Adresse ein." - -#@ edd -#: includes/admin-pages/settings-old.php:177 -msgid "Miscellaneous" -msgstr "Sonstiges" - -#@ edd -#: includes/admin-pages/settings-old.php:183 -#: includes/admin-pages/settings-old.php:188 -msgid "Enable Ajax" -msgstr "AJAX aktivieren" - -#@ edd -#: includes/admin-pages/settings-old.php:192 -msgid "Check this to enable AJAX for the shopping cart." -msgstr "Wählen Sie diese Einstellung, um AJAX für den Warenkorb zu aktivieren." - -#@ edd -#: includes/admin-pages/settings-old.php:199 -#: includes/admin-pages/settings-old.php:204 -msgid "Logged-In Only" -msgstr "Nur angemeldete Benutzer" - -#@ edd -#: includes/admin-pages/settings-old.php:208 -#: includes/register-settings.php:238 -msgid "Require that users be logged-in to purchase files." -msgstr "Erzwingen Sie, dass Benutzer/ Kunden angemeldet (d.h. registriert) sind, um Dateien kaufen zu können." - -#@ edd -#: includes/admin-pages/settings-old.php:215 -#: includes/register-settings.php:67 -msgid "Currency Settings" -msgstr "Währungseinstellungen" - -#@ edd -#: includes/admin-pages/settings-old.php:221 -#: includes/admin-pages/settings-old.php:226 -#: includes/register-settings.php:73 -msgid "Currency" -msgstr "Währung" - -#@ edd -#: includes/admin-pages/settings-old.php:232 -#: includes/misc-functions.php:185 -msgid "US Dollars ($)" -msgstr "US Dollar ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:233 -#: includes/misc-functions.php:186 -msgid "Euros (€)" -msgstr "Euro (€)" - -#@ edd -#: includes/admin-pages/settings-old.php:234 -#: includes/misc-functions.php:187 -msgid "Pounds Sterling (£)" -msgstr "Pfund Sterling (£)" - -#@ edd -#: includes/admin-pages/settings-old.php:235 -#: includes/misc-functions.php:188 -msgid "Australian Dollars ($)" -msgstr "Australische Dollar ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:236 -#: includes/misc-functions.php:189 -msgid "Brazilian Real ($)" -msgstr "Brasilianischer Real ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:237 -#: includes/misc-functions.php:190 -msgid "Canadian Dollars ($)" -msgstr "Kanadischer Dollar ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:238 -#: includes/misc-functions.php:191 -msgid "Czech Koruna" -msgstr "Tschechische Krone" - -#@ edd -#: includes/admin-pages/settings-old.php:239 -#: includes/misc-functions.php:192 -msgid "Danish Krone" -msgstr "Dänische Krone" - -#@ edd -#: includes/admin-pages/settings-old.php:240 -#: includes/misc-functions.php:193 -msgid "Hong Kong Dollar ($)" -msgstr "Hongkong Dollar ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:241 -#: includes/misc-functions.php:194 -msgid "Hungarian Forint" -msgstr "Ungarischer Forint" - -#@ edd -#: includes/admin-pages/settings-old.php:242 -#: includes/misc-functions.php:195 -msgid "Israeli Shekel" -msgstr "Israelischer Schekel" - -#@ edd -#: includes/admin-pages/settings-old.php:243 -#: includes/misc-functions.php:196 -msgid "Japanese Yen (¥)" -msgstr "Japanischer Yen (¥)" - -#@ edd -#: includes/admin-pages/settings-old.php:244 -#: includes/misc-functions.php:197 -msgid "Malaysian Ringgits" -msgstr "Malaysischer Ringgits" - -#@ edd -#: includes/admin-pages/settings-old.php:245 -#: includes/misc-functions.php:198 -msgid "Mexican Peso ($)" -msgstr "Mexikanischer Peso ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:246 -#: includes/misc-functions.php:199 -msgid "New Zealand Dollar ($)" -msgstr "Neuseeland Dollar ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:247 -#: includes/misc-functions.php:200 -msgid "Norwegian Krone" -msgstr "Norwegische Krone" - -#@ edd -#: includes/admin-pages/settings-old.php:248 -#: includes/misc-functions.php:201 -msgid "Philippine Pesos" -msgstr "Philipinischer Pesos" - -#@ edd -#: includes/admin-pages/settings-old.php:249 -#: includes/misc-functions.php:202 -msgid "Polish Zloty" -msgstr "Polnischer Zloty" - -#@ edd -#: includes/admin-pages/settings-old.php:250 -#: includes/misc-functions.php:203 -msgid "Singapore Dollar ($)" -msgstr "Singapur Dollar ($)" - -#@ edd -#: includes/admin-pages/settings-old.php:251 -#: includes/misc-functions.php:204 -msgid "Swedish Krona" -msgstr "Schwedische Krone" - -#@ edd -#: includes/admin-pages/settings-old.php:252 -#: includes/misc-functions.php:205 -msgid "Swiss Franc" -msgstr "Schweizer Franken" - -#@ edd -#: includes/admin-pages/settings-old.php:253 -#: includes/misc-functions.php:206 -msgid "Taiwan New Dollars" -msgstr "Taiwan New Dollar" - -#@ edd -#: includes/admin-pages/settings-old.php:254 -#: includes/misc-functions.php:207 -msgid "Thai Baht" -msgstr "Thai Baht" - -#@ edd -#: includes/admin-pages/settings-old.php:261 -msgid "Choose your currency." -msgstr "Wählen Sie Ihre Währung." - -#@ edd -#: includes/admin-pages/settings-old.php:261 -msgid "Note" -msgstr "Hinweis" - -#@ edd -#: includes/admin-pages/settings-old.php:261 -msgid "If you use Stripe, you MUST use USD." -msgstr "Wenn Sie Stripe verwenden, dann MÜSSEN Sie US Dollar wählen." - -#@ edd -#: includes/admin-pages/settings-old.php:268 -#: includes/admin-pages/settings-old.php:273 -#: includes/register-settings.php:80 -msgid "Currency Position" -msgstr "Währungsposition" - -#@ edd -#: includes/admin-pages/settings-old.php:277 -#: includes/register-settings.php:84 -msgid "Before - $10" -msgstr "Davor - € 10" - -#@ edd -#: includes/admin-pages/settings-old.php:278 -#: includes/register-settings.php:85 -msgid "After - 10$" -msgstr "Danach - 10 €" - -#@ edd -#: includes/admin-pages/settings-old.php:280 -#: includes/register-settings.php:81 -msgid "Choose the location of the currency sign." -msgstr "Wählen Sie die Anordnung des Währungssymbols." - -#@ edd -#: includes/admin-pages/settings-old.php:288 -msgid "Messages" -msgstr "Benachrichtigungen" - -#@ edd -#: includes/admin-pages/settings-old.php:294 -#: includes/admin-pages/settings-old.php:299 -msgid "Payment Confirmation" -msgstr "Zahlungsbestätigung" - -#@ edd -#: includes/admin-pages/settings-old.php:303 -msgid "Enter the message displayed after a user makes a successful purchase. HTML is accepted." -msgstr "Geben Sie die Mitteilung ein, die angezeigt wird, nachdem ein Benutzer/ Kunde einen erfolgreichen Kauf getätigt hat. HTML wird akzeptiert." - -#@ edd -#: includes/admin-pages/settings-old.php:313 -msgid "Save Options" -msgstr "Einstellungen speichern" - -#@ edd -#: includes/admin-pages/settings.php:33 -msgid "General" -msgstr "Allgemein" - -#@ edd -#: includes/admin-pages/settings.php:35 -msgid "Emails" -msgstr "E-Mails" - -#@ edd -#: includes/admin-pages.php:28 -msgid "Earnings and Sales Reports" -msgstr "Einnahme- und Verkaufsstatistiken" - -#@ edd -#: includes/admin-pages.php:29 -msgid "Easy Digital Download Settings" -msgstr "Einfache Digitale Downloads - Einstellungen" - -#@ edd -#: includes/admin-pages.php:29 -msgid "Settings" -msgstr "Einstellungen" - -#@ edd -#: includes/ajax-functions.php:118 -#: includes/process-purchase.php:239 -msgid "The discount you entered is invalid" -msgstr "Der Gutschein, den Sie eingegeben haben, ist ungültig." - -#@ edd -#: includes/scripts.php:45 -msgid "Your cart is empty" -msgstr "Ihr Warenkorb ist noch leer." - -#@ edd -#: includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "Name des Elements" - -#@ edd -#: includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "Preis des Elements" - -#@ edd -#: includes/cart-template.php:80 -#: includes/templates/checkout_cart.php:36 -msgid "remove" -msgstr "entfernen" - -#@ edd -#: includes/templates/checkout_cart.php:49 -msgid "Total" -msgstr "Gesamt" - -#@ edd -#: includes/cart-template.php:46 -#: includes/cart-template.php:49 -#: includes/install.php:31 -#: includes/template-functions.php:137 -#: includes/template-functions.php:158 -msgid "Checkout" -msgstr "Kasse" - -#@ edd -#: includes/checkout-template.php:75 -msgid "Choose Your Payment Method" -msgstr "Zahlungsweise wählen" - -#@ edd -#: includes/admin-pages/payments-history.php:319 -#: includes/checkout-template.php:457 -#: includes/metabox.php:525 -#: includes/metabox.php:622 -msgid "Next" -msgstr "Weiter" - -#@ edd -#: includes/checkout-template.php:137 -msgid "Email address" -msgstr "E-Mail-Adresse" - -#@ edd -#: includes/checkout-template.php:138 -msgid "Email Address" -msgstr "E-Mail-Adresse" - -#@ edd -#: includes/checkout-template.php:142 -#: includes/checkout-template.php:143 -#: includes/checkout-template.php:391 -#: includes/checkout-template.php:392 -#: includes/export-functions.php:41 -msgid "First Name" -msgstr "Vorname" - -#@ edd -#: includes/checkout-template.php:146 -#: includes/checkout-template.php:395 -msgid "Last name" -msgstr "Nachname" - -#@ edd -#: includes/checkout-template.php:147 -#: includes/checkout-template.php:396 -#: includes/export-functions.php:42 -msgid "Last Name" -msgstr "Nachname" - -#@ edd -#: includes/checkout-template.php:159 -msgid "Enter discount" -msgstr "Gutschein eingeben" - -#@ edd -#: includes/checkout-template.php:161 -msgid "Discount" -msgstr "Rabatt" - -#@ edd -#: includes/checkout-template.php:163 -msgid "Apply Discount" -msgstr "Gutschein zuweisen" - -#@ edd -#: includes/checkout-template.php:481 -#: includes/dashboard-columns.php:58 -#: includes/metabox.php:243 -#: includes/metabox.php:333 -#: includes/shortcodes.php:26 -#: includes/template-functions.php:60 -#: includes/thickbox.php:61 -msgid "Purchase" -msgstr "Kaufen" - -#@ edd -#: includes/checkout-template.php:214 -msgid "Go back" -msgstr "Zurück gehen" - -#@ edd -#: includes/checkout-template.php:218 -msgid "You must be logged in to complete your purchase" -msgstr "Sie müssen angemeldet sein, um Ihren Kauf abschliessen zu können." - -#@ edd -#: includes/checkout-template.php:249 -msgid "Card name" -msgstr "Kartenname" - -#@ edd -#: includes/checkout-template.php:250 -msgid "Name on the Card" -msgstr "Name auf der Karte" - -#@ edd -#: includes/checkout-template.php:253 -msgid "Card number" -msgstr "Kartennummer" - -#@ edd -#: includes/checkout-template.php:254 -msgid "Card Number" -msgstr "Kartennummer" - -#@ edd -#: includes/checkout-template.php:257 -msgid "Security code" -msgstr "Sicherheits-Code" - -#@ edd -#: includes/checkout-template.php:258 -msgid "CVC" -msgstr "Prüfziffer" - -#@ edd -#: includes/checkout-template.php:264 -#: includes/graphing.php:121 -#: includes/graphing.php:221 -msgid "Month" -msgstr "Monat" - -#@ edd -#: includes/checkout-template.php:266 -msgid "Year" -msgstr "Jahr" - -#@ edd -#: includes/checkout-template.php:267 -msgid "Expiration (MM/YYYY)" -msgstr "Gültig bis (MM/JJJJ)" - -#@ edd -#: includes/checkout-template.php:247 -msgid "Credit Card Info" -msgstr "Kreditkarten-Infos" - -#@ edd -#: includes/checkout-template.php:297 -msgid "Address line 1" -msgstr "Anschrift" - -#@ edd -#: includes/checkout-template.php:298 -msgid "Billing Address" -msgstr "Rechnungsanschrift" - -#@ edd -#: includes/checkout-template.php:301 -msgid "Address line 2" -msgstr "Anschrift - Zusatz" - -#@ edd -#: includes/checkout-template.php:302 -msgid "Billing Address Line 2" -msgstr "Rechnung: Anschrift - Zusatz" - -#@ edd -#: includes/checkout-template.php:305 -msgid "City" -msgstr "Ort" - -#@ edd -#: includes/checkout-template.php:306 -msgid "Billing City" -msgstr "Rechnung: Ort" - -#@ edd -#: includes/checkout-template.php:320 -msgid "State / Province" -msgstr "Bundesland/ Kanton" - -#@ edd -#: includes/checkout-template.php:337 -msgid "Billing State / Province" -msgstr "Rechnung: Bundesland/ Kanton" - -#@ edd -#: includes/checkout-template.php:340 -msgid "Zip / Postal code" -msgstr "Postleitzahl" - -#@ edd -#: includes/checkout-template.php:341 -msgid "Billing Zip / Postal Code" -msgstr "Rechnung: Postleitzahl" - -#@ edd -#: includes/checkout-template.php:370 -msgid "Create an account" -msgstr "Ein Benutzerkonto erstellen" - -#@ edd -#: includes/checkout-template.php:373 -#: includes/checkout-template.php:374 -#: includes/checkout-template.php:421 -#: includes/login-register.php:47 -#: includes/login-register.php:48 -msgid "Username" -msgstr "Benutzername" - -#@ edd -#: includes/checkout-template.php:377 -#: includes/checkout-template.php:378 -#: includes/checkout-template.php:425 -#: includes/login-register.php:51 -msgid "Password" -msgstr "Passwort" - -#@ edd -#: includes/checkout-template.php:381 -msgid "Confirm password" -msgstr "Passwort bestätigen" - -#@ edd -#: includes/checkout-template.php:382 -msgid "Password Again" -msgstr "Passwort nochmals" - -#@ edd -#: includes/checkout-template.php:368 -msgid "Already have an account?" -msgstr "Haben Sie schon ein Benutzerkonto?" - -#@ edd -#: includes/checkout-template.php:368 -#: includes/login-register.php:58 -msgid "Login" -msgstr "Anmelden" - -#@ edd -#: includes/checkout-template.php:417 -msgid "Login to your account" -msgstr "Bei Ihrem Benutzerkonto anmelden" - -#@ edd -#: includes/checkout-template.php:420 -msgid "Your username" -msgstr "Ihr Benutzername" - -#@ edd -#: includes/checkout-template.php:424 -msgid "Your password" -msgstr "Ihr Passwort" - -#@ edd -#: includes/checkout-template.php:432 -msgid "Need to create an account?" -msgstr "Benötigen Sie ein Benutzerkonto?" - -#@ edd -#: includes/checkout-template.php:434 -msgid "Register" -msgstr "Registrieren" - -#@ edd -#: includes/dashboard-columns.php:27 -#: includes/pdf-reports.php:67 -#: includes/post-types.php:183 -#: includes/widgets.php:169 -msgid "Categories" -msgstr "Kategorien" - -#@ edd -#: includes/dashboard-columns.php:28 -#: includes/pdf-reports.php:68 -#: includes/post-types.php:205 -#: includes/widgets.php:170 -msgid "Tags" -msgstr "Schlagwörter" - -#@ edd -#: includes/dashboard-columns.php:30 -#: includes/graphing.php:32 -#: includes/graphing.php:222 -#: includes/pdf-reports.php:208 -msgid "Sales" -msgstr "Verkäufe" - -#@ edd -#: includes/dashboard-columns.php:31 -#: includes/graphing.php:78 -#: includes/graphing.php:122 -#: includes/graphing.php:169 -#: includes/pdf-reports.php:191 -msgid "Earnings" -msgstr "Einnahmen" - -#@ edd -#: includes/dashboard-columns.php:32 -#: includes/metabox.php:346 -msgid "Short Code" -msgstr "Shortcode" - -#@ edd -#: includes/dashboard-columns.php:189 -msgid "Show all categories" -msgstr "Alle Kategorien" - -#@ edd -#: includes/dashboard-columns.php:200 -msgid "Show all tags" -msgstr "Alle Schlagwörter" - -#@ edd -#: includes/email-functions.php:59 -msgid "Hello" -msgstr "Hallo" - -#@ edd -#: includes/email-functions.php:59 -msgid "A download purchase has been made" -msgstr "Ein Download-Kauf wurde getätigt" - -#@ edd -#: includes/email-functions.php:60 -msgid "Downloads sold:" -msgstr "Verkaufte Downloads:" - -#@ edd -#: includes/email-functions.php:74 -msgid "Amount: " -msgstr "Betrag: " - -#@ edd -#: includes/email-functions.php:76 -msgid "Thank you" -msgstr "Danke!" - -#@ edd -#: includes/email-functions.php:78 -msgid "New download purchase" -msgstr "Neuer Download-Kauf" - -#@ edd -#: includes/gateways/paypal.php:271 -msgid "Invalid IPN" -msgstr "Ungültige Sofortige Zahlungsbestätigung (IPN)" - -#@ edd -#: includes/graphing.php:77 -#: includes/post-types.php:125 -msgid "Download" -msgstr "Download" - -#@ edd -#: includes/graphing.php:136 -msgid "Earnings per month" -msgstr "Einnahmen pro Monat" - -#@ edd -#: includes/metabox.php:181 -msgid "Download Files" -msgstr "Dateien zum Herunterladen" - -#@ edd -#: includes/metabox.php:220 -msgid "Upload the downloadable files." -msgstr "Laden Sie die herunterladbaren Dateien hoch." - -#@ edd -#: includes/metabox.php:241 -msgid "Purchase Text" -msgstr "Kauf-Text" - -#@ edd -#: includes/metabox.php:243 -msgid "Add the text you would like displayed for the purchase text" -msgstr "Geben Sie den Text ein, den Sie für den Verkaufstext anzeigen wollen." - -#@ edd -#: includes/metabox.php:262 -msgid "Link Style" -msgstr "Linkstil" - -#@ edd -#: includes/metabox.php:266 -msgid "Choose the style of the purchase link" -msgstr "Wählen Sie den Stil für den Kauf-Link." - -#@ edd -#: includes/metabox.php:287 -msgid "Button Color" -msgstr "Buttonfarbe" - -#@ edd -#: includes/metabox.php:313 -msgid "Disable the purchase button?" -msgstr "Den Kauf-Button deaktivieren?" - -#@ edd -#: includes/metabox.php:196 -#: includes/metabox.php:215 -msgid "file name" -msgstr "Dateiname" - -#@ edd -#: includes/metabox.php:197 -#: includes/metabox.php:216 -msgid "file url" -msgstr "Datei-URL" - -#@ edd -#: includes/metabox.php:149 -#: includes/metabox.php:220 -#: includes/post-types.php:43 -#: includes/post-types.php:81 -msgid "Add New" -msgstr "Hinzufügen" - -#@ edd -#: includes/metabox.php:338 -msgid "Notes" -msgstr "Hinweise" - -#@ edd -#: includes/metabox.php:341 -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "Die Stil-Optionen oben spiegeln NICHT den Stil des Shortcodes wider! Der Shortcode erlaubt Ihnen, einen Kauf-Button für diesen Download an einer beliebigen Stelle Ihrer Webseite zu platzieren." - -#@ edd -#: includes/metabox.php:434 -msgid "Sales:" -msgstr "Verkäufe:" - -#@ edd -#: includes/metabox.php:440 -msgid "Earnings:" -msgstr "Einnahmen:" - -#@ edd -#: includes/scripts.php:123 -msgid "Add New Download" -msgstr "Neuen Download hinzufügen" - -#@ edd -#: includes/post-types.php:126 -msgid "Downloads" -msgstr "Downloads" - -#@ edd -#: includes/post-types.php:79 -msgctxt "post type general name" -msgid "Payments" -msgstr "Zahlungen" - -#@ edd -#: includes/post-types.php:80 -msgctxt "post type singular name" -msgid "Payment" -msgstr "Zahlung" - -#@ edd -#: includes/post-types.php:82 -msgid "Add New Payment" -msgstr "Neue Zahlung hinzufügen" - -#@ edd -#: includes/post-types.php:84 -msgid "New Payment" -msgstr "Neue Zahlung" - -#@ edd -#: includes/post-types.php:85 -msgid "All Payments" -msgstr "Alle Zahlungen" - -#@ edd -#: includes/post-types.php:86 -msgid "View Payment" -msgstr "Zahlung ansehen" - -#@ edd -#: includes/post-types.php:87 -msgid "Search Payments" -msgstr "Zahlungen suchen" - -#@ edd -#: includes/post-types.php:88 -msgid "No Payments found" -msgstr "Keine Zahlungen gefunden" - -#@ edd -#: includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "Es befinden sich keine Zahlungen im Papierkorb." - -#@ edd -#: includes/post-types.php:173 -msgctxt "taxonomy general name" -msgid "Categories" -msgstr "Kategorien" - -#@ edd -#: includes/post-types.php:174 -msgctxt "taxonomy singular name" -msgid "Category" -msgstr "Kategorie" - -#@ edd -#: includes/post-types.php:175 -msgid "Search Categories" -msgstr "Kategorien suchen" - -#@ edd -#: includes/post-types.php:176 -msgid "All Categories" -msgstr "Alle Kategorien" - -#@ edd -#: includes/post-types.php:177 -msgid "Parent Category" -msgstr "Übergeordnete Kategorie" - -#@ edd -#: includes/post-types.php:178 -msgid "Parent Category:" -msgstr "Übergeordnete Kategorie:" - -#@ edd -#: includes/post-types.php:179 -msgid "Edit Category" -msgstr "Kategorie bearbeiten" - -#@ edd -#: includes/post-types.php:180 -msgid "Update Category" -msgstr "Kategorie aktualisieren" - -#@ edd -#: includes/post-types.php:181 -msgid "Add New Category" -msgstr "Neue Kategorie" - -#@ edd -#: includes/post-types.php:182 -msgid "New Category Name" -msgstr "Neuer Kategoriename" - -#@ edd -#: includes/post-types.php:195 -msgctxt "taxonomy general name" -msgid "Tags" -msgstr "Schlagwörter" - -#@ edd -#: includes/post-types.php:196 -msgctxt "taxonomy singular name" -msgid "Tag" -msgstr "Schlagwort" - -#@ edd -#: includes/post-types.php:197 -msgid "Search Tags" -msgstr "Schlagwörter suchen" - -#@ edd -#: includes/post-types.php:198 -msgid "All Tags" -msgstr "Alle Schlagwörter" - -#@ edd -#: includes/post-types.php:199 -msgid "Parent Tag" -msgstr "Übergeordnetes Schlagwort" - -#@ edd -#: includes/post-types.php:200 -msgid "Parent Tag:" -msgstr "Übergeordnetes Schlagwort:" - -#@ edd -#: includes/post-types.php:201 -msgid "Edit Tag" -msgstr "Schlagwort bearbeiten" - -#@ edd -#: includes/post-types.php:202 -msgid "Update Tag" -msgstr "Schlagwort aktualisieren" - -#@ edd -#: includes/post-types.php:203 -msgid "Add New Tag" -msgstr "Neues Schlagwort" - -#@ edd -#: includes/post-types.php:204 -msgid "New Tag Name" -msgstr "Neuer Schlagwortname" - -#@ edd -#: includes/post-types.php:233 -#: includes/post-types.php:234 -msgid "Download updated." -msgstr "Download aktualisiert." - -#@ edd -#: includes/post-types.php:235 -msgid "Download published." -msgstr "Download veröffentlicht." - -#@ edd -#: includes/post-types.php:236 -msgid "Download saved." -msgstr "Download gespeichert." - -#@ edd -#: includes/post-types.php:237 -msgid "Download submitted." -msgstr "Download hinzugefügt." - -#@ edd -#: includes/process-download.php:294 -msgid "You do not have permission to download this file" -msgstr "Sie haben keine Berechtigung, diese Datei herunterzuladen." - -#@ edd -#: includes/process-download.php:294 -msgid "Purchase Verification Failed" -msgstr "Die Kauf-Bestätigung schlug fehl." - -#@ edd -#: includes/process-purchase.php:349 -msgid "Username already taken" -msgstr "Dieser Benutzername existiert bereits." - -#@ edd -#: includes/process-purchase.php:355 -msgid "Invalid username" -msgstr "Ungültiger Benutzername" - -#@ edd -#: includes/process-purchase.php:378 -#: includes/process-purchase.php:522 -msgid "Invalid email" -msgstr "Ungültige E-Mail-Adresse" - -#@ edd -#: includes/process-purchase.php:384 -msgid "Email already used" -msgstr "Diese E-Mail-Adresse wird bereits verwendet" - -#@ edd -#: includes/process-purchase.php:422 -#: includes/process-purchase.php:487 -msgid "Enter a password" -msgstr "Geben Sie ein Passwort ein" - -#@ edd -#: includes/process-purchase.php:407 -msgid "Passwords don't match" -msgstr "Die beiden Passwörter stimmen nicht überein" - -#@ edd -#: includes/login-register.php:92 -#: includes/process-purchase.php:472 -msgid "The password you entered is incorrect" -msgstr "Das von Ihnen eingegeben Passwort ist falsch." - -#@ edd -#: includes/login-register.php:95 -#: includes/process-purchase.php:491 -msgid "The username you entered does not exist" -msgstr "Der von Ihnen eingegebene Benutzername existiert nicht." - -#@ edd -#: includes/register-settings.php:42 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "Während Sie sich im Testmodus des Plugins befinden, werden keine Live-Transaktionen ausgeführt. Um den Testmodus vollständig verwenden zu können, benötigen Sie ein Sandkasten-Benutzerkonto (Sandbox Test Account) bei Ihrem Zahlungsart-Anbieter, den Sie testen möchten." - -#@ edd -#: includes/register-settings.php:48 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "Dies ist die 'Kasse'-Seite, wo Käufer ihren Kauf abschliessen werden." - -#@ edd -#: includes/register-settings.php:55 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "Dies ist die Seite, wohin Käufer nach abgeschlossenem Kauf hingeleitet werden." - -#@ edd -#: includes/register-settings.php:68 -msgid "Configure the currency options" -msgstr "Richten Sie Ihre Währungsoptionen ein." - -#@ edd -#: includes/register-settings.php:74 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "Wählen Sie Ihre Währung. Hinweis: Einige Zahlungsarten-Anbieter haben Beschränkungen für bestimmte Währungen!" - -#@ edd -#: includes/register-settings.php:90 -msgid "Thousands Separator" -msgstr "Tausender-Trennzeichen" - -#@ edd -#: includes/register-settings.php:91 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "Das Symbol, um die Tausender abzutrennen; gewöhnlich . (Punkt) oder , (Komma)." - -#@ edd -#: includes/register-settings.php:98 -msgid "Decimal Separator" -msgstr "Zehner-Trennzeichen" - -#@ edd -#: includes/register-settings.php:99 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "Das Symbol, um die Zehner-Werte abzutrennen; gewöhnlich , (Komma) oder . (Punkt)." - -#@ edd -#: includes/register-settings.php:111 -msgid "Choose the payment gateways you want to enable." -msgstr "Wählen Sie die Zahlungsweise(n), die Sie aktivieren wollen." - -#@ edd -#: includes/register-settings.php:118 -msgid "Display icons for the selected payment methods" -msgstr "Icon-Bilder für die ausgewählten Bezahlmethoden anzeigen." - -#@ edd -#: includes/register-settings.php:132 -msgid "Configure the PayPal settings" -msgstr "PayPal Einstellungen festlegen" - -#@ edd -#: includes/register-settings.php:138 -msgid "Enter your PayPal account's email" -msgstr "Geben Sie die E-Mail-Adresse Ihres PayPal-Benutzerkontos ein." - -#@ edd -#: includes/register-settings.php:144 -msgid "Alternate PayPal Purchase Verification" -msgstr "Alternative PayPal Kauf-Bestätigung" - -#@ edd -#: includes/register-settings.php:179 -msgid "From Email" -msgstr "Absender-E-Mail" - -#@ edd -#: includes/register-settings.php:180 -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "Absender-E-Mail-Adresse, von welcher aus Bestellbestätigungen versandt werden. Diese fungiert als Adresse für \"Von\" (from) sowie \"Antwort an\" (reply-to)." - -#@ edd -#: includes/register-settings.php:185 -msgid "Purchase Email Subject" -msgstr "Betreff der Bestellbestätigung" - -#@ edd -#: includes/register-settings.php:186 -msgid "Enter the subject line for the purchase receipt email" -msgstr "Geben Sie die Betreffzeile der E-Mail für die Bestellbestätigung an." - -#@ edd -#: includes/email-functions.php:47 -#: includes/register-settings.php:191 -msgid "Purchase Receipt" -msgstr "Bestellbestätigung" - -#@ edd -#: includes/register-settings.php:192 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "Geben Sie hier den E-Mail-Text ein, den Benutzer bzw. Kunden nach einem erfolgreichen, abgeschlossenen Kauf erhalten. HTML-Formatierungen sind erlaubt. Verfügbare Template-Tags sind:" - -#@ edd -#: includes/register-settings.php:193 -msgid "A list of download URLs for each download purchased" -msgstr "Eine Liste von Download-URLs für jeden gekauften Download" - -#@ edd -#: includes/register-settings.php:194 -msgid "The buyer's name" -msgstr "Name des Käufers" - -#@ edd -#: includes/register-settings.php:195 -msgid "The date of the purchase" -msgstr "Datum des Kaufs" - -#@ edd -#: includes/register-settings.php:199 -msgid "Your site name" -msgstr "Name Ihrer Webseite" - -#@ edd -#: includes/register-settings.php:231 -msgid "Enable jQuery Validation" -msgstr "jQuery-Validierung aktivieren" - -#@ edd -#: includes/register-settings.php:232 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "Wählen Sie diese Einstellung, um die jQuery-Validierung (via JavaScript) für das Bezahlformular zu aktivieren." - -#@ edd -#: includes/register-settings.php:237 -msgid "Disable Guest Checkout" -msgstr "Gastbestellungen deaktivieren" - -#@ edd -#: includes/register-settings.php:243 -msgid "Show Register / Login Form?" -msgstr "Registrierungs-/ Anmeldeformular anzeigen?" - -#@ edd -#: includes/register-settings.php:256 -msgid "Disable Redownload?" -msgstr "Erneutes Herunterladen deaktivieren?" - -#@ edd -#: includes/register-settings.php:314 -msgid "General Settings" -msgstr "Allgemeine Einstellungen" - -#@ edd -#: includes/register-settings.php:340 -msgid "Payment Gateway Settings" -msgstr "Zahlungsweisen - Einstellungen" - -#@ edd -#: includes/register-settings.php:366 -msgid "Email Settings" -msgstr "E-Mail-Einstellungen" - -#@ edd -#: includes/register-settings.php:419 -msgid "Misc Settings" -msgstr "Sonstige Einstellungen" - -#@ edd -#: includes/scripts.php:40 -msgid "Please enter a discount code" -msgstr "Bitte geben Sie einen Gutschein-Code ein." - -#@ edd -#: includes/scripts.php:41 -msgid "Discount Applied" -msgstr "Gutschein übernommen" - -#@ edd -#: includes/scripts.php:44 -msgid "You have already added this item to your cart" -msgstr "Sie haben dieses Element bereits Ihrem Warenkorb hinzugefügt." - -#@ edd -#: includes/scripts.php:46 -msgid "Loading" -msgstr "Laden..." - -#@ edd -#: includes/templates/history-downloads.php:10 -msgid "Download Name" -msgstr "Name des Downloads" - -#@ edd -#: includes/templates/history-downloads.php:12 -#: includes/templates/history-purchases.php:14 -msgid "Files" -msgstr "Dateien" - -#@ edd -#: includes/templates/history-downloads.php:48 -#: includes/templates/history-purchases.php:48 -#: includes/widgets.php:232 -msgid "No downloadable files found." -msgstr "Keine herunterladbaren Dateien gefunden." - -#@ edd -#: includes/templates/history-downloads.php:68 -msgid "You have not purchased any downloads" -msgstr "Sie haben bisher keine Downloads gekauft." - -#@ edd -#: includes/template-functions.php:167 -msgid "added to your cart" -msgstr "Zu Ihrem Warenkorb hinzugefügt." - -#@ edd -#: includes/thickbox.php:30 -msgid "Insert Download" -msgstr "Download einfügen" - -#@ edd -#: includes/thickbox.php:65 -msgid "You must choose a download" -msgstr "Sie müssen einen Download auswählen" - -#@ edd -#: includes/thickbox.php:100 -msgid "Choose a style" -msgstr "Stil auswählen" - -#@ edd -#: includes/thickbox.php:111 -msgid "Choose a button color" -msgstr "Buttonfarbe auswählen" - -#@ edd -#: includes/thickbox.php:120 -msgid "Link text . . ." -msgstr "Link-Text ..." - -#@ edd -#: includes/thickbox.php:124 -msgid "Cancel" -msgstr "Abbrechen" - -#@ edd -#: includes/thickbox.php:126 -msgid "Button Styles" -msgstr "Button-Stile" - -#@ edd -#: includes/widgets.php:39 -msgid "Downloads Cart" -msgstr "EDD: Downloads-Warenkorb" - -#@ edd -#: includes/widgets.php:39 -msgid "Display the downloads shopping cart" -msgstr "Den Downloads-Warenkorb anzeigen" - -#@ edd -#: includes/widgets.php:82 -#: includes/widgets.php:162 -msgid "Title:" -msgstr "Titel:" - -#@ edd -#: includes/widgets.php:87 -msgid "Show Quantity:" -msgstr "Menge anzeigen:" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:48 -msgid "Payment Status" -msgstr "Zahlungsstatus" - -#@ edd -#: includes/admin-pages/payments-history.php:98 -#: includes/payment-functions.php:295 -msgid "Pending" -msgstr "In Bearbeitung" - -#@ edd -#: includes/payment-functions.php:296 -msgid "Complete" -msgstr "Abgeschlossen" - -#@ edd -#: includes/admin-pages/payments-history.php:318 -#: includes/metabox.php:524 -#: includes/metabox.php:621 -msgid "Previous" -msgstr "Zurück" - -#@ edd -#: includes/metabox.php:264 -msgid "Button" -msgstr "Button" - -#@ edd -#: includes/metabox.php:265 -msgid "Text" -msgstr "Text" - -#@ edd -#: includes/metabox.php:24 -msgid "Purchase Log" -msgstr "Protokoll: Verkäufe" - -#@ edd -#: includes/metabox.php:25 -msgid "File Download Log" -msgstr "Protokoll: Heruntergeladene Dateien" - -#@ edd -#: includes/metabox.php:206 -#: includes/metabox.php:217 -#: includes/register-settings.php:708 -msgid "Upload File" -msgstr "Datei hochladen" - -#@ edd -#: includes/metabox.php:476 -msgid "Sales Log" -msgstr "Umsatzverlauf" - -#@ edd -#: includes/metabox.php:478 -msgid "Each sale for this download is listed below." -msgstr "Jeder Umsatz für diesen Download wird unten aufgelistet." - -#@ edd -#: includes/metabox.php:492 -#: includes/metabox.php:584 -msgid "Date:" -msgstr "Datum:" - -#@ edd -#: includes/metabox.php:496 -msgid "Buyer:" -msgstr "Käufer:" - -#@ edd -#: includes/metabox.php:500 -msgid "Purchase ID:" -msgstr "Kauf-ID:" - -#@ edd -#: includes/metabox.php:508 -msgid "No sales yet" -msgstr "Bisher keine Verkäufe" - -#@ edd -#: includes/metabox.php:565 -msgid "Download Log" -msgstr "Download-Verlauf" - -#@ edd -#: includes/metabox.php:567 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "Jedes Mal, wenn eine Datei heruntergeladen wird, wird es hier aufgezeichnet." - -#@ edd -#: includes/metabox.php:588 -msgid "Downloaded by:" -msgstr "Heruntergeladen von:" - -#@ edd -#: includes/metabox.php:592 -msgid "IP Address:" -msgstr "IP-Adresse:" - -#@ edd -#: includes/metabox.php:596 -msgid "File: " -msgstr "Datei: " - -#@ edd -#: includes/metabox.php:605 -msgid "No file downloads yet yet" -msgstr "Bisher keine Datei-Downloads" - -#@ edd -#: includes/register-settings.php:173 -msgid "From Name" -msgstr "Absender-Name" - -#@ edd -#: includes/register-settings.php:174 -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "Der Name, unter dem Bestellbestätigungen versandt werden. Wahrscheinlich wird dies Ihr Webseiten- oder Shop-Name sein." - -#@ edd -#: includes/misc-functions.php:208 -msgid "Indian Rupee" -msgstr "indische Rupie" - -#@ edd -#: includes/register-settings.php:117 -msgid "Accepted Payment Method Icons" -msgstr "Icons der akzeptierten Zahlungsmethoden" - -#@ edd -#: includes/register-settings.php:118 -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "Sie werden auch Ihre Zahlungsweisen entsprechend einrichten müssen, wenn Sie Kreditkarten akzeptieren." - -#@ edd -#: includes/admin-pages/payments-history.php:285 -#: includes/admin-pages/payments-history.php:287 -#: includes/export-functions.php:127 -msgid "guest" -msgstr "Gast" - -#@ edd -#: includes/checkout-template.php:370 -msgid "(optional)" -msgstr "(optional)" - -#@ edd -#: includes/admin-notices.php:36 -msgid "The payment has been deleted." -msgstr "Die Zahlung wurde gelöscht." - -#@ edd -#: includes/admin-notices.php:39 -msgid "The purchase receipt has been resent." -msgstr "Die Bestellbestätigungs-E-Mail wurde erneut versandt." - -#@ edd -#: includes/admin-pages/add-ons.php:69 -msgid "Add Ons for Easy Digital Downloads" -msgstr "Erweiterungen für Einfache Digitale Downloads" - -#@ edd -#: includes/admin-pages/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "Diese Zusatzmodule (Add-Ons) erweitern die Funktionalität von Einfache Digitale Downloads." - -#@ edd -#: includes/admin-pages/settings.php:36 -msgid "Styles" -msgstr "Stile" - -#@ edd -#: includes/admin-pages.php:30 -msgid "Easy Digital Download Add Ons" -msgstr "Einfache Digitale Downloads Erweiterungen" - -#@ edd -#: includes/admin-pages.php:30 -msgid "Add Ons" -msgstr "Erweiterungen" - -#@ edd -#: includes/cart-template.php:99 -msgid "Your cart is empty." -msgstr "Ihr Warenkorb ist noch leer." - -#@ edd -#: includes/checkout-template.php:190 -msgid "Show Terms" -msgstr "Bedingungen anzeigen" - -#@ edd -#: includes/checkout-template.php:191 -msgid "Hide Terms" -msgstr "Bedingungen verbergen" - -#@ edd -#: includes/checkout-template.php:194 -msgid "Agree to Terms?" -msgstr "Akzeptieren Sie unsere Bedingungen?" - -#@ edd -#: includes/metabox.php:84 -msgid "Pricing" -msgstr "Preisgestaltung" - -#@ edd -#: includes/metabox.php:113 -#: includes/metabox.php:115 -#: includes/metabox.php:135 -#: includes/metabox.php:137 -msgid "9.99" -msgstr "9,99" - -#@ edd -#: includes/metabox.php:295 -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "Wählen Sie eine Farbe für den Verkaufs-Link, falls oben 'Button' ausgewählt wurde." - -#@ edd -#: includes/metabox.php:316 -msgid "Check this if you do not want the purchase button displayed." -msgstr "Diese Einstellung aktivieren, falls Sie keinen Kauf-Button anzeigen wollen." - -#@ edd -#: includes/metabox.php:347 -msgid "This short code can be placed anywhere on your site" -msgstr "Dieser Shortcode kann an einer beliebigen Stelle Ihrer Webseite eingefügt werden." - -#@ edd -#: includes/process-purchase.php:259 -msgid "You must agree to the terms of use" -msgstr "Sie müssen unseren Nutzungsbedigungen zustimmen" - -#@ edd -#: includes/register-settings.php:208 -msgid "Disable Styles" -msgstr "Stile deaktivieren" - -#@ edd -#: includes/register-settings.php:209 -msgid "Check this to disable all included styling" -msgstr "Diese Einstellung auswählen, wenn Sie alle beigepackten CSS-Stile des Plugins deaktivieren wollen." - -#@ edd -#: includes/register-settings.php:214 -msgid "Checkout Button Color" -msgstr "Button-Farbe des 'Kasse'-Buttons" - -#@ edd -#: includes/register-settings.php:215 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "Wählen Sie die Button-Farbe, die Sie für die Buttons des Bezahlvorgangs möchten." - -#@ edd -#: includes/register-settings.php:262 -msgid "Terms of Agreement" -msgstr "Verkaufs- und Zahlungsbedingungen (AGB)" - -#@ edd -#: includes/register-settings.php:268 -msgid "Agree to Terms" -msgstr "Den AGB zustimmen" - -#@ edd -#: includes/register-settings.php:274 -msgid "Agree to Terms Label" -msgstr "Beschriftung für die Zustimmung zu den Bedingungen" - -#@ edd -#: includes/register-settings.php:281 -msgid "Agreement Text" -msgstr "Text der Bedingungen" - -#@ edd -#: includes/register-settings.php:392 -msgid "Style Settings" -msgstr "Stil-Einstellungen" - -#@ edd -#: includes/template-functions.php:263 -msgid "Gray" -msgstr "Grau" - -#@ edd -#: includes/template-functions.php:264 -msgid "Pink" -msgstr "Pink" - -#@ edd -#: includes/template-functions.php:266 -msgid "Green" -msgstr "Grün" - -#@ edd -#: includes/template-functions.php:267 -msgid "Teal" -msgstr "Türkis" - -#@ edd -#: includes/template-functions.php:268 -msgid "Black" -msgstr "Schwarz" - -#@ edd -#: includes/template-functions.php:269 -msgid "Dark Gray" -msgstr "Dunkelgrau" - -#@ edd -#: includes/template-functions.php:270 -msgid "Orange" -msgstr "Orange" - -#@ edd -#: includes/template-functions.php:271 -msgid "Purple" -msgstr "Violett" - -#@ edd -#: includes/template-functions.php:272 -msgid "Slate" -msgstr "Schieferfarben" - -#@ edd -#: includes/template-functions.php:295 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "Sie haben diese Datei bereits gekauft, doch Sie können sie auch mehrmals kaufen." - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:22 -msgid "Buyer's Email" -msgstr "E-Mail des Kunden" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "Falls notwendig, können Sie hier die E-Mail-Adresse des Kunden aktualisieren." - -#@ edd -#: includes/checkout-template.php:434 -msgid "or checkout as a guest." -msgstr "oder bezahlen Sie als Gast." - -#@ edd -#: includes/login-register.php:45 -msgid "Log into Your Account" -msgstr "Anmelden bei Ihrem Benutzerkonto" - -#@ edd -#: includes/login-register.php:61 -msgid "Lost Password" -msgstr "Passwort vergessen" - -#@ edd -#: includes/login-register.php:62 -msgid "Lost Password?" -msgstr "Passwort vergessen?" - -#@ edd -#: includes/login-register.php:69 -msgid "You are already logged in" -msgstr "Sie sind bereits angemeldet" - -#@ edd -#: includes/register-settings.php:196 -msgid "The total price of the purchase" -msgstr "Der Gesamtpreis der Bestellung" - -#@ edd -#: includes/scripts.php:124 -msgid "Use This File" -msgstr "Diese Datei verwenden" - -#@ edd -#: includes/template-functions.php:265 -msgid "Blue" -msgstr "Blau" - -#@ edd -#: includes/widgets.php:112 -msgid "Downloads Categories / Tags" -msgstr "EDD: Downloads-Kategorien/ -Schlagwörter" - -#@ edd -#: includes/widgets.php:112 -msgid "Display the downloads categories or tags" -msgstr "Kategorien oder Schlagwörter (Tags) für Downloads anzeigen." - -#@ edd -#: includes/widgets.php:167 -msgid "Taxonomy:" -msgstr "Taxonomie:" - -#@ edd -#: includes/cart-functions.php:401 -#, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "Sie haben %s erfolgreich zu Ihrem Warenkorb hinzugefügt." - -#@ edd -#: includes/cart-functions.php:402 -msgid "Checkout." -msgstr "Kasse." - -#@ edd -#: includes/install.php:42 -msgid "Purchase Confirmation" -msgstr "Kauf-Bestätigung" - -#@ edd -#: includes/install.php:43 -msgid "Thank you for your purchase!" -msgstr "Danke für Ihren Einkauf!" - -#@ edd -#: includes/install.php:53 -#: includes/widgets.php:193 -msgid "Purchase History" -msgstr "Bestellhistorie" - -#@ edd -#: includes/process-purchase.php:454 -msgid "You must login or register to complete your purchase" -msgstr "Sie müssen sich anmelden oder neu registrieren, um Ihre Bestellung abschließen zu können." - -#@ edd -#: includes/templates/history-purchases.php:11 -msgid "Purchase ID" -msgstr "Kauf-ID" - -#@ edd -#: includes/templates/history-purchases.php:61 -msgid "You have not made any purchases" -msgstr "Sie haben noch keine Bestellungen getätigt." - -#@ edd -#: includes/admin-notices.php:62 -msgid "There seems to be an issue with the server. Please try again in a few minutes." -msgstr "Es scheint Probleme mit dem Server zu geben. Bitte versuchen Sie es noch einmal in einigen Minuten." - -#@ edd -#: includes/admin-pages/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "Alle Erweiterungen durchstöbern" - -#@ edd -#: includes/email-template.php:107 -msgid "Sample Product Title" -msgstr "Beispiel-Produkttitel" - -#@ edd -#: includes/email-template.php:110 -msgid "Sample Download File Name" -msgstr "Beispiel-Download-Dateiname" - -#@ edd -#: includes/email-template.php:158 -msgid "Purchase Receipt Preview" -msgstr "Bestellbestätigung-Vorschau" - -#@ edd -#: includes/email-template.php:158 -msgid "Preview Purchase Receipt" -msgstr "Vorschau der Bestellbestätigung" - -#@ edd -#: includes/email-template.php:23 -msgid "Default Template" -msgstr "Standard-Vorlage" - -#@ edd -#: includes/email-template.php:24 -msgid "No template, plain text only" -msgstr "Keine Vorlage, nur reiner Text" - -#@ edd -#: includes/misc-functions.php:209 -msgid "Turkish Lira" -msgstr "Türkische Lira" - -#@ edd -#: includes/admin-pages/payments-history.php:101 -#: includes/payment-functions.php:297 -msgid "Refunded" -msgstr "Zurückerstattet" - -#@ edd -#: includes/process-purchase.php:206 -msgid "The selected gateway is not active" -msgstr "Die ausgewählte Zahlungsweise ist nicht aktiv" - -#@ edd -#: includes/process-purchase.php:210 -msgid "No gateway has been selected" -msgstr "Es wurde keine Zahlungsweise ausgewählt" - -#@ edd -#: includes/process-purchase.php:303 -msgid "The user information is invalid." -msgstr "Die Benutzerinformation ist ungültig." - -#@ edd -#: includes/process-purchase.php:366 -msgid "You must register or login to complete your purchase" -msgstr "Sie müssen sich registrieren oder anmelden, um Ihren Kauf abschließen zu können." - -#@ edd -#: includes/process-purchase.php:396 -#: includes/process-purchase.php:529 -msgid "Enter an email" -msgstr "Eine E-Mail-Adresse eingeben" - -#@ edd -#: includes/process-purchase.php:427 -msgid "Enter the password confirmation" -msgstr "Bestätigen Sie das Passwort" - -#@ edd -#: includes/register-settings.php:145 -msgid "If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal." -msgstr "Falls Zahlungen als nicht vollständig/ abgeschlossen gekennzeichnet werden, aktivieren Sie bitte dieses Kontrollkästchen. Hinweis: Dies erfordert jedoch, dass die Käufer von PayPal entsprechend zu Ihrer Webseite zurückkehren." - -#@ edd -#: includes/scripts.php:126 -msgid "Are you sure you wish to delete this payment?" -msgstr "Sind Sie sicher, dass Sie diese Zahlung löschen wollen?" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:64 -msgid "Send Purchase Receipt" -msgstr "Bestellbestätigung senden" - -#@ edd -#: includes/admin-pages/forms/edit-payment.php:68 -msgid "Check this box to send the purchase receipt, including all download links." -msgstr "Diese Einstellung wählen, um eine Bestellbestätigung zu versenden, die auch alle Download-Links enthält." - -#@ edd -#: includes/admin-pages/payments-history.php:266 -msgid "Payment Method:" -msgstr "Zahlungsweise:" - -#@ edd -#: includes/email-template.php:199 -msgid "Dear" -msgstr "Sehr geehrte/r" - -#@ edd -#: includes/email-template.php:200 -msgid "Thank you for your purchase. Please click on the link(s) below to download your files." -msgstr "Vielen Dank für Ihre Bestellung! Bitte klicken Sie auf die Links unten, um Ihre Datei(en) herunterzuladen." - -#@ edd -#: includes/graphing.php:42 -#, php-format -msgid "%s Performance in Sales" -msgstr "%s-Performance in Verkäufen" - -#@ edd -#: includes/graphing.php:88 -#, php-format -msgid "%s Performance in Earnings" -msgstr "%s-Performance bei den Einnahmen" - -#@ edd -#: includes/metabox.php:22 -#, php-format -msgid "%1$s Configuration" -msgstr "%1$s-Einstellungen" - -#@ edd -#: includes/metabox.php:23 -#, php-format -msgid "%1$s Stats" -msgstr "%1$s-Statistiken" - -#@ edd -#: includes/post-types.php:44 -#, php-format -msgid "Add New %1$s" -msgstr "Neuen %1$s hinzufügen" - -#@ edd -#: includes/post-types.php:45 -#, php-format -msgid "Edit %1$s" -msgstr "%1$s bearbeiten" - -#@ edd -#: includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "Neuer %1$s" - -#@ edd -#: includes/post-types.php:47 -#, php-format -msgid "All %2$s" -msgstr "Alle %2$s" - -#@ edd -#: includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "%1$s ansehen" - -#@ edd -#: includes/post-types.php:49 -#, php-format -msgid "Search %2$s" -msgstr "%2$s suchen" - -#@ edd -#: includes/post-types.php:50 -#, php-format -msgid "No %2$s found" -msgstr "Es wurde kein %2$s gefunden." - -#@ edd -#: includes/post-types.php:51 -#, php-format -msgid "No %2$s found in Trash" -msgstr "Es befindet sich kein %2$s im Papierkorb." - -#@ edd -#: includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "%2$s" - -#@ edd -#: includes/process-purchase.php:289 -msgid "Please enter a valid email address." -msgstr "Bitte geben Sie eine gültige E-Mail-Adresse an." - -#@ edd -#: includes/register-settings.php:225 -msgid "Disable Ajax" -msgstr "AJAX deaktivieren" - -#@ edd -#: includes/register-settings.php:226 -msgid "Check this to disable AJAX for the shopping cart." -msgstr "Wählen Sie diese Einstellung um AJAX für den Warenkorb zu deaktivieren." - -#@ edd -#: includes/thickbox.php:29 -#: includes/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "%s einfügen" - -#@ edd -#: includes/thickbox.php:88 -#, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "Verwenden Sie das Formular unten, um einen Shortcode für den Kauf eines %s einzufügen" - -#@ edd -#: includes/thickbox.php:91 -#, php-format -msgid "Choose a %s" -msgstr "Einen %s auswählen" - -#@ edd -#: includes/email-functions.php:75 -msgid "Payment Method: " -msgstr "Zahlungsmethode: " - -#@ edd -#: includes/gateway-functions.php:28 -msgid "Test Payment" -msgstr "Test-Zahlung" - -#@ edd -#: includes/register-settings.php:160 -msgid "Email Template" -msgstr "E-Mail-Template" - -#@ edd -#: includes/register-settings.php:161 -msgid "Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" to see the new template." -msgstr "Wählen Sie eine Vorlage (Template). Klicken Sie \"Änderungen übernehmen\" und danach \"Vorschau der Bestellbestätigung\", um das neue Template anzusehen." - -#@ edd -#: includes/register-settings.php:198 -msgid "The method of payment used for this purchase" -msgstr "Die Zahlungsweise, die für diesen Kauf verwendet wurde." - -#@ edd -#: includes/admin-pages/payments-history.php:90 -msgid "All" -msgstr "Alle" - -#@ edd -#: includes/admin-pages/payments-history.php:95 -msgid "Completed" -msgstr "Abgeschlossen" - -#@ edd -#: includes/admin-pages/payments-history.php:106 -msgid "Export" -msgstr "Exportieren" - -#@ edd -#: includes/admin-pages/payments-history.php:131 -msgid "Showing payments for: " -msgstr "Zahlungen anzeigen für: " - -#@ edd -#: includes/admin-pages/payments-history.php:131 -msgid "clear" -msgstr "leeren" - -#@ edd -#: includes/admin-pages/payments-history.php:271 -msgid "Purchase Key" -msgstr "Kaufschlüssel" - -#@ edd -#: includes/admin-pages/reports.php:41 -msgid "Download Sales and Earnings PDF Report for all Products" -msgstr "Umsatz- und Verdienst-Berichte (PDF) für alle Produkte herunterladen" - -#@ edd -#: includes/admin-pages/reports.php:44 -msgid "Please Note: Transactions created while in test mode are not included on this page or in the PDF reports." -msgstr "Bitte beachten: Transaktionen, die im Testmodus getätigt wurden, sind nicht auf dieser Seite oder in den PDF-Berichten enthalten." - -#@ edd -#: includes/checkout-template.php:134 -msgid "Personal Info" -msgstr "Persönliche Info" - -#@ edd -#: includes/dashboard-columns.php:228 -#, php-format -msgid "%s Data" -msgstr "%s Daten" - -#@ edd -#: includes/export-functions.php:44 -msgid "Discounts," -msgstr "Rabatte" - -#@ edd -#: includes/export-functions.php:45 -msgid "Amount paid" -msgstr "Gezahlter Betrag" - -#@ edd -#: includes/export-functions.php:46 -msgid "Payment method" -msgstr "Zahlungsweise" - -#@ edd -#: includes/graphing.php:236 -msgid "Sales per month" -msgstr "Verkäufe pro Monat" - -#@ edd -#: includes/metabox.php:184 -msgid "File Name" -msgstr "Dateiname" - -#@ edd -#: includes/metabox.php:185 -msgid "File URL" -msgstr "Datei-URL" - -#@ edd -#: includes/metabox.php:199 -msgid "All Prices" -msgstr "Alle Preise" - -#@ edd -#: includes/pdf-reports.php:36 -msgid "to" -msgstr "bis" - -#@ edd -#: includes/pdf-reports.php:41 -#: includes/pdf-reports.php:52 -msgid "Sales and earnings reports for the current year for all products" -msgstr "Umsatz- und Verdienst-Berichte für das aktuelle Jahr für alle Produkte" - -#@ edd -#: includes/pdf-reports.php:57 -msgid "Date Range: " -msgstr "Datumsbereich: " - -#@ edd -#: includes/pdf-reports.php:61 -msgid "Table View" -msgstr "Tabellarische Ansicht" - -#@ edd -#: includes/pdf-reports.php:65 -msgid "Product Name" -msgstr "Produktname" - -#@ edd -#: includes/pdf-reports.php:69 -msgid "Number of Sales" -msgstr "Anzahl der Verkäufe" - -#@ edd -#: includes/pdf-reports.php:70 -msgid "Earnings to Date" -msgstr "Bisheriger Verdienst" - -#@ edd -#: includes/pdf-reports.php:119 -msgid "Graph View" -msgstr "Diagramm-Ansicht" - -#@ edd -#: includes/pdf-reports.php:212 -msgid "Sales and Earnings by Month for all Products" -msgstr "Umsätze und Verdienste pro Monat für alle Produkte" - -#@ edd -#: includes/pdf-reports.php:226 -msgid "Jan" -msgstr "Jan" - -#@ edd -#: includes/pdf-reports.php:227 -msgid "Feb" -msgstr "Feb" - -#@ edd -#: includes/pdf-reports.php:228 -msgid "Mar" -msgstr "März" - -#@ edd -#: includes/pdf-reports.php:229 -msgid "Apr" -msgstr "Apr" - -#@ edd -#: includes/pdf-reports.php:230 -msgid "May" -msgstr "Mai" - -#@ edd -#: includes/pdf-reports.php:231 -msgid "June" -msgstr "Juni" - -#@ edd -#: includes/pdf-reports.php:232 -msgid "July" -msgstr "Juli" - -#@ edd -#: includes/pdf-reports.php:233 -msgid "Aug" -msgstr "Aug" - -#@ edd -#: includes/pdf-reports.php:234 -msgid "Sept" -msgstr "Sept" - -#@ edd -#: includes/pdf-reports.php:235 -msgid "Oct" -msgstr "Okt" - -#@ edd -#: includes/pdf-reports.php:236 -msgid "Nov" -msgstr "Nov" - -#@ edd -#: includes/pdf-reports.php:237 -msgid "Dec" -msgstr "Dez" - -#@ edd -#: includes/register-settings.php:150 -msgid "Disable PayPal IPN Verification" -msgstr "PayPal Sofortige Zahlungsbestätigung (IPN) Verifizierung deaktivieren" - -#@ edd -#: includes/register-settings.php:197 -msgid "The unique ID number for this purchase receipt" -msgstr "Die einzigartige ID-Ziffer für diese Bestellbestätigung" - -#@ edd -#: includes/register-settings.php:244 -msgid "Display the registration and login forms on the checkout page for non-logged-in users." -msgstr "Die Formulare zur Benutzerregistrierung und zum Anmelden auf der 'Kasse'-Seite auch für nicht angemeldete Besucher anzeigen." - -#@ edd -#: includes/register-settings.php:249 -msgid "Download Link Expiration" -msgstr "Ablauf von Download-Links" - -#@ edd -#: includes/register-settings.php:250 -msgid "How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours." -msgstr "Wie lange sollen Download-Links aktiv sein? Der Standard ist 24 Stunden vom Zeitpunkt der Generierung. Geben Sie eine Zeit in Stunden ein." - -#@ edd -#: includes/register-settings.php:257 -msgid "Check this if you do not want to allow users to redownload items from their purchase history." -msgstr "Aktivieren Sie diese Einstellung, wenn Sie Benutzern erlauben wollen, Dateien erneut von Ihrer Benutzerseite herunterzuladen." - -#@ edd -#: includes/register-settings.php:269 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing." -msgstr "Aktivieren Sie diese Einstellung, um beim Bezahlvorgang eine Zustimmung zu AGB/ Widerrufsbelehrung anzuzeigen, die Besucher bestätigen müssen, um die Bestellung abschliessen zu können." - -#@ edd -#: includes/register-settings.php:275 -msgid "Label shown next to the agree to terms check box." -msgstr "Beschriftung neben dem Kontrollkästchen zur Zustimmung zu AGB/ Widerrufsbelehrung." - -#@ edd -#: includes/register-settings.php:282 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "Falls Zustimmung zu AGB/ Widerrufsbelehrung aktiviert ist, geben Sie hier den entsprechenden Text ein." - -#@ edd -#: includes/register-settings.php:746 -msgid "Settings Updated" -msgstr "Einstellungen aktualisiert" - -#@ edd -#: includes/scripts.php:125 -msgid "Sorry, not available for variable priced products." -msgstr "Entschuldigung, dies ist nicht verfügbar für Produkte mit variablem Preis." - -#@ edd -#: includes/ajax-functions.php:102 -msgid "This discount code has been used already" -msgstr "Dieser Gutschein-Code wurde bereits verwendet." - -#@ edd -#: includes/email-functions.php:73 -msgid "Purchased by: " -msgstr "Gekauft von: " - -#@ edd -#: includes/misc-functions.php:210 -msgid "Iranian Rial" -msgstr "Iranischer Rial" - -#@ edd -#: includes/pdf-reports.php:112 -msgid "No Downloads found." -msgstr "Es wurden keine Downloads gefunden." - -#@ edd -#: includes/register-settings.php:47 -msgid "Checkout Page" -msgstr "'Kasse'-Seite" - -#@ edd -#: includes/register-settings.php:61 -msgid "Download Links on Success Page" -msgstr "Download-Links auf der Erfolgs-Seite" - -#@ edd -#: includes/register-settings.php:62 -msgid "Show a list of all download links on the success page after completing a purchase?" -msgstr "Eine Liste aller Download-Links auf der Erfolgs-Seite nach dem erfolgreichen Kauf anzeigen?" - -#@ edd -#: includes/register-settings.php:151 -msgid "If payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases." -msgstr "Wenn Zahlungen nicht als abgeschlossen gekennzeichnet werden, dann aktivieren Sie dieses Kontrollkästchen. Dies zwingt die Webseite dazu, eine leicht weniger sichere Methode zur Kauf-Verifizierung anzuwenden." - -#@ edd -#: includes/register-settings.php:287 -msgid "Complete Purchase Text" -msgstr "Text für abgeschlossenen Kauf" - -#@ edd -#: includes/register-settings.php:288 -msgid "The button label for completing a purchase." -msgstr "Die Button-Beschriftung, um eine Bestellung abzuschließen." - -#@ edd -#: includes/scripts.php:42 -msgid "Please enter an email address before applying a discount code" -msgstr "Geben Sie bitte eine E-Mail-Adresse ein, bevor Sie einen Gutschein-Code hinzufügen." - -#@ edd -#: includes/admin-pages/forms/add-discount.php:75 -#: includes/admin-pages/forms/edit-discount.php:89 -msgid "Minimum Amount" -msgstr "Mindestbestellwert" - -#@ edd -#: includes/admin-pages/forms/add-discount.php:79 -#: includes/admin-pages/forms/edit-discount.php:93 -msgid "The minimum amount that must be purchased before this discount can be used. Leave blank for no minimum." -msgstr "Der Wert, welcher mindestens umgesetzt werden muss, bevor ein Gutschein zugewiesen werden kann. Frei lassen, um keinen Mindestbestellwert zu verwenden." - -#@ edd -#: includes/graphing.php:168 -msgid "Day" -msgstr "Tag" - -#@ default -#: includes/admin-notices.php:44 -#, php-format -msgid "The payment history needs updated. %s" -msgstr "Die bisherigen Zahlungen sollten aktualisiert werden. %s" - -#@ edd -#: includes/admin-notices.php:44 -msgid "Click to Upgrade" -msgstr "Klicken, um zu aktualisieren" - -#@ edd -#: includes/admin-pages/payments-history.php:249 -msgid "Date and Time:" -msgstr "Datum und Zeit:" - -#@ edd -#: includes/admin-pages/payments-history.php:304 -msgid "Total Earnings:" -msgstr "Gesamter Verdienst:" - -#@ edd -#: includes/admin-pages/reports.php:42 -msgid "Download a CSV Customers List" -msgstr "Eine Kundenliste als CSV-Datei herunterladen" - -#@ edd -#: includes/checkout-template.php:317 -msgid "Billing Country" -msgstr "Land für Rechnungsstellung" - -#@ edd -#: includes/export-functions.php:169 -msgid "Export not allowed for non-administrators." -msgstr "Export ist für nicht-Administratoren nicht erlaubt." - -#@ edd -#: includes/graphing.php:189 -#, php-format -msgid "Earnings per day for last %s days" -msgstr "Verdienst pro Tag für die letzten %s Tage" - -#@ edd -#: includes/metabox.php:98 -msgid "Enable variable pricing" -msgstr "Preis-Varianten aktivieren" - -#@ edd -#: includes/metabox.php:120 -#: includes/metabox.php:142 -msgid "Option Name" -msgstr "Optionsname" - -#@ edd -#: includes/metabox.php:159 -msgctxt "Variable price preposition. $2.99 for {X}" -msgid "for" -msgstr "für" - -#@ edd -#: includes/process-download.php:266 -#: includes/process-download.php:283 -msgid "Sorry but this file does not exist." -msgstr "Entschuldigung, aber diese Datei existiert nicht." - -#@ edd -#: includes/shortcodes.php:194 -msgid "Purchase All Items" -msgstr "Alle Elemente kaufen" - -#@ edd -#: includes/shortcodes.php:336 -#, php-format -msgctxt "download post type name" -msgid "No %s found" -msgstr "Kein %s gefunden" - -#@ edd -#: includes/template-functions.php:48 -#, php-format -msgid "No checkout page has been configured. Visit Settings to set one." -msgstr "Es wurde keine 'Kasse'-Seite eingerichtet. Gehen Sie zu den Einstellungen, um Eine einzurichten." - -#@ edd -#: includes/widgets.php:193 -msgid "Display a user's purchase history" -msgstr "Die Bestellhistorie eines Benutzers anzeigen" - -#@ edd -#: includes/widgets.php:259 -msgid "Title" -msgstr "Titel" - diff --git a/languages/edd-es_ES.mo b/languages/edd-es_ES.mo deleted file mode 100755 index f9aaaf67076..00000000000 Binary files a/languages/edd-es_ES.mo and /dev/null differ diff --git a/languages/edd-es_ES.po b/languages/edd-es_ES.po deleted file mode 100755 index 2b43bf6390d..00000000000 --- a/languages/edd-es_ES.po +++ /dev/null @@ -1,2026 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-11 09:21-0300\n" -"PO-Revision-Date: 2012-07-11 09:26-0300\n" -"Last-Translator: Matt Varone \n" -"Language-Team: Matt Varone \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_n;_x\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-Language: Spanish\n" -"X-Poedit-Country: ARGENTINA\n" -"X-Poedit-SearchPath-0: ..\n" -"X-Poedit-SearchPath-1: ../includes\n" -"X-Poedit-SearchPath-2: ../includes/admin-pages\n" -"X-Poedit-SearchPath-3: ../includes/admin-pages/forms\n" -"X-Poedit-SearchPath-4: ../includes/gateways\n" -"X-Poedit-SearchPath-5: .\n" - -#: ../includes/admin-notices.php:24 -msgid "Discount code updated." -msgstr "Codigo de descuento actualizado." - -#: ../includes/admin-notices.php:27 -msgid "There was a problem updating your discount code, please try again." -msgstr "Ha ocurrido un problema al actualizar su codigo de descuento, por favor trate nuevamente" - -#: ../includes/admin-notices.php:30 -msgid "The payment has been deleted." -msgstr "El pago ha sido borrado." - -#: ../includes/admin-notices.php:33 -msgid "The purchase receipt has been resent." -msgstr "El recibo de compra ah sido reenviado." - -#: ../includes/admin-notices.php:49 -msgid "There seems to be an issue with the server. Please try again in a few minutes." -msgstr "Error en el sistema. Por favor trate nuevamente en unos minutos." - -#: ../includes/admin-pages.php:26 -msgid "Payment History" -msgstr "Historal de Pagos" - -#: ../includes/admin-pages.php:27 -msgid "Discount Codes" -msgstr "Códigos de Descuento" - -#: ../includes/admin-pages.php:28 -msgid "Earnings and Sales Reports" -msgstr "Reportes de Ganancias y Ventas" - -#: ../includes/admin-pages.php:28 -msgid "Reports" -msgstr "Reportes" - -#: ../includes/admin-pages.php:29 -msgid "Easy Digital Download Settings" -msgstr "Ajustes de Easy Digital Download" - -#: ../includes/admin-pages.php:29 -msgid "Settings" -msgstr "Ajustes" - -#: ../includes/admin-pages.php:30 -msgid "Easy Digital Download Add Ons" -msgstr "Extensiones de Easy Digital Download" - -#: ../includes/admin-pages.php:30 -msgid "Add Ons" -msgstr "Agregados" - -#: ../includes/ajax-functions.php:108 -msgid "The discount you entered is invalid" -msgstr "El descuento ingresado no es valido" - -#: ../includes/cart-functions.php:390 -#, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "Ah agregado %s con exito al su carro de compras." - -#: ../includes/cart-functions.php:391 -msgid "Checkout." -msgstr "Caja." - -#: ../includes/cart-template.php:50 -#: ../includes/cart-template.php:53 -msgid "Checkout" -msgstr "Caja" - -#: ../includes/cart-template.php:83 -msgid "remove" -msgstr "remover" - -#: ../includes/cart-template.php:100 -msgid "Your cart is empty." -msgstr "Su carrito esta vacío." - -#: ../includes/checkout-template.php:75 -msgid "Choose Your Payment Method" -msgstr "Elija Su Metodo de Pago" - -#: ../includes/checkout-template.php:86 -msgid "Next" -msgstr "Siguiente" - -#: ../includes/checkout-template.php:134 -msgid "Email address" -msgstr "Direccion de email" - -#: ../includes/checkout-template.php:135 -msgid "Email Address" -msgstr "Dirección de Email" - -#: ../includes/checkout-template.php:139 -#: ../includes/checkout-template.php:140 -#: ../includes/checkout-template.php:355 -#: ../includes/checkout-template.php:356 -msgid "First Name" -msgstr "Nombre" - -#: ../includes/checkout-template.php:143 -#: ../includes/checkout-template.php:359 -msgid "Last name" -msgstr "Apellido" - -#: ../includes/checkout-template.php:144 -#: ../includes/checkout-template.php:360 -msgid "Last Name" -msgstr "Apellido" - -#: ../includes/checkout-template.php:152 -msgid "Enter discount" -msgstr "Ingrese el descuento" - -#: ../includes/checkout-template.php:154 -msgid "Discount" -msgstr "Descuento" - -#: ../includes/checkout-template.php:156 -msgid "Apply Discount" -msgstr "Aplicar el Descuento" - -#: ../includes/checkout-template.php:182 -msgid "Show Terms" -msgstr "Mostrar Terminos" - -#: ../includes/checkout-template.php:183 -msgid "Hide Terms" -msgstr "Esconder Términos" - -#: ../includes/checkout-template.php:186 -msgid "Agree to Terms?" -msgstr "Acepta los Términos?" - -#: ../includes/checkout-template.php:203 -#: ../includes/dashboard-columns.php:73 -msgid "Purchase" -msgstr "Comprar" - -#: ../includes/checkout-template.php:210 -msgid "Go back" -msgstr "Volver" - -#: ../includes/checkout-template.php:214 -msgid "You must be logged in to complete your purchase" -msgstr "Para completar su compra debe estar registrado" - -#: ../includes/checkout-template.php:243 -msgid "Card name" -msgstr "Nombre en la tarjeta" - -#: ../includes/checkout-template.php:244 -msgid "Name on the Card" -msgstr "Titular de la Tarjeta" - -#: ../includes/checkout-template.php:247 -msgid "Card number" -msgstr "Numero de tarjeta" - -#: ../includes/checkout-template.php:248 -msgid "Card Number" -msgstr "Numero de Tarjeta" - -#: ../includes/checkout-template.php:251 -msgid "Security code" -msgstr "Codigo de Seguridad" - -#: ../includes/checkout-template.php:252 -msgid "CVC" -msgstr "CVC" - -#: ../includes/checkout-template.php:256 -msgid "Month" -msgstr "Mes" - -#: ../includes/checkout-template.php:258 -msgid "Year" -msgstr "Año" - -#: ../includes/checkout-template.php:259 -msgid "Expiration (MM/YYYY)" -msgstr "Expiración (MM/AAAA)" - -#: ../includes/checkout-template.php:287 -msgid "Credit Card Info" -msgstr "Informacion de la Tarjeta" - -#: ../includes/checkout-template.php:289 -msgid "Address line 1" -msgstr "Dirección linea 1 " - -#: ../includes/checkout-template.php:290 -msgid "Billing Address" -msgstr "Dirección que recibe la Factura" - -#: ../includes/checkout-template.php:293 -msgid "Address line 2" -msgstr "Dirección linea 2" - -#: ../includes/checkout-template.php:294 -msgid "Billing Address Line 2" -msgstr "Dirección que recibe la Factura linea 2" - -#: ../includes/checkout-template.php:297 -msgid "City" -msgstr "Ciudad" - -#: ../includes/checkout-template.php:298 -msgid "Billing City" -msgstr "Ciudad Facturacion" - -#: ../includes/checkout-template.php:301 -msgid "State / Province" -msgstr "Estado / Provincia" - -#: ../includes/checkout-template.php:302 -msgid "Billing State / Province" -msgstr "Facturacion Estado / Provincia" - -#: ../includes/checkout-template.php:305 -msgid "Zip / Postal code" -msgstr "Zip / Codigo Postal" - -#: ../includes/checkout-template.php:306 -msgid "Billing Zip / Postal Code" -msgstr "Facturación Zip / Codigo Postal" - -#: ../includes/checkout-template.php:332 -msgid "Already have an account?" -msgstr "Ya tiene una cuenta?" - -#: ../includes/checkout-template.php:332 -msgid "Login" -msgstr "Ingresar" - -#: ../includes/checkout-template.php:334 -msgid "Create an account" -msgstr "Crear una cuenta" - -#: ../includes/checkout-template.php:334 -msgid "(optional)" -msgstr "(opcional)" - -#: ../includes/checkout-template.php:337 -#: ../includes/checkout-template.php:338 -#: ../includes/checkout-template.php:385 -msgid "Username" -msgstr "Nombre de Usuario" - -#: ../includes/checkout-template.php:341 -#: ../includes/checkout-template.php:342 -#: ../includes/checkout-template.php:389 -msgid "Password" -msgstr "Contraseña" - -#: ../includes/checkout-template.php:345 -msgid "Confirm password" -msgstr "Confirmar Contraseña" - -#: ../includes/checkout-template.php:346 -msgid "Password Again" -msgstr "Reingresar Contraseña" - -#: ../includes/checkout-template.php:351 -#: ../includes/checkout-template.php:352 -msgid "Email" -msgstr "Email" - -#: ../includes/checkout-template.php:381 -msgid "Login to your account" -msgstr "Ingresa a tu cuenta" - -#: ../includes/checkout-template.php:384 -msgid "Your username" -msgstr "Su Usuario" - -#: ../includes/checkout-template.php:388 -msgid "Your password" -msgstr "Su Password" - -#: ../includes/checkout-template.php:396 -msgid "Need to create an account?" -msgstr "No tiene cuenta?" - -#: ../includes/checkout-template.php:398 -msgid "Register" -msgstr "Registro" - -#: ../includes/checkout-template.php:398 -msgid "or checkout as a guest." -msgstr "o complete el pago como visitante." - -#: ../includes/dashboard-columns.php:26 -msgid "Name" -msgstr "Nombre" - -#: ../includes/dashboard-columns.php:27 -msgid "Categories" -msgstr "Categorias" - -#: ../includes/dashboard-columns.php:28 -msgid "Tags" -msgstr "Etiquetas" - -#: ../includes/dashboard-columns.php:29 -msgid "Price" -msgstr "Precio" - -#: ../includes/dashboard-columns.php:30 -msgid "Sales" -msgstr "Ventas" - -#: ../includes/dashboard-columns.php:31 -msgid "Earnings" -msgstr "Ganancias" - -#: ../includes/dashboard-columns.php:32 -msgid "Short Code" -msgstr "Short Code" - -#: ../includes/dashboard-columns.php:33 -msgid "Date" -msgstr "Fecha" - -#: ../includes/dashboard-columns.php:181 -msgid "Show all categories" -msgstr "Mostrar todas las categorias" - -#: ../includes/dashboard-columns.php:192 -msgid "Show all tags" -msgstr "Mostrar todas las etiquetas" - -#: ../includes/email-functions.php:47 -msgid "Hello" -msgstr "Hola" - -#: ../includes/email-functions.php:47 -msgid "A download purchase has been made" -msgstr "Se ha realizado la compra de una descarga" - -#: ../includes/email-functions.php:48 -msgid "Downloads sold:" -msgstr "Descargas vendidas:" - -#: ../includes/email-functions.php:59 -msgid "Amount: " -msgstr "Monto:" - -#: ../includes/email-functions.php:60 -msgid "Payment Method: " -msgstr "Metodo de pago:" - -#: ../includes/email-functions.php:61 -msgid "Thank you" -msgstr "Gracias" - -#: ../includes/email-functions.php:63 -msgid "New download purchase" -msgstr "Nueva compra de descarga" - -#: ../includes/email-template.php:82 -msgid "Sample Product Title" -msgstr "Ejemplo de titulo de producto" - -#: ../includes/email-template.php:85 -msgid "Sample Download File Name" -msgstr "Nombre Ejemplo de Descarga" - -#: ../includes/email-template.php:133 -msgid "Purchase Receipt Preview" -msgstr "Previsualizar Recibo de Compra" - -#: ../includes/email-template.php:133 -msgid "Preview Purchase Receipt" -msgstr "Previzualizar el Recibo de Compra" - -#: ../includes/email-template.php:138 -msgid "Close" -msgstr "Cerrar" - -#: ../includes/email-template.php:175 -msgid "Dear" -msgstr "Estimado" - -#: ../includes/email-template.php:176 -msgid "Thank you for your purchase. Please click on the link(s) below to download your files." -msgstr "Gracias por su compra. Por favor haga click en/los enlace/s a continuacion para descargar sus archivos." - -#: ../includes/email-template.php:295 -msgid "Default Template" -msgstr "Plantilla por default" - -#: ../includes/email-template.php:296 -msgid "No template, plain text only" -msgstr "Sin plantilla, formato texto simple" - -#: ../includes/error-tracking.php:30 -msgid "Error" -msgstr "Error" - -#: ../includes/export-functions.php:31 -msgid "ID" -msgstr "ID" - -#: ../includes/export-functions.php:34 -msgid "Products" -msgstr "Productos" - -#: ../includes/export-functions.php:35 -msgid "Discounts," -msgstr "Descuentos," - -#: ../includes/export-functions.php:36 -msgid "Amount paid" -msgstr "Monto pagado:" - -#: ../includes/export-functions.php:37 -msgid "Payment method" -msgstr "Metodo de pago" - -#: ../includes/export-functions.php:38 -msgid "Key" -msgstr "Valor" - -#: ../includes/export-functions.php:40 -msgid "User" -msgstr "Usuario" - -#: ../includes/export-functions.php:41 -msgid "Status" -msgstr "Estado" - -#: ../includes/export-functions.php:101 -#: ../includes/export-functions.php:109 -msgid "none" -msgstr "ninguno" - -#: ../includes/export-functions.php:117 -msgid "guest" -msgstr "invitado" - -#: ../includes/export-functions.php:125 -msgid "No payments recorded yet" -msgstr "No se han registrado pagos al momento" - -#: ../includes/gateway-functions.php:28 -msgid "Test Payment" -msgstr "Probar Pago" - -#: ../includes/graphing.php:42 -#, php-format -msgid "%s Performance in Sales" -msgstr "Performance de %s en Ventas" - -#: ../includes/graphing.php:77 -#: ../includes/post-types.php:124 -msgid "Download" -msgstr "Descargar" - -#: ../includes/graphing.php:88 -#, php-format -msgid "%s Performance in Earnings" -msgstr "Performance de %s en Ganancias" - -#: ../includes/graphing.php:136 -msgid "Earnings per month" -msgstr "Ganancias por mes" - -#: ../includes/install.php:42 -msgid "Purchase Confirmation" -msgstr "Comfirmacion de Compra" - -#: ../includes/install.php:43 -msgid "Thank you for your purchase!" -msgstr "Gracias por su compra!" - -#: ../includes/install.php:53 -msgid "Purchase History" -msgstr "Historial de Compras" - -#: ../includes/login-register.php:45 -msgid "Log into Your Account" -msgstr "Ingresa A Tu Cuenta" - -#: ../includes/login-register.php:61 -msgid "Lost Password" -msgstr "Recordar Contraseña" - -#: ../includes/login-register.php:62 -msgid "Lost Password?" -msgstr "Recuperar Contraseña?" - -#: ../includes/login-register.php:69 -msgid "You are already logged in" -msgstr "Ya esta ingresado en el sistema" - -#: ../includes/login-register.php:92 -msgid "The password you entered is incorrect" -msgstr "La contraseña que ingreso no es valida" - -#: ../includes/login-register.php:95 -msgid "The username you entered does not exist" -msgstr "El nombre de usuario no existe" - -#: ../includes/metabox.php:22 -#, php-format -msgid "%1$s Configuration" -msgstr "Configurar %1$s" - -#: ../includes/metabox.php:23 -#, php-format -msgid "%1$s Stats" -msgstr "%1$s Estado" - -#: ../includes/metabox.php:24 -msgid "Purchase Log" -msgstr "Registro de Compra" - -#: ../includes/metabox.php:25 -msgid "File Download Log" -msgstr "Registro de Descargas de Archivo" - -#: ../includes/metabox.php:26 -msgid "Payment Info" -msgstr "Informacion del Pago" - -#: ../includes/metabox.php:69 -msgid "Pricing" -msgstr "Precio" - -#: ../includes/metabox.php:73 -msgid "Check this to enable variable pricing." -msgstr "Seleccione para activar precio variable." - -#: ../includes/metabox.php:96 -msgid "price option name" -msgstr "nombre de la opcion de precio" - -#: ../includes/metabox.php:97 -#: ../includes/metabox.php:107 -msgid "9.99" -msgstr "9,99" - -#: ../includes/metabox.php:106 -msgid "price name" -msgstr "nombre del precio" - -#: ../includes/metabox.php:110 -msgid "Add New Price Option" -msgstr "Agregar Nueva Opcion de Precio" - -#: ../includes/metabox.php:126 -msgid "Enter the download price. Do not include a currency symbol" -msgstr "Ingrese el precio de descarga. No incluye el simbolo de la moneda." - -#: ../includes/metabox.php:153 -msgid "Download Files" -msgstr "Descargar Archivos" - -#: ../includes/metabox.php:156 -msgid "File Name" -msgstr "Nombre del Archivo" - -#: ../includes/metabox.php:157 -msgid "File URL" -msgstr "URL Archivo:" - -#: ../includes/metabox.php:168 -#: ../includes/metabox.php:187 -msgid "file name" -msgstr "nombre del archivo" - -#: ../includes/metabox.php:169 -#: ../includes/metabox.php:188 -msgid "file url" -msgstr "url del archivo" - -#: ../includes/metabox.php:171 -msgid "All Prices" -msgstr "Todos los Precios" - -#: ../includes/metabox.php:178 -#: ../includes/metabox.php:189 -msgid "Upload File" -msgstr "Subir Archivo" - -#: ../includes/metabox.php:192 -#: ../includes/post-types.php:43 -#: ../includes/post-types.php:81 -msgid "Add New" -msgstr "Agregar Nuevo" - -#: ../includes/metabox.php:192 -msgid "Upload the downloadable files." -msgstr "Subir las descargas." - -#: ../includes/metabox.php:213 -msgid "Purchase Text" -msgstr "Texto de Compra" - -#: ../includes/metabox.php:215 -msgid "Add the text you would like displayed for the purchase text" -msgstr "Ingrese el texto a mostrar para la compra" - -#: ../includes/metabox.php:234 -msgid "Link Style" -msgstr "Estilo del Enlace" - -#: ../includes/metabox.php:236 -msgid "Button" -msgstr "Boton" - -#: ../includes/metabox.php:237 -msgid "Text" -msgstr "Texto" - -#: ../includes/metabox.php:238 -msgid "Choose the style of the purchase link" -msgstr "Elija el estilo del enlace de compra" - -#: ../includes/metabox.php:259 -msgid "Button Color" -msgstr "Color del Boton" - -#: ../includes/metabox.php:267 -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "Elija el color del enlace de compra, si se selecciono la opcion de boton." - -#: ../includes/metabox.php:285 -msgid "Disable the purchase button?" -msgstr "Mostrar el boton de compra?" - -#: ../includes/metabox.php:288 -msgid "Check this if you do not want the purchase button displayed." -msgstr "Seleccionar si no quiere que se muestre el boton de descarga." - -#: ../includes/metabox.php:306 -msgid "Notes" -msgstr "Notas" - -#: ../includes/metabox.php:309 -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "Las opciones de estilo NO reflejan el estilo del shortcode. El shortcode le permite ubicar un boton de compra para esta descarga en cualquier lugar de su sitio." - -#: ../includes/metabox.php:315 -msgid "This short code can be placed anywhere on your site" -msgstr "Puede utilizar este shortcode en cualquier lugar de su sitio." - -#: ../includes/metabox.php:402 -msgid "Sales:" -msgstr "Ventas:" - -#: ../includes/metabox.php:408 -msgid "Earnings:" -msgstr "Ganancias:" - -#: ../includes/metabox.php:444 -msgid "Sales Log" -msgstr "Log de Ventas" - -#: ../includes/metabox.php:446 -msgid "Each sale for this download is listed below." -msgstr "Cada venta para esta descarga esta listada abajo." - -#: ../includes/metabox.php:460 -#: ../includes/metabox.php:552 -msgid "Date:" -msgstr "Fecha:" - -#: ../includes/metabox.php:464 -msgid "Buyer:" -msgstr "Comprador:" - -#: ../includes/metabox.php:468 -msgid "Purchase ID:" -msgstr "ID Compra:" - -#: ../includes/metabox.php:476 -msgid "No sales yet" -msgstr "No se ha realizado ninguna venta" - -#: ../includes/metabox.php:492 -#: ../includes/metabox.php:589 -msgid "Previous" -msgstr "Anterior" - -#: ../includes/metabox.php:533 -msgid "Download Log" -msgstr "Registro de Descargas" - -#: ../includes/metabox.php:535 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "Cada vez que un archivo es descargado se registra a continuacion." - -#: ../includes/metabox.php:556 -msgid "Downloaded by:" -msgstr "Descargado por:" - -#: ../includes/metabox.php:560 -msgid "IP Address:" -msgstr "Dirección IP:" - -#: ../includes/metabox.php:564 -msgid "File: " -msgstr "Archivo:" - -#: ../includes/metabox.php:573 -msgid "No file downloads yet yet" -msgstr "No hay archivos para descargar" - -#: ../includes/misc-functions.php:185 -msgid "US Dollars ($)" -msgstr "Dólares USA ($)" - -#: ../includes/misc-functions.php:186 -msgid "Euros (€)" -msgstr "Euros (€)" - -#: ../includes/misc-functions.php:187 -msgid "Pounds Sterling (£)" -msgstr "Libras Esterlinas (£)" - -#: ../includes/misc-functions.php:188 -msgid "Australian Dollars ($)" -msgstr "Dolar Australiano ($)" - -#: ../includes/misc-functions.php:189 -msgid "Brazilian Real ($)" -msgstr "Real Brazilero ($)" - -#: ../includes/misc-functions.php:190 -msgid "Canadian Dollars ($)" -msgstr "Dolar Canadiense ($)" - -#: ../includes/misc-functions.php:191 -msgid "Czech Koruna" -msgstr "Koruna Checa" - -#: ../includes/misc-functions.php:192 -msgid "Danish Krone" -msgstr "Krone Danes " - -#: ../includes/misc-functions.php:193 -msgid "Hong Kong Dollar ($)" -msgstr "Dolar Hong Kong ($)" - -#: ../includes/misc-functions.php:194 -msgid "Hungarian Forint" -msgstr "Forint Hungaro" - -#: ../includes/misc-functions.php:195 -msgid "Israeli Shekel" -msgstr "Shekel Israeli" - -#: ../includes/misc-functions.php:196 -msgid "Japanese Yen (¥)" -msgstr "Yen Japones (¥)" - -#: ../includes/misc-functions.php:197 -msgid "Malaysian Ringgits" -msgstr "Ringgits Malasia" - -#: ../includes/misc-functions.php:198 -msgid "Mexican Peso ($)" -msgstr "Peso Mejicano ($)" - -#: ../includes/misc-functions.php:199 -msgid "New Zealand Dollar ($)" -msgstr "Dolar Neozelandés ($)" - -#: ../includes/misc-functions.php:200 -msgid "Norwegian Krone" -msgstr "Krone Noruego" - -#: ../includes/misc-functions.php:201 -msgid "Philippine Pesos" -msgstr "Pesos Filipinos" - -#: ../includes/misc-functions.php:202 -msgid "Polish Zloty" -msgstr "Zloty Polacos" - -#: ../includes/misc-functions.php:203 -msgid "Singapore Dollar ($)" -msgstr "Dolar Singapur ($)" - -#: ../includes/misc-functions.php:204 -msgid "Swedish Krona" -msgstr "Krona Sueco" - -#: ../includes/misc-functions.php:205 -msgid "Swiss Franc" -msgstr "Franco Suizo" - -#: ../includes/misc-functions.php:206 -msgid "Taiwan New Dollars" -msgstr "Nuevos Dolares Taiwain" - -#: ../includes/misc-functions.php:207 -msgid "Thai Baht" -msgstr "Bath Thailandes" - -#: ../includes/misc-functions.php:208 -msgid "Indian Rupee" -msgstr "Rupia India" - -#: ../includes/misc-functions.php:209 -msgid "Turkish Lira" -msgstr "Lira Turca" - -#: ../includes/payment-functions.php:259 -msgid "Pending" -msgstr "Pendiente" - -#: ../includes/payment-functions.php:260 -msgid "Complete" -msgstr "Completo" - -#: ../includes/payment-functions.php:261 -msgid "Refunded" -msgstr "Reintegrado" - -#: ../includes/post-types.php:44 -#, php-format -msgid "Add New %1$s" -msgstr "Agregar Nuevo %1$s" - -#: ../includes/post-types.php:45 -#, php-format -msgid "Edit %1$s" -msgstr "Editar %1$s" - -#: ../includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "Nuevo %1$s" - -#: ../includes/post-types.php:47 -#, php-format -msgid "All %2$s" -msgstr "Todas las %2$s" - -#: ../includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "Ver %1$s" - -#: ../includes/post-types.php:49 -#, php-format -msgid "Search %2$s" -msgstr "Buscar %2$s" - -#: ../includes/post-types.php:50 -#, php-format -msgid "No %2$s found" -msgstr "No se encontraron %2$s" - -#: ../includes/post-types.php:51 -#, php-format -msgid "No %2$s found in Trash" -msgstr "No se encontraron %2$s en la papelera" - -#: ../includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "%2$s" - -#: ../includes/post-types.php:79 -msgid "Payments" -msgstr "Pagos" - -#: ../includes/post-types.php:80 -msgid "Payment" -msgstr "Pago" - -#: ../includes/post-types.php:82 -msgid "Add New Payment" -msgstr "Agregar Pago" - -#: ../includes/post-types.php:83 -msgid "Edit Payment" -msgstr "Editar Pago" - -#: ../includes/post-types.php:84 -msgid "New Payment" -msgstr "Nuevo Pago" - -#: ../includes/post-types.php:85 -msgid "All Payments" -msgstr "Todos los Pagos" - -#: ../includes/post-types.php:86 -msgid "View Payment" -msgstr "Ver Pago" - -#: ../includes/post-types.php:87 -msgid "Search Payments" -msgstr "Buscar Pagos" - -#: ../includes/post-types.php:88 -msgid "No Payments found" -msgstr "No se encontraron pagos" - -#: ../includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "No se encontraron pagos en la papelera" - -#: ../includes/post-types.php:125 -msgid "Downloads" -msgstr "Descargas" - -#: ../includes/post-types.php:173 -msgid "Category" -msgstr "Categoria" - -#: ../includes/post-types.php:174 -msgid "Search Categories" -msgstr "Buscar Categorias" - -#: ../includes/post-types.php:175 -msgid "All Categories" -msgstr "Todas las Categorias" - -#: ../includes/post-types.php:176 -msgid "Parent Category" -msgstr "CAtegoria Superior" - -#: ../includes/post-types.php:177 -msgid "Parent Category:" -msgstr "Categoria Superior:" - -#: ../includes/post-types.php:178 -msgid "Edit Category" -msgstr "Editar Categoria" - -#: ../includes/post-types.php:179 -msgid "Update Category" -msgstr "Actualizar Categoría" - -#: ../includes/post-types.php:180 -msgid "Add New Category" -msgstr "Agregar Nueva Categoría" - -#: ../includes/post-types.php:181 -msgid "New Category Name" -msgstr "Nombre de la Nueva Categoría" - -#: ../includes/post-types.php:195 -msgid "Tag" -msgstr "Etiqueta" - -#: ../includes/post-types.php:196 -msgid "Search Tags" -msgstr "Buscar Etiquetas" - -#: ../includes/post-types.php:197 -msgid "All Tags" -msgstr "Todas las Etiquetas" - -#: ../includes/post-types.php:198 -msgid "Parent Tag" -msgstr "Etiqueta Superior" - -#: ../includes/post-types.php:199 -msgid "Parent Tag:" -msgstr "Etiqueta Superior:" - -#: ../includes/post-types.php:200 -msgid "Edit Tag" -msgstr "Editar Etiqeta" - -#: ../includes/post-types.php:201 -msgid "Update Tag" -msgstr "Actualizar Etiqueta" - -#: ../includes/post-types.php:202 -msgid "Add New Tag" -msgstr "Agregar Nueva Etiqueta" - -#: ../includes/post-types.php:203 -msgid "New Tag Name" -msgstr "Nombre Nueva Etiqueta" - -#: ../includes/post-types.php:232 -#: ../includes/post-types.php:233 -msgid "Download updated." -msgstr "Descarga actualizada." - -#: ../includes/post-types.php:234 -msgid "Download published." -msgstr "Descarga publicada." - -#: ../includes/post-types.php:235 -msgid "Download saved." -msgstr "Descarga Guardada." - -#: ../includes/post-types.php:236 -msgid "Download submitted." -msgstr "Descarga Enviada." - -#: ../includes/process-download.php:96 -msgid "You do not have permission to download this file" -msgstr "No tiene los permisos suficientes para descargar este archivo" - -#: ../includes/process-download.php:96 -msgid "Purchase Verification Failed" -msgstr "Fallo la Verificacion de la Compra" - -#: ../includes/process-purchase.php:193 -msgid "The selected gateway is not active" -msgstr "la pasarela de pagos seleccionada no esta activa" - -#: ../includes/process-purchase.php:197 -msgid "No gateway has been selected" -msgstr "No se ha seleccionado pasarela de pagos" - -#: ../includes/process-purchase.php:245 -msgid "You must agree to the terms of use" -msgstr "Debe aceptar los terminos de uso" - -#: ../includes/process-purchase.php:275 -msgid "Please enter a valid email address." -msgstr "Por favor ingrese una casilla de email valida." - -#: ../includes/process-purchase.php:289 -msgid "The user information is invalid." -msgstr "La información del usuario no es valida." - -#: ../includes/process-purchase.php:335 -msgid "Username already taken" -msgstr "El nombre de usuario ya esta tomado" - -#: ../includes/process-purchase.php:341 -msgid "Invalid username" -msgstr "Nombre de usuario invalido" - -#: ../includes/process-purchase.php:352 -msgid "You must register or login to complete your purchase" -msgstr "Para completar su compra debe ingresar o registrarce" - -#: ../includes/process-purchase.php:364 -#: ../includes/process-purchase.php:508 -msgid "Invalid email" -msgstr "email invalido" - -#: ../includes/process-purchase.php:370 -msgid "Email already used" -msgstr "La casilla de email ya se encuentra en uso" - -#: ../includes/process-purchase.php:382 -#: ../includes/process-purchase.php:515 -msgid "Enter an email" -msgstr "Ingrese un email" - -#: ../includes/process-purchase.php:393 -msgid "Passwords don't match" -msgstr "Las contraseñas no concuerdan" - -#: ../includes/process-purchase.php:408 -#: ../includes/process-purchase.php:473 -msgid "Enter a password" -msgstr "Ingrese una contraseña" - -#: ../includes/process-purchase.php:413 -msgid "Enter the password confirmation" -msgstr "Ingrese la confirmación de la contraseña" - -#: ../includes/process-purchase.php:440 -msgid "You must login or register to complete your purchase" -msgstr "Para completar su compra debe ingresar o registrarce" - -#: ../includes/register-settings.php:41 -msgid "Test Mode" -msgstr "Modo de prueba" - -#: ../includes/register-settings.php:42 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "El modo de prueba no procesa las transacciones. Para utilizar el modo de prueba debe tener una cuenta sandbox para la ouerta de pago que este probando." - -#: ../includes/register-settings.php:47 -msgid "Purchase Page" -msgstr "Pagina de Compra" - -#: ../includes/register-settings.php:48 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "Esta es la pagina donde los compradores completaran su compra" - -#: ../includes/register-settings.php:54 -msgid "Success Page" -msgstr "Compra de Exito" - -#: ../includes/register-settings.php:55 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "Esta es la pagina donde se envia a los compradores luego de completar una compra" - -#: ../includes/register-settings.php:61 -msgid "Currency Settings" -msgstr "Ajustes de Moneda" - -#: ../includes/register-settings.php:62 -msgid "Configure the currency options" -msgstr "Configure los ajustes de moneda" - -#: ../includes/register-settings.php:67 -msgid "Currency" -msgstr "Moneda" - -#: ../includes/register-settings.php:68 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "Elija su moneda. Tenga en cuenta que algunos gateways tienen restricciones de moneda." - -#: ../includes/register-settings.php:74 -msgid "Currency Position" -msgstr "Posicion de la Moneda" - -#: ../includes/register-settings.php:75 -msgid "Choose the location of the currency sign." -msgstr "Elija la locacion del simbolo de la moneda." - -#: ../includes/register-settings.php:78 -msgid "Before - $10" -msgstr "Antes - $10" - -#: ../includes/register-settings.php:79 -msgid "After - 10$" -msgstr "Despues - 10$" - -#: ../includes/register-settings.php:84 -msgid "Thousands Separator" -msgstr "Separador de Miles" - -#: ../includes/register-settings.php:85 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "El simbolo ( generalmente , o . ) para separa los miles" - -#: ../includes/register-settings.php:92 -msgid "Decimal Separator" -msgstr "Separador de Decimales" - -#: ../includes/register-settings.php:93 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "El simbolo ( generalmente , o . ) para separa los decimales" - -#: ../includes/register-settings.php:104 -#: ../includes/admin-pages/settings.php:34 -msgid "Payment Gateways" -msgstr "Gateways de Pago" - -#: ../includes/register-settings.php:105 -msgid "Choose the payment gateways you want to enable." -msgstr "Elija la pasarela de pago que quiere activar." - -#: ../includes/register-settings.php:111 -msgid "Accepted Payment Method Icons" -msgstr "Iconos de los Metodos de Pago aceptados" - -#: ../includes/register-settings.php:112 -msgid "Display icons for the selected payment methods" -msgstr "Mostrar iconos para los medios de pago seleccionados" - -#: ../includes/register-settings.php:112 -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "Tambien deberá configurar los ajustes de la pasarela de pagos si elige aceptar tarjeta de credito" - -#: ../includes/register-settings.php:125 -msgid "PayPal Settings" -msgstr "Ajustes de PayPal" - -#: ../includes/register-settings.php:126 -msgid "Configure the PayPal settings" -msgstr "Configure los ajustes de PayPal" - -#: ../includes/register-settings.php:131 -msgid "PayPal Email" -msgstr "PayPal Email" - -#: ../includes/register-settings.php:132 -msgid "Enter your PayPal account's email" -msgstr "Ingrese el email de su cuenta de PayPal" - -#: ../includes/register-settings.php:138 -msgid "Alternate PayPal Purchase Verification" -msgstr "Alternar la Verificacion de Compra de PayPal" - -#: ../includes/register-settings.php:139 -msgid "If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal." -msgstr "Si los pagos no estan siendo marcados como completos marque esta opcion. Nota: requiere que los compradores regresen de PayPal a su sitio." - -#: ../includes/register-settings.php:144 -msgid "Disable PayPal IPN Verification" -msgstr "Desactivar la Verificacion de Compra de PayPal" - -#: ../includes/register-settings.php:145 -msgid "If payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifiyin purchases." -msgstr "Si los pagos no estan siendo marcados como completos marque esta opcion. Esto fuerza al sitio a utilizar un metodo de verificacion de compra menos seguro." - -#: ../includes/register-settings.php:154 -msgid "Email Template" -msgstr "Plantilla del Email" - -#: ../includes/register-settings.php:155 -msgid "Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" to see the new template." -msgstr "Elija una plantilla. Grabe los cambios y seleccione Previsualizar El Recibo de Compra para ver la nueva plantilla." - -#: ../includes/register-settings.php:167 -msgid "From Name" -msgstr "Nombre De" - -#: ../includes/register-settings.php:168 -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "El nombre de donde vendran los recibos. Probablemente el nombre de su sitio o tienda." - -#: ../includes/register-settings.php:173 -msgid "From Email" -msgstr "Email Envio" - -#: ../includes/register-settings.php:174 -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "Email que envia los recibos. Actua como direccion de \"from\" y \"reply-to\" ." - -#: ../includes/register-settings.php:179 -msgid "Purchase Email Subject" -msgstr "Titulo del Recibo de Compra" - -#: ../includes/register-settings.php:180 -msgid "Enter the subject line for the purchase receipt email" -msgstr "Ingrese el titulo para el recibo de compra enviado por email" - -#: ../includes/register-settings.php:185 -msgid "Purchase Receipt" -msgstr "Recibo de Compra" - -#: ../includes/register-settings.php:186 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "Ingrese el mail que es enviado a los usuarios despues de completar una compra exitosamente. Acepta HTML. Etiquetas de plantilla disponibles:" - -#: ../includes/register-settings.php:187 -msgid "A list of download URLs for each download purchased" -msgstr "Una lista de las URLs de descargar para cada producto comprado" - -#: ../includes/register-settings.php:188 -msgid "The buyer's name" -msgstr "El nombre del comprador" - -#: ../includes/register-settings.php:189 -msgid "The date of the purchase" -msgstr "La fecha de la compra" - -#: ../includes/register-settings.php:190 -msgid "The total price of the purchase" -msgstr "El precio total de la compra" - -#: ../includes/register-settings.php:191 -msgid "The unique ID number for this purchase receipt" -msgstr "El ID numerico unico para este recibo de compra" - -#: ../includes/register-settings.php:192 -msgid "The method of payment used for this purchase" -msgstr "El metodo de pago usado para esta compra" - -#: ../includes/register-settings.php:193 -msgid "Your site name" -msgstr "El nombre de su sitio" - -#: ../includes/register-settings.php:202 -msgid "Disable Styles" -msgstr "Desactivar Estilos" - -#: ../includes/register-settings.php:203 -msgid "Check this to disable all included styling" -msgstr "Seleccione para desactivar todos los estilos incluidos" - -#: ../includes/register-settings.php:208 -msgid "Checkout Button Color" -msgstr "Color del Boton de la Caja" - -#: ../includes/register-settings.php:209 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "Seleccione el color de boton deseado para la for botones de caja" - -#: ../includes/register-settings.php:219 -msgid "Disable Ajax" -msgstr "Desactivar AJAX" - -#: ../includes/register-settings.php:220 -msgid "Check this to disable AJAX for the shopping cart." -msgstr "Seleccione para desactivar AJAX para el carro de compras." - -#: ../includes/register-settings.php:225 -msgid "Enable jQuery Validation" -msgstr "Activar Validacion con jQuery" - -#: ../includes/register-settings.php:226 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "Seleccione para activar validacion de jQuery en el formulario de caja." - -#: ../includes/register-settings.php:231 -msgid "Disable Guest Checkout" -msgstr "Descativar la compra para invidatos" - -#: ../includes/register-settings.php:232 -msgid "Require that users be logged-in to purchase files." -msgstr "Requerir que los usuarios hallan ingresado a su cuenta para realizar una comra." - -#: ../includes/register-settings.php:237 -msgid "Show Register / Login Form?" -msgstr "Mostrar formulario de Registro / Ingreso " - -#: ../includes/register-settings.php:238 -msgid "Display the registration and login forms on the checkout page for non-logged-in users." -msgstr "Mostrar los formularios de registro e ingreso en la pagina de Caja para los usuarios que no hallan ingresado al sistema." - -#: ../includes/register-settings.php:243 -msgid "Download Link Expiration" -msgstr "Expiracion del enlace de Descarga" - -#: ../includes/register-settings.php:244 -msgid "How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours." -msgstr "Cuan largo deben permanecer validos los enlaces de descarga? Por defecto son validos por 24hs. Ingrese el tiempo en horas." - -#: ../includes/register-settings.php:250 -msgid "Disable Redownload?" -msgstr "Desactivar Volver a Descargar" - -#: ../includes/register-settings.php:251 -msgid "Check this if you do not want to allow users to redownload items from their purchase history." -msgstr "Seleccione si no quiere permitir a los usuarios descargar nuevamente los items desde el historial de compras/" - -#: ../includes/register-settings.php:256 -msgid "Terms of Agreement" -msgstr "Terminos de Acuerdo" - -#: ../includes/register-settings.php:262 -msgid "Agree to Terms" -msgstr "Acepte los Términos" - -#: ../includes/register-settings.php:263 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing." -msgstr "Seleccione para mostrar un Aceptar Términos requerido para los compradores en el checkout." - -#: ../includes/register-settings.php:268 -msgid "Agree to Terms Label" -msgstr "Etiqueta de Aceptar Terminos" - -#: ../includes/register-settings.php:269 -msgid "Label shown next to the agree to terms check box." -msgstr "Etiqueta mostrada al lado del chekbox de Aceptar Terminos" - -#: ../includes/register-settings.php:275 -msgid "Agreement Text" -msgstr "Condiciones de Acuerdo" - -#: ../includes/register-settings.php:276 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "Si Aceptar los Terminos esta seleccionado, ingrese los terminos a aceptar." - -#: ../includes/register-settings.php:302 -msgid "General Settings" -msgstr "Ajustes Generales" - -#: ../includes/register-settings.php:328 -msgid "Payment Gateway Settings" -msgstr "Ajustes de la Pasarela de Pago" - -#: ../includes/register-settings.php:354 -msgid "Email Settings" -msgstr "Ajustes de Email" - -#: ../includes/register-settings.php:380 -msgid "Style Settings" -msgstr "Ajustes de estilo" - -#: ../includes/register-settings.php:407 -msgid "Misc Settings" -msgstr "Ajustes Micelanoes" - -#: ../includes/register-settings.php:734 -msgid "Settings Updated" -msgstr "Ajustes Actualizados" - -#: ../includes/scripts.php:40 -msgid "Please enter a discount code" -msgstr "Por favor ingrese un codigo de descuento" - -#: ../includes/scripts.php:41 -msgid "Discount Applied" -msgstr "Descuento Aplicado" - -#: ../includes/scripts.php:43 -msgid "You have already added this item to your cart" -msgstr "Ya tiene este item en su carro" - -#: ../includes/scripts.php:44 -msgid "Your cart is empty" -msgstr "Su carrito esta vacio" - -#: ../includes/scripts.php:45 -msgid "Loading" -msgstr "Cargando" - -#: ../includes/scripts.php:112 -msgid "Add New Download" -msgstr "Agregar Nueva Descarga" - -#: ../includes/scripts.php:113 -msgid "Use This File" -msgstr "Usar Este Archivo" - -#: ../includes/scripts.php:114 -msgid "Are you sure you wish to delete this payment?" -msgstr "Esta seguro que quiere eliminar este pago?" - -#: ../includes/shortcodes.php:63 -msgid "Download Name" -msgstr "Nombre de Descarga" - -#: ../includes/shortcodes.php:65 -#: ../includes/shortcodes.php:152 -msgid "Files" -msgstr "Archivos" - -#: ../includes/shortcodes.php:103 -#: ../includes/shortcodes.php:180 -msgid "No downloadable files found." -msgstr "No se han encontrado archivos para descarga." - -#: ../includes/shortcodes.php:119 -msgid "You have not purchased any downloads" -msgstr "No ha comprado niguna descarga" - -#: ../includes/shortcodes.php:149 -msgid "Purchase ID" -msgstr "ID Compra" - -#: ../includes/shortcodes.php:151 -#: ../includes/admin-pages/discount-codes.php:42 -#: ../includes/admin-pages/discount-codes.php:56 -#: ../includes/admin-pages/forms/add-discount.php:48 -#: ../includes/admin-pages/forms/edit-discount.php:53 -msgid "Amount" -msgstr "Monto" - -#: ../includes/shortcodes.php:193 -msgid "You have not made any purchases" -msgstr "No ha realizado ninguna compra" - -#: ../includes/shortcodes.php:344 -msgid "Add to Cart" -msgstr "Agregar al Carro" - -#: ../includes/shortcodes.php:437 -msgid "No downloads found" -msgstr "No se encontraron descargas" - -#: ../includes/template-functions.php:124 -msgid "added to your cart" -msgstr "agregado a su carrito" - -#: ../includes/template-functions.php:214 -msgid "Gray" -msgstr "Gris" - -#: ../includes/template-functions.php:215 -msgid "Pink" -msgstr "Rosa" - -#: ../includes/template-functions.php:216 -msgid "Blue" -msgstr "Azul" - -#: ../includes/template-functions.php:217 -msgid "Green" -msgstr "Verde" - -#: ../includes/template-functions.php:218 -msgid "Teal" -msgstr "Turqueza" - -#: ../includes/template-functions.php:219 -msgid "Black" -msgstr "Negro" - -#: ../includes/template-functions.php:220 -msgid "Dark Gray" -msgstr "Gris Oscuro" - -#: ../includes/template-functions.php:221 -msgid "Orange" -msgstr "Naranja" - -#: ../includes/template-functions.php:222 -msgid "Purple" -msgstr "Violeta" - -#: ../includes/template-functions.php:223 -msgid "Slate" -msgstr "Granito" - -#: ../includes/template-functions.php:242 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "Usted ya ha comprado este item, pero puede comprarlo nuevamente." - -#: ../includes/thickbox.php:29 -#: ../includes/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "Insert %s" - -#: ../includes/thickbox.php:30 -msgid "Insert Download" -msgstr "Ingresar Descarga" - -#: ../includes/thickbox.php:65 -msgid "You must choose a download" -msgstr "Debe seleccionar una descarga" - -#: ../includes/thickbox.php:88 -#, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "Utilize el formulario a continuacion para ingresar el shortcode para la compra de una %s" - -#: ../includes/thickbox.php:91 -#, php-format -msgid "Choose a %s" -msgstr "Seleccione un %s" - -#: ../includes/thickbox.php:100 -msgid "Choose a style" -msgstr "Seleccione un estilo" - -#: ../includes/thickbox.php:111 -msgid "Choose a button color" -msgstr "Seleccione un color para el boton" - -#: ../includes/thickbox.php:120 -msgid "Link text . . ." -msgstr "Enlace de texto..." - -#: ../includes/thickbox.php:124 -msgid "Cancel" -msgstr "Cancelar" - -#: ../includes/thickbox.php:126 -msgid "Button Styles" -msgstr "Estilo de Botones" - -#: ../includes/widgets.php:38 -msgid "Downloads Cart" -msgstr "Carro de Descargas" - -#: ../includes/widgets.php:38 -msgid "Display the downloads shopping cart" -msgstr "Mostrar el carro de descargas" - -#: ../includes/widgets.php:81 -#: ../includes/widgets.php:161 -msgid "Title:" -msgstr "Titulo:" - -#: ../includes/widgets.php:86 -msgid "Show Quantity:" -msgstr "Mostrar Cantidad:" - -#: ../includes/widgets.php:111 -msgid "Downloads Categories / Tags" -msgstr "Categorias y Etiquetas de Descargas" - -#: ../includes/widgets.php:111 -msgid "Display the downloads categories or tags" -msgstr "Muestra categorias o etiquetas de descargas" - -#: ../includes/widgets.php:166 -msgid "Taxonomy:" -msgstr "Taxonomia:" - -#: ../includes/admin-pages/add-ons.php:69 -msgid "Add Ons for Easy Digital Downloads" -msgstr "Extenciones para Easy Digital Download" - -#: ../includes/admin-pages/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "Estos agregandos extienden la funcionalidad de Easy Digital Downloads." - -#: ../includes/admin-pages/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "Ver todas las extensiones" - -#: ../includes/admin-pages/discount-codes.php:40 -#: ../includes/admin-pages/discount-codes.php:54 -#: ../includes/admin-pages/forms/add-discount.php:27 -#: ../includes/admin-pages/forms/edit-discount.php:32 -msgid "Code" -msgstr "Codigo" - -#: ../includes/admin-pages/discount-codes.php:41 -#: ../includes/admin-pages/discount-codes.php:55 -#: ../includes/admin-pages/forms/add-discount.php:36 -#: ../includes/admin-pages/forms/edit-discount.php:41 -msgid "Type" -msgstr "Tipo" - -#: ../includes/admin-pages/discount-codes.php:43 -#: ../includes/admin-pages/discount-codes.php:57 -msgid "Uses" -msgstr "Usos" - -#: ../includes/admin-pages/discount-codes.php:44 -#: ../includes/admin-pages/discount-codes.php:58 -#: ../includes/admin-pages/forms/add-discount.php:75 -#: ../includes/admin-pages/forms/edit-discount.php:80 -msgid "Max Uses" -msgstr "Limite Usos" - -#: ../includes/admin-pages/discount-codes.php:45 -#: ../includes/admin-pages/discount-codes.php:59 -msgid "Start Date" -msgstr "Fecha Inicio" - -#: ../includes/admin-pages/discount-codes.php:46 -#: ../includes/admin-pages/discount-codes.php:60 -msgid "Expiration" -msgstr "Expiracion" - -#: ../includes/admin-pages/discount-codes.php:48 -#: ../includes/admin-pages/discount-codes.php:62 -msgid "Actions" -msgstr "Acciones" - -#: ../includes/admin-pages/discount-codes.php:85 -#: ../includes/admin-pages/discount-codes.php:87 -msgid "unlimited" -msgstr "inlimitado" - -#: ../includes/admin-pages/discount-codes.php:96 -msgid "No start date" -msgstr "Sin fecha de Inicio" - -#: ../includes/admin-pages/discount-codes.php:103 -msgid "Expired" -msgstr "Expirado" - -#: ../includes/admin-pages/discount-codes.php:105 -msgid "no expiration" -msgstr "sin expiracion" - -#: ../includes/admin-pages/discount-codes.php:111 -#: ../includes/admin-pages/payments-history.php:176 -msgid "Edit" -msgstr "Editar" - -#: ../includes/admin-pages/discount-codes.php:113 -msgid "Deactivate" -msgstr "Deactivar" - -#: ../includes/admin-pages/discount-codes.php:115 -msgid "Activate" -msgstr "Activar" - -#: ../includes/admin-pages/discount-codes.php:117 -#: ../includes/admin-pages/payments-history.php:178 -msgid "Delete" -msgstr "Borrar" - -#: ../includes/admin-pages/discount-codes.php:122 -msgid "No discount codes have been created." -msgstr "No se han creado codigos de descuento." - -#: ../includes/admin-pages/payments-history.php:82 -msgid "All" -msgstr "Todos" - -#: ../includes/admin-pages/payments-history.php:87 -msgid "Completed" -msgstr "Completado" - -#: ../includes/admin-pages/payments-history.php:96 -msgid "Deleted" -msgstr "Borrado" - -#: ../includes/admin-pages/payments-history.php:100 -msgid "Export" -msgstr "Exportar" - -#: ../includes/admin-pages/payments-history.php:103 -msgid "Payment mode" -msgstr "Modo de pago" - -#: ../includes/admin-pages/payments-history.php:105 -msgid "Live" -msgstr "Vivo" - -#: ../includes/admin-pages/payments-history.php:106 -msgid "Test" -msgstr "Prueba" - -#: ../includes/admin-pages/payments-history.php:116 -msgid "Payments per page" -msgstr "Pagos por pagina" - -#: ../includes/admin-pages/payments-history.php:118 -msgid "Show" -msgstr "Mostrar" - -#: ../includes/admin-pages/payments-history.php:124 -msgid "Showing payments for: " -msgstr "Mostrar pagos por:" - -#: ../includes/admin-pages/payments-history.php:124 -msgid "clear" -msgstr "borrar" - -#: ../includes/admin-pages/payments-history.php:177 -msgid "Resend Purchase Receipt" -msgstr "Reenviar el Recibo de Compra" - -#: ../includes/admin-pages/payments-history.php:190 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "Detalles de Pago de la Compra #%s" - -#: ../includes/admin-pages/payments-history.php:190 -msgid "View Order Details" -msgstr "Ver los Detalles de la Orden" - -#: ../includes/admin-pages/payments-history.php:198 -msgid "Purchased File" -msgstr "Archivo Comprado" - -#: ../includes/admin-pages/payments-history.php:198 -msgid "Purchased Files" -msgstr "Archivos Comprados" - -#: ../includes/admin-pages/payments-history.php:241 -msgid "Discount used:" -msgstr "Descuento utilizado:" - -#: ../includes/admin-pages/payments-history.php:242 -msgid "Total:" -msgstr "Total:" - -#: ../includes/admin-pages/payments-history.php:245 -msgid "Buyer's Personal Details:" -msgstr "Detalles Personales del Comprador:" - -#: ../includes/admin-pages/payments-history.php:247 -msgid "Name:" -msgstr "Nombre:" - -#: ../includes/admin-pages/payments-history.php:248 -msgid "Email:" -msgstr "Email:" - -#: ../includes/admin-pages/payments-history.php:257 -msgid "Payment Method:" -msgstr "Metodo de pago:" - -#: ../includes/admin-pages/payments-history.php:262 -msgid "Purchase Key" -msgstr "Identificacion Compra" - -#: ../includes/admin-pages/reports.php:34 -msgid "Transactions created while in test mode are not included on this page." -msgstr "Las transacciones creadas en modo de prueba no son incluidas en esta pagina." - -#: ../includes/admin-pages/settings.php:33 -msgid "General" -msgstr "General" - -#: ../includes/admin-pages/settings.php:35 -msgid "Emails" -msgstr "Emails" - -#: ../includes/admin-pages/settings.php:36 -msgid "Styles" -msgstr "Estilos" - -#: ../includes/admin-pages/settings.php:37 -msgid "Misc" -msgstr "Misc" - -#: ../includes/admin-pages/forms/add-discount.php:12 -msgid "Add New Discount" -msgstr "Agregar Nuevo Descuento" - -#: ../includes/admin-pages/forms/add-discount.php:22 -#: ../includes/admin-pages/forms/edit-discount.php:27 -msgid "The name of this discount" -msgstr "El nombre de este descuento" - -#: ../includes/admin-pages/forms/add-discount.php:31 -#: ../includes/admin-pages/forms/edit-discount.php:36 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "Ingrese un codigo para este descuento, como 10PORCIENTO" - -#: ../includes/admin-pages/forms/add-discount.php:40 -#: ../includes/admin-pages/forms/edit-discount.php:45 -msgid "Percentage" -msgstr "Porcentage" - -#: ../includes/admin-pages/forms/add-discount.php:41 -#: ../includes/admin-pages/forms/edit-discount.php:46 -msgid "Flat amount" -msgstr "Monto fijo" - -#: ../includes/admin-pages/forms/add-discount.php:43 -#: ../includes/admin-pages/forms/edit-discount.php:48 -msgid "The kind of discount to apply for this discount." -msgstr "El tipo de descuento que se aplica para este código." - -#: ../includes/admin-pages/forms/add-discount.php:52 -#: ../includes/admin-pages/forms/edit-discount.php:57 -msgid "The amount of this discount code." -msgstr "El monto de descuento para este codigo." - -#: ../includes/admin-pages/forms/add-discount.php:57 -#: ../includes/admin-pages/forms/edit-discount.php:62 -msgid "Start date" -msgstr "Fecha Inicio" - -#: ../includes/admin-pages/forms/add-discount.php:61 -#: ../includes/admin-pages/forms/edit-discount.php:66 -msgid "Enter the start date for this discount code in the format of yyyy-mm-dd. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "Ingrese la fecha de inicio para este código de descuento en el formato aaaa-mm-dd. Dejar en blanco para utilizar sin fecha de inicio. Si se ingresa una fecha el descuento solo puede ser utilizado a partir de esta fecha." - -#: ../includes/admin-pages/forms/add-discount.php:66 -#: ../includes/admin-pages/forms/edit-discount.php:71 -msgid "Expiration date" -msgstr "Fecha de expiracion" - -#: ../includes/admin-pages/forms/add-discount.php:70 -#: ../includes/admin-pages/forms/edit-discount.php:75 -msgid "Enter the expiration date for this discount code in the format of yyyy-mm-dd. For no expiration, leave blank" -msgstr "Ingrese la fecha de expiracion para este codigo de ddescuento en el formato aaaa-mm-dd. Para no expiración, dejar en blanco." - -#: ../includes/admin-pages/forms/add-discount.php:79 -#: ../includes/admin-pages/forms/edit-discount.php:84 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "El máximo numero de veces que este código puede ser utilizado. Dejar en blanco para ilimitado." - -#: ../includes/admin-pages/forms/add-discount.php:87 -msgid "Add Discount Code" -msgstr "Agregar Codigo de Descuento" - -#: ../includes/admin-pages/forms/edit-discount.php:13 -msgid "Something went wrong." -msgstr "Algo salio mal." - -#: ../includes/admin-pages/forms/edit-discount.php:17 -msgid "Edit Discount" -msgstr "Editar Descuento" - -#: ../includes/admin-pages/forms/edit-discount.php:17 -msgid "Go Back" -msgstr "Volver" - -#: ../includes/admin-pages/forms/edit-discount.php:93 -msgid "Active" -msgstr "Activo" - -#: ../includes/admin-pages/forms/edit-discount.php:94 -msgid "Inactive" -msgstr "Inactivo" - -#: ../includes/admin-pages/forms/edit-discount.php:96 -msgid "The status of this discount code." -msgstr "El estado de este codigo de descuento." - -#: ../includes/admin-pages/forms/edit-discount.php:106 -msgid "Update Discount Code" -msgstr "Actualizar Codigo de Descuento" - -#: ../includes/admin-pages/forms/edit-payment.php:22 -msgid "Buyer's Email" -msgstr "Email del Comprador" - -#: ../includes/admin-pages/forms/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "Si es requerido, puede actualizar el email del comprador aqui." - -#: ../includes/admin-pages/forms/edit-payment.php:31 -msgid "Downloads Purchased" -msgstr "Descargas Compradas" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "Agregar descarga a la compra #%s" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -msgid "Add download to purchase" -msgstr "Agregar descarga a la compra" - -#: ../includes/admin-pages/forms/edit-payment.php:48 -msgid "Payment Status" -msgstr "Estado del Pago" - -#: ../includes/admin-pages/forms/edit-payment.php:64 -msgid "Send Purchase Receipt" -msgstr "Enviar el Recibo de Compra" - -#: ../includes/admin-pages/forms/edit-payment.php:68 -msgid "Check this box to send the purchase receipt, including all download links." -msgstr "Enviar un recivo de compra, incluyendo los enlaces de descargas." - -#: ../includes/admin-pages/forms/edit-payment.php:78 -msgid "Update Payment" -msgstr "Actualizar Pago" - -#: ../includes/admin-pages/forms/edit-payment.php:91 -msgid "Add Selected Downloads" -msgstr "Agregar las Descargas Seleccionadas" - -#: ../includes/gateways/paypal.php:264 -msgid "Invalid IPN" -msgstr "IPN Invalido" - -#: ../includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "Nombre del item" - -#: ../includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "Precio del item" - -#: ../includes/templates/checkout_cart.php:47 -msgid "Total" -msgstr "Total" - -#~ msgid "Price: " -#~ msgstr "Precio:" - -#~ msgid "Manual Payment" -#~ msgstr "Pago Manual" - -#~ msgid "Download Stats" -#~ msgstr "Estadisticas de Descargas" - -#~ msgid "pending" -#~ msgstr "pendiente" - -#~ msgid "complete" -#~ msgstr "completo" - -#~ msgid "refunded" -#~ msgstr "reintegro" - -#~ msgid "Edit Download" -#~ msgstr "Editar Descarga" - -#~ msgid "New Download" -#~ msgstr "Nueva Descarga" - -#~ msgid "All Downloads" -#~ msgstr "Todas las Descarga" - -#~ msgid "View Download" -#~ msgstr "Ver Descarga" - -#~ msgid "Search Downloads" -#~ msgstr "Buscar Descarga" - -#~ msgid "No Downloads found" -#~ msgstr "No se han encontrado Descargas" - -#~ msgid "No Downloads found in Trash" -#~ msgstr "No se han encontrado Descargas en la papelera" - -#~ msgid "Disable cURL for PayPal IPN" -#~ msgstr "Desactivar cURL para PayPal IPN" - -#~ msgid "" -#~ "If payments are not getting marked as complete, and disabling cURL does " -#~ "not fix the problem, then check this box" -#~ msgstr "" -#~ "Si los pagos no se estan completando y desactivar cURL no lo soluciona, " -#~ "seleccione esta opcion" - -#~ msgid "Choose a download" -#~ msgstr "Seleccione una descarga" - -#~ msgid "Something has gone wrong, please try again" -#~ msgstr "Algo fallo, por favor trate nuevamente" - -#~ msgid "Short Code for this Download" -#~ msgstr "ShortCode para esta Descarga" - -#~ msgid "here" -#~ msgstr "aqui" - -#~ msgid "Click %s to purchase again." -#~ msgstr "Seleccione %s para comprar nuevamente." - -#~ msgid "Your shopping cart is empty" -#~ msgstr "Su carrito de compras esta vacio" - -#~ msgid "email@domain.com" -#~ msgstr "email@dominio.com" - -#~ msgid "Enter the download price" -#~ msgstr "Ingrese el precio de la descarga" - -#~ msgid "Use this plugin in test mode" -#~ msgstr "Usar este plugin en modo de prueba" diff --git a/languages/edd-fr_FR.mo b/languages/edd-fr_FR.mo deleted file mode 100644 index 60c2d7fec3b..00000000000 Binary files a/languages/edd-fr_FR.mo and /dev/null differ diff --git a/languages/edd-fr_FR.po b/languages/edd-fr_FR.po deleted file mode 100755 index 76ddb7b7922..00000000000 --- a/languages/edd-fr_FR.po +++ /dev/null @@ -1,2789 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads v1.1.5.2\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-26 13:25+0100\n" -"PO-Revision-Date: 2012-09-26 13:26+0100\n" -"Last-Translator: FxB \n" -"Language-Team: FxB \n" -"Language: fr_FR\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Poedit-KeywordsList: __;_e;esc_attr__;esc_attr_e;esc_html__;esc_html_e;_n;" -"_x;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_x:1,2c\n" -"X-Poedit-Basepath: ../\n" -"X-Textdomain-Support: yes\n" -"X-Generator: Poedit 1.5.3\n" -"X-Poedit-SearchPath-0: .\n" - -# @ edd -#: includes/ajax-functions.php:102 -msgid "This discount code has been used already" -msgstr "Ce code de promo a déja été utilisé" - -# @ edd -#: includes/ajax-functions.php:118 includes/process-purchase.php:239 -msgid "The discount you entered is invalid" -msgstr "La remise que vous avez encodé est invalide" - -# @ edd -#: includes/cart-functions.php:405 -#, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "Vous venez d'ajouter avec succès %s à votre panier." - -# @ edd -#: includes/cart-functions.php:406 -msgid "Checkout." -msgstr "Commander." - -# @ edd -#: includes/cart-template.php:46 includes/cart-template.php:49 -#: includes/install.php:31 includes/template-functions.php:137 -#: includes/template-functions.php:158 -msgid "Checkout" -msgstr "Commander" - -# @ edd -#: includes/cart-template.php:80 includes/templates/checkout_cart.php:36 -msgid "remove" -msgstr "retirer" - -# @ edd -#: includes/cart-template.php:99 -msgid "Your cart is empty." -msgstr "Votre panier est vide." - -# @ edd -#: includes/checkout-template.php:101 -msgid "Personal Info" -msgstr "Info personnelle" - -# @ edd -#: includes/checkout-template.php:104 -msgid "Email address" -msgstr "Adresse e-mail" - -# @ edd -#: includes/checkout-template.php:105 -msgid "Email Address" -msgstr "Adresse e-mail" - -# @ edd -#: includes/checkout-template.php:109 includes/checkout-template.php:110 -#: includes/checkout-template.php:343 includes/checkout-template.php:344 -#: includes/admin/export-functions.php:41 -msgid "First Name" -msgstr "Prénom" - -# @ edd -#: includes/checkout-template.php:113 includes/checkout-template.php:347 -msgid "Last name" -msgstr "Nom" - -# @ edd -#: includes/checkout-template.php:114 includes/checkout-template.php:348 -#: includes/admin/export-functions.php:42 -msgid "Last Name" -msgstr "Nom" - -#: includes/checkout-template.php:142 -msgid "Show Terms" -msgstr "Afficher les conditions" - -#: includes/checkout-template.php:143 -msgid "Hide Terms" -msgstr "Cacher les conditions" - -#: includes/checkout-template.php:146 -msgid "Agree to Terms?" -msgstr "Accepter les conditions ?" - -# @ edd -#: includes/checkout-template.php:166 -msgid "Go back" -msgstr "Retour" - -# @ edd -#: includes/checkout-template.php:170 -msgid "You must be logged in to complete your purchase" -msgstr "Vous devez être connecté afin de poursuivre l'achat" - -# @ edd -#: includes/checkout-template.php:199 -msgid "Credit Card Info" -msgstr "Infos carte de crédit" - -# @ edd -#: includes/checkout-template.php:201 -msgid "Card name" -msgstr "Nom de la carte" - -# @ edd -#: includes/checkout-template.php:202 -msgid "Name on the Card" -msgstr "Nom sur la carte" - -# @ edd -#: includes/checkout-template.php:205 -msgid "Card number" -msgstr "Numéro de la carte" - -# @ edd -#: includes/checkout-template.php:206 -msgid "Card Number" -msgstr "Numéro de la carte" - -# @ edd -#: includes/checkout-template.php:209 -msgid "Security code" -msgstr "Code de sécurité" - -# @ edd -#: includes/checkout-template.php:210 -msgid "CVC" -msgstr "CVC" - -# @ edd -#: includes/checkout-template.php:216 -#: includes/admin/reporting/graphing.php:121 -#: includes/admin/reporting/graphing.php:221 -msgid "Month" -msgstr "Mois" - -# @ edd -#: includes/checkout-template.php:218 -msgid "Year" -msgstr "Année" - -# @ edd -#: includes/checkout-template.php:219 -msgid "Expiration (MM/YYYY)" -msgstr "Expiration (MM/AAAA)" - -# @ edd -#: includes/checkout-template.php:249 -msgid "Address line 1" -msgstr "Adresse ligne 1" - -# @ edd -#: includes/checkout-template.php:250 -msgid "Billing Address" -msgstr "Adresse de facturation" - -# @ edd -#: includes/checkout-template.php:253 -msgid "Address line 2" -msgstr "Adresse ligne 2" - -# @ edd -#: includes/checkout-template.php:254 -msgid "Billing Address Line 2" -msgstr "Adresse de facturation ligne 2" - -# @ edd -#: includes/checkout-template.php:257 -msgid "City" -msgstr "Ville" - -# @ edd -#: includes/checkout-template.php:258 -msgid "Billing City" -msgstr "Ville de facturation" - -# @ edd -#: includes/checkout-template.php:269 -msgid "Billing Country" -msgstr "Pays de facturation" - -# @ edd -#: includes/checkout-template.php:272 -msgid "State / Province" -msgstr "État / Province" - -# @ edd -#: includes/checkout-template.php:289 -msgid "Billing State / Province" -msgstr "État / Province de facturation" - -# @ edd -#: includes/checkout-template.php:292 -msgid "Zip / Postal code" -msgstr "Code postal" - -# @ edd -#: includes/checkout-template.php:293 -msgid "Billing Zip / Postal Code" -msgstr "Code Postal" - -# @ edd -#: includes/checkout-template.php:320 -msgid "Already have an account?" -msgstr "Vous avez déjà un compte ?" - -# @ edd -#: includes/checkout-template.php:320 includes/login-register.php:58 -msgid "Login" -msgstr "Se connecter" - -# @ edd -#: includes/checkout-template.php:322 -msgid "Create an account" -msgstr "Créer un compte" - -# @ edd -#: includes/checkout-template.php:322 -msgid "(optional)" -msgstr "(facultatif)" - -# @ edd -#: includes/checkout-template.php:325 includes/checkout-template.php:326 -#: includes/checkout-template.php:373 includes/login-register.php:47 -#: includes/login-register.php:48 -msgid "Username" -msgstr "Nom d'utilisateur" - -# @ edd -#: includes/checkout-template.php:329 includes/checkout-template.php:330 -#: includes/checkout-template.php:377 includes/login-register.php:51 -msgid "Password" -msgstr "Mot de passe" - -# @ edd -#: includes/checkout-template.php:333 -msgid "Confirm password" -msgstr "Confirmer le mot de passe" - -# @ edd -#: includes/checkout-template.php:334 -msgid "Password Again" -msgstr "Mot de passe à nouveau" - -# @ edd -#: includes/checkout-template.php:339 includes/checkout-template.php:340 -#: includes/admin/export-functions.php:40 -#: includes/admin/payments/payments-history.php:139 -#: includes/admin/payments/payments-history.php:156 -msgid "Email" -msgstr "E-mail " - -# @ edd -#: includes/checkout-template.php:369 -msgid "Login to your account" -msgstr "Se connecter au compte" - -# @ edd -#: includes/checkout-template.php:372 -msgid "Your username" -msgstr "Votre nom d'utilisateur" - -# @ edd -#: includes/checkout-template.php:376 -msgid "Your password" -msgstr "Votre mot de passe" - -# @ edd -#: includes/checkout-template.php:384 -msgid "Need to create an account?" -msgstr "Besoin de créer un compte ?" - -# @ edd -#: includes/checkout-template.php:386 -msgid "Register" -msgstr "Enregistrer" - -#: includes/checkout-template.php:386 -msgid "or checkout as a guest." -msgstr "ou commander en tant qu'invité." - -# @ edd -#: includes/checkout-template.php:415 -msgid "Choose Your Payment Method" -msgstr "Choisissez votre méthode de paiement" - -# @ edd -#: includes/checkout-template.php:443 -msgid "Enter discount" -msgstr "Entrer code promo" - -# @ edd -#: includes/checkout-template.php:445 -msgid "Discount" -msgstr "Remise" - -# @ edd -#: includes/checkout-template.php:447 -msgid "Apply Discount" -msgstr "Appliquer le code promo" - -# @ edd -#: includes/checkout-template.php:473 includes/admin/downloads/metabox.php:570 -#: includes/admin/downloads/metabox.php:667 -#: includes/admin/payments/payments-history.php:319 -msgid "Next" -msgstr "Suivant" - -# @ edd -#: includes/checkout-template.php:497 includes/shortcodes.php:26 -#: includes/template-functions.php:60 includes/admin/thickbox.php:61 -#: includes/admin/downloads/dashboard-columns.php:57 -msgid "Purchase" -msgstr "Acheter" - -# @ edd -#: includes/email-functions.php:47 includes/register-settings.php:191 -msgid "Purchase Receipt" -msgstr "Facture d'achat" - -# @ edd -#: includes/email-functions.php:62 -msgid "Hello" -msgstr "Bonjour" - -# @ edd -#: includes/email-functions.php:62 -msgid "A download purchase has been made" -msgstr "Un achat de téléchargement a été réalisé" - -# @ edd -#: includes/email-functions.php:63 -msgid "Downloads sold:" -msgstr "Téléchargements vendus" - -# @ edd -#: includes/email-functions.php:76 -msgid "Purchased by: " -msgstr "Acheté par :" - -# @ edd -#: includes/email-functions.php:77 -msgid "Amount: " -msgstr "Montant :" - -# @ edd -#: includes/email-functions.php:78 -msgid "Payment Method: " -msgstr "Méthode de Paiement :" - -# @ edd -#: includes/email-functions.php:79 -msgid "Thank you" -msgstr "Merci" - -# @ edd -#: includes/email-functions.php:82 -msgid "New download purchase" -msgstr "Nouvel achat de téléchargement enregistré" - -#: includes/email-template.php:23 -msgid "Default Template" -msgstr "Modèle par défaut" - -#: includes/email-template.php:24 -msgid "No template, plain text only" -msgstr "Pas de modèle, uniquement du texte" - -#: includes/email-template.php:115 -msgid "Sample Product Title" -msgstr "Le titre du produit de test" - -# @ edd -#: includes/email-template.php:118 -msgid "Sample Download File Name" -msgstr "Le nom du fichier de test de téléchargement" - -#: includes/email-template.php:118 -msgid "Optional notes about this download." -msgstr "Remarques facultatives sur ce téléchargement." - -#: includes/email-template.php:129 -msgid "These are some sample notes added to a product." -msgstr "Voici quelques exemples de remarques ajoutées à un produit." - -# @ edd -#: includes/email-template.php:168 -msgid "Purchase Receipt Preview" -msgstr "Aperçu du reçu d'achat" - -# @ edd -#: includes/email-template.php:168 -msgid "Preview Purchase Receipt" -msgstr "Aperçu du reçu d'achat" - -# @ edd -#: includes/email-template.php:209 -msgid "Dear" -msgstr "Cher" - -#: includes/email-template.php:210 -msgid "" -"Thank you for your purchase. Please click on the link(s) below to download " -"your files." -msgstr "" -"Nous vous remercions de votre achat. S'il vous plaît cliquez sur le(s) lien" -"(s) ci-dessous pour télécharger vos fichiers." - -# @ edd -#: includes/error-tracking.php:30 includes/process-download.php:266 -#: includes/process-download.php:283 includes/query-filters.php:42 -#: includes/admin/discounts/edit-discount.php:13 -msgid "Error" -msgstr "Erreur" - -# @ edd -#: includes/gateway-functions.php:28 -msgid "Test Payment" -msgstr "Paiement de test" - -# @ edd -#: includes/install.php:42 -msgid "Purchase Confirmation" -msgstr "Confirmation d'achat" - -# @ edd -#: includes/install.php:43 -msgid "Thank you for your purchase!" -msgstr "Merci pour votre achat !" - -# @ edd -#: includes/install.php:53 includes/widgets.php:193 -msgid "Purchase History" -msgstr "Historique des achats" - -# @ edd -#: includes/login-register.php:45 -msgid "Log into Your Account" -msgstr "Se connecter à votre compte" - -# @ edd -#: includes/login-register.php:61 -msgid "Lost Password" -msgstr "Mot de passe oublié" - -# @ edd -#: includes/login-register.php:62 -msgid "Lost Password?" -msgstr "Mot de Passe perdu ?" - -#: includes/login-register.php:69 -msgid "You are already logged in" -msgstr "Vous êtes déjà connecté(e)." - -# @ edd -#: includes/login-register.php:92 includes/process-purchase.php:472 -msgid "The password you entered is incorrect" -msgstr "Le mot de passe fourni ne correspond pas" - -# @ edd -#: includes/login-register.php:95 includes/process-purchase.php:491 -msgid "The username you entered does not exist" -msgstr "Le nom d'utilisateur fourni n'existe pas" - -# @ edd -#: includes/misc-functions.php:185 -msgid "US Dollars ($)" -msgstr "Dollar US ($)" - -# @ edd -#: includes/misc-functions.php:186 -msgid "Euros (€)" -msgstr "Euro (€)" - -# @ edd -#: includes/misc-functions.php:187 -msgid "Pounds Sterling (£)" -msgstr "Livre Sterling (£)" - -# @ edd -#: includes/misc-functions.php:188 -msgid "Australian Dollars ($)" -msgstr "Dollar Australien ($)" - -# @ edd -#: includes/misc-functions.php:189 -msgid "Brazilian Real ($)" -msgstr "Réal brésilien ($)" - -# @ edd -#: includes/misc-functions.php:190 -msgid "Canadian Dollars ($)" -msgstr "Dollar canadien ($)" - -# @ edd -#: includes/misc-functions.php:191 -msgid "Czech Koruna" -msgstr "Couronne tchèque" - -# @ edd -#: includes/misc-functions.php:192 -msgid "Danish Krone" -msgstr "Couronne danoise" - -# @ edd -#: includes/misc-functions.php:193 -msgid "Hong Kong Dollar ($)" -msgstr "Dollar de Hong Kong ($)" - -# @ edd -#: includes/misc-functions.php:194 -msgid "Hungarian Forint" -msgstr "Forint hongrois" - -# @ edd -#: includes/misc-functions.php:195 -msgid "Israeli Shekel" -msgstr "Nouveau Shekel" - -# @ edd -#: includes/misc-functions.php:196 -msgid "Japanese Yen (¥)" -msgstr "Yen Japonais (¥)" - -# @ edd -#: includes/misc-functions.php:197 -msgid "Malaysian Ringgits" -msgstr "Ringgit malaisien" - -# @ edd -#: includes/misc-functions.php:198 -msgid "Mexican Peso ($)" -msgstr "Peso mexicain ($)" - -# @ edd -#: includes/misc-functions.php:199 -msgid "New Zealand Dollar ($)" -msgstr "Dollar Nouvelle Zélande ($)" - -# @ edd -#: includes/misc-functions.php:200 -msgid "Norwegian Krone" -msgstr "Couronne norvégienne" - -# @ edd -#: includes/misc-functions.php:201 -msgid "Philippine Pesos" -msgstr "Peso philippin" - -# @ edd -#: includes/misc-functions.php:202 -msgid "Polish Zloty" -msgstr "Zloty polonais" - -# @ edd -#: includes/misc-functions.php:203 -msgid "Singapore Dollar ($)" -msgstr "Dollar de Singapour ($)" - -# @ edd -#: includes/misc-functions.php:204 -msgid "Swedish Krona" -msgstr "Couronne suédoise" - -# @ edd -#: includes/misc-functions.php:205 -msgid "Swiss Franc" -msgstr "Franc suisse" - -# @ edd -#: includes/misc-functions.php:206 -msgid "Taiwan New Dollars" -msgstr "Nouveau dollar de Taiwan" - -# @ edd -#: includes/misc-functions.php:207 -msgid "Thai Baht" -msgstr "Baht thaïlandais" - -# @ edd -#: includes/misc-functions.php:208 -msgid "Indian Rupee" -msgstr "Roupie indienne" - -#: includes/misc-functions.php:209 -msgid "Turkish Lira" -msgstr "Lire Turque (TL)" - -#: includes/misc-functions.php:210 -msgid "Iranian Rial" -msgstr "Rial iranian" - -# @ edd -#: includes/payment-functions.php:299 -#: includes/admin/payments/payments-history.php:98 -msgid "Pending" -msgstr "En attente" - -# @ edd -#: includes/payment-functions.php:300 -msgid "Complete" -msgstr "Terminé" - -#: includes/payment-functions.php:301 -#: includes/admin/payments/payments-history.php:101 -msgid "Refunded" -msgstr "Remboursé" - -# @ edd -#: includes/post-types.php:43 includes/post-types.php:81 -msgid "Add New" -msgstr "Ajouter Nouveau" - -# @ edd -#: includes/post-types.php:44 -#, php-format -msgid "Add New %1$s" -msgstr "Ajouter Nouveau %1$s" - -# @ edd -#: includes/post-types.php:45 -#, php-format -msgid "Edit %1$s" -msgstr "Modifier %1$s" - -#: includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "Nouveau %1$s" - -# @ edd -#: includes/post-types.php:47 -#, php-format -msgid "All %2$s" -msgstr "Tous les %2$s" - -#: includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "Afficher %1$s" - -# @ edd -#: includes/post-types.php:49 -#, php-format -msgid "Search %2$s" -msgstr "Rechercher %2$s" - -# @ edd -#: includes/post-types.php:50 -#, php-format -msgid "No %2$s found" -msgstr "Aucun %2$s trouvé" - -# @ edd -#: includes/post-types.php:51 -#, php-format -msgid "No %2$s found in Trash" -msgstr "Aucun %2$s trouvé dans la Corbeille" - -#: includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "%2$s" - -# @ edd -#: includes/post-types.php:79 -msgctxt "post type general name" -msgid "Payments" -msgstr "Paiements" - -# @ edd -#: includes/post-types.php:80 -msgctxt "post type singular name" -msgid "Payment" -msgstr "Paiement" - -# @ edd -#: includes/post-types.php:82 -msgid "Add New Payment" -msgstr "Ajouter un paiement" - -# @ edd -#: includes/post-types.php:83 includes/admin/payments/edit-payment.php:16 -msgid "Edit Payment" -msgstr "Modifier paiement" - -# @ edd -#: includes/post-types.php:84 -msgid "New Payment" -msgstr "Nouveau paiement" - -# @ edd -#: includes/post-types.php:85 -msgid "All Payments" -msgstr "Tous les paiements" - -# @ edd -#: includes/post-types.php:86 -msgid "View Payment" -msgstr "Afficher paiement" - -# @ edd -#: includes/post-types.php:87 -msgid "Search Payments" -msgstr "Rechercher paiements" - -# @ edd -#: includes/post-types.php:88 -msgid "No Payments found" -msgstr "Aucun paiement trouvé" - -# @ edd -#: includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "Aucun paiement trouvé dans la corbeille" - -# @ edd -#: includes/post-types.php:91 includes/admin/admin-pages.php:26 -#: includes/admin/payments/payments-history.php:85 -msgid "Payment History" -msgstr "Historique des paiements" - -# @ edd -#: includes/post-types.php:125 includes/admin/reporting/graphing.php:77 -msgid "Download" -msgstr "Téléchargement" - -# @ edd -#: includes/post-types.php:126 -msgid "Downloads" -msgstr "Téléchargements" - -# @ edd -#: includes/post-types.php:173 -msgctxt "taxonomy general name" -msgid "Categories" -msgstr "Catégories" - -# @ edd -#: includes/post-types.php:174 -msgctxt "taxonomy singular name" -msgid "Category" -msgstr "Catégorie" - -# @ edd -#: includes/post-types.php:175 -msgid "Search Categories" -msgstr "Rechercher catégories" - -# @ edd -#: includes/post-types.php:176 -msgid "All Categories" -msgstr "Toutes les catégories" - -# @ edd -#: includes/post-types.php:177 -msgid "Parent Category" -msgstr "Catégorie parente" - -# @ edd -#: includes/post-types.php:178 -msgid "Parent Category:" -msgstr "Catégorie parente :" - -# @ edd -#: includes/post-types.php:179 -msgid "Edit Category" -msgstr "Modifier la catégorie" - -# @ edd -#: includes/post-types.php:180 -msgid "Update Category" -msgstr "Mettre à jour la catégorie" - -# @ edd -#: includes/post-types.php:181 -msgid "Add New Category" -msgstr "Ajouter une catégorie" - -# @ edd -#: includes/post-types.php:182 -msgid "New Category Name" -msgstr "Nouveau nom de catégorie" - -# @ edd -#: includes/post-types.php:183 includes/widgets.php:169 -#: includes/admin/downloads/dashboard-columns.php:27 -#: includes/admin/reporting/pdf-reports.php:67 -msgid "Categories" -msgstr "Catégories" - -# @ edd -#: includes/post-types.php:198 -msgctxt "taxonomy general name" -msgid "Tags" -msgstr "Mots-clefs" - -# @ edd -#: includes/post-types.php:199 -msgctxt "taxonomy singular name" -msgid "Tag" -msgstr "Mot-clef" - -# @ edd -#: includes/post-types.php:200 -msgid "Search Tags" -msgstr "Rechercher mots-clefs" - -# @ edd -#: includes/post-types.php:201 -msgid "All Tags" -msgstr "Tous les mots-clefs" - -# @ edd -#: includes/post-types.php:202 -msgid "Parent Tag" -msgstr "Mot-clef parent" - -# @ edd -#: includes/post-types.php:203 -msgid "Parent Tag:" -msgstr "Mot-clef parent :" - -# @ edd -#: includes/post-types.php:204 -msgid "Edit Tag" -msgstr "Modifier mot-clef" - -# @ edd -#: includes/post-types.php:205 -msgid "Update Tag" -msgstr "Mettre à jour mot-clef" - -# @ edd -#: includes/post-types.php:206 -msgid "Add New Tag" -msgstr "Ajouter nouveau mot-clef" - -# @ edd -#: includes/post-types.php:207 -msgid "New Tag Name" -msgstr "Nouveau nom du mot-clef" - -# @ edd -#: includes/post-types.php:208 includes/widgets.php:170 -#: includes/admin/downloads/dashboard-columns.php:28 -#: includes/admin/reporting/pdf-reports.php:68 -msgid "Tags" -msgstr "Mots-clefs" - -# @ edd -#: includes/post-types.php:239 includes/post-types.php:240 -msgid "Download updated." -msgstr "Téléchargement mis à jour." - -# @ edd -#: includes/post-types.php:241 -msgid "Download published." -msgstr "Téléchargement publié." - -# @ edd -#: includes/post-types.php:242 -msgid "Download saved." -msgstr "Téléchargements sauvegardés." - -# @ edd -#: includes/post-types.php:243 -msgid "Download submitted." -msgstr "Téléchargement ajouté." - -#: includes/process-download.php:266 includes/process-download.php:283 -msgid "Sorry but this file does not exist." -msgstr "Désolé mais ce fichier n'existe pas." - -# @ edd -#: includes/process-download.php:294 -msgid "You do not have permission to download this file" -msgstr "Vous n'avez pas la permission de télécharger ce fichier" - -# @ edd -#: includes/process-download.php:294 -msgid "Purchase Verification Failed" -msgstr "Erreur lors de la vérification de l'achat" - -#: includes/process-purchase.php:206 -msgid "The selected gateway is not active" -msgstr "La passerelle sélectionnée n'est pas active" - -#: includes/process-purchase.php:210 -msgid "No gateway has been selected" -msgstr "Aucune passerelle n'a été sélectionné" - -#: includes/process-purchase.php:259 -msgid "You must agree to the terms of use" -msgstr "Vous devez accepter les conditions d'utilisation" - -# @ edd -#: includes/process-purchase.php:289 -msgid "Please enter a valid email address." -msgstr "Veuillez saisir une adresse de messagerie valide." - -#: includes/process-purchase.php:303 -msgid "The user information is invalid." -msgstr "Les informations sur l'utilisateur sont incorrectes." - -# @ edd -#: includes/process-purchase.php:349 -msgid "Username already taken" -msgstr "Nom d'utilisateur déjà utilisé" - -# @ edd -#: includes/process-purchase.php:355 -msgid "Invalid username" -msgstr "Nom d'utilisateur incorrect" - -# @ edd -#: includes/process-purchase.php:366 -msgid "You must register or login to complete your purchase" -msgstr "Vous devez être enregistré ou connecté afin de terminer votre achat" - -# @ edd -#: includes/process-purchase.php:378 includes/process-purchase.php:522 -msgid "Invalid email" -msgstr "E-mail incorrect" - -# @ edd -#: includes/process-purchase.php:384 -msgid "Email already used" -msgstr "E-mail déjà utilisé" - -# @ edd -#: includes/process-purchase.php:396 includes/process-purchase.php:529 -msgid "Enter an email" -msgstr "Entrez une adresse e-mail" - -# @ edd -#: includes/process-purchase.php:407 -msgid "Passwords don't match" -msgstr "Les mote de passe ne correspondent pas" - -# @ edd -#: includes/process-purchase.php:422 includes/process-purchase.php:487 -msgid "Enter a password" -msgstr "Entrez un mot de passe" - -# @ edd -#: includes/process-purchase.php:427 -msgid "Enter the password confirmation" -msgstr "Entrez la confirmation de mot de passe" - -# @ edd -#: includes/process-purchase.php:454 -msgid "You must login or register to complete your purchase" -msgstr "Vous devez être connecté ou enregistré afin de terminer votre achat" - -# @ edd -#: includes/query-filters.php:42 -msgid "You do not have permission to view this file." -msgstr "Vous n'avez pas la permission de voir ce fichier." - -# @ edd -#: includes/register-settings.php:41 -msgid "Test Mode" -msgstr "Mode de test" - -# @ edd -#: includes/register-settings.php:42 -msgid "" -"While in test mode no live transactions are processed. To fully use test " -"mode, you must have a sandbox (test) account for the payment gateway you are " -"testing." -msgstr "" -"En mode test, aucune transaction réelle n'est effectuée. Afin d'utiliser le " -"mode test pleinement, vous devez disposer d'un compte de test pour la " -"passerelle de paiement que vous testez." - -# @ edd -#: includes/register-settings.php:47 -msgid "Checkout Page" -msgstr "Page de commande" - -# @ edd -#: includes/register-settings.php:48 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "La page de commande où les acheteurs réalisent leurs achats." - -# @ edd -#: includes/register-settings.php:54 -msgid "Success Page" -msgstr "Page d'achat réussi" - -# @ edd -#: includes/register-settings.php:55 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "" -"La page vers laquelle les acheteurs sont redirigés après avoir réalisé leurs " -"achats." - -#: includes/register-settings.php:61 -msgid "Download Links on Success Page" -msgstr "Liens des téléchargements sur la page d'achat réussi" - -#: includes/register-settings.php:62 -msgid "" -"Show a list of all download links on the success page after completing a " -"purchase?" -msgstr "" -"Afficher la liste de tous les liens des téléchargements sur la page d'achat " -"réussi après avoir terminé un achat ?" - -# @ edd -#: includes/register-settings.php:67 -msgid "Currency Settings" -msgstr "Réglages de monnaie" - -# @ edd -#: includes/register-settings.php:68 -msgid "Configure the currency options" -msgstr "Configurer les réglages de monnaie" - -# @ edd -#: includes/register-settings.php:73 -msgid "Currency" -msgstr "Monnaie" - -# @ edd -#: includes/register-settings.php:74 -msgid "" -"Choose your currency. Note that some payment gateways have currency " -"restrictions." -msgstr "" -"Choissisez votre monnaie. Veuillez noter que certaines passerelles de " -"paiement ont des limitations quant à la monnaie." - -# @ edd -#: includes/register-settings.php:80 -msgid "Currency Position" -msgstr "Position du symbole monétaire" - -# @ edd -#: includes/register-settings.php:81 -msgid "Choose the location of the currency sign." -msgstr "Choisissez l'emplacement du symbole monétaire." - -# @ edd -#: includes/register-settings.php:84 -msgid "Before - $10" -msgstr "Avant - $10" - -# @ edd -#: includes/register-settings.php:85 -msgid "After - 10$" -msgstr "Après - 10$" - -# @ edd -#: includes/register-settings.php:90 -msgid "Thousands Separator" -msgstr "Séparateur des milliers" - -# @ edd -#: includes/register-settings.php:91 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "Le séparateur (le plus souvent , ou .) pour séparer les milliers." - -# @ edd -#: includes/register-settings.php:98 -msgid "Decimal Separator" -msgstr "Séparateur décimal" - -# @ edd -#: includes/register-settings.php:99 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "Le séparateur (le plus souvent , ou .) pour séparer les décimales." - -# @ edd -#: includes/register-settings.php:110 includes/admin/settings/settings.php:34 -msgid "Payment Gateways" -msgstr "Portails de paiement" - -# @ edd -#: includes/register-settings.php:111 -msgid "Choose the payment gateways you want to enable." -msgstr "Choisissez les passerelles de paiement que vous désirez activer." - -# @ edd -#: includes/register-settings.php:117 -msgid "Accepted Payment Method Icons" -msgstr "Icônes des méthodes de paiement acceptées" - -# @ edd -#: includes/register-settings.php:118 -msgid "Display icons for the selected payment methods" -msgstr "Afficher les icônes pour les méthodes de paiement acceptées" - -# @ edd -#: includes/register-settings.php:118 -msgid "" -"You will also need to configure your gateway settings if you are accepting " -"credit cards" -msgstr "" -"Vous devrez également configurer les réglages de votre passerelle si vous " -"acceptez les cartes de crédit." - -# @ edd -#: includes/register-settings.php:131 -msgid "PayPal Settings" -msgstr "Réglages PayPal" - -# @ edd -#: includes/register-settings.php:132 -msgid "Configure the PayPal settings" -msgstr "Configurer les réglages PayPal" - -# @ edd -#: includes/register-settings.php:137 -msgid "PayPal Email" -msgstr "E-mail PayPal" - -# @ edd -#: includes/register-settings.php:138 -msgid "Enter your PayPal account's email" -msgstr "Fournissez votre adresse e-mail PayPal." - -# @ edd -#: includes/register-settings.php:144 -msgid "Alternate PayPal Purchase Verification" -msgstr "Vérification d'achat PayPal alternative" - -# @ edd -#: includes/register-settings.php:145 -msgid "" -"If payments are not getting marked as complete, then check this box. Note, " -"this requires that buyers return to your site from PayPal." -msgstr "" -"Si les paiements ne sont pas marqués comme terminés, cocher cette option. " -"Remarque, ce qui exige que les acheteurs reviennent sur votre site de PayPal." - -# @ edd -#: includes/register-settings.php:150 -msgid "Disable PayPal IPN Verification" -msgstr "Désactiver cURL pour l'IPN PayPal" - -# @ edd -#: includes/register-settings.php:151 -msgid "" -"If payments are not getting marked as complete, then check this box. This " -"forces the site to use a slightly less secure method of verifying purchases." -msgstr "" -"Si les paiements ne sont pas marqués comme terminés, cocher cette case. Cela " -"force le site a utiliser une méthode un peu moins sécurisée pour vérifier " -"les achats." - -#: includes/register-settings.php:160 -msgid "Email Template" -msgstr "Modèle d'e-mail" - -#: includes/register-settings.php:161 -msgid "" -"Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" " -"to see the new template." -msgstr "" -"Choisissez un modèle. Cliquez sur \"Enregistrer les modifications\" puis " -"\"Aperçu de reçu d'achat\" pour voir le nouveau modèle." - -# @ edd -#: includes/register-settings.php:173 -msgid "From Name" -msgstr "Nom de l'expéditeur" - -# @ edd -#: includes/register-settings.php:174 -msgid "" -"The name purchase receipts are said to come from. This should probably be " -"your site or shop name." -msgstr "" -"Nom de l'expéditeur pour les factures d'achat. Par exemple le nom de votre " -"site ou magasin." - -# @ edd -#: includes/register-settings.php:179 -msgid "From Email" -msgstr "E-mail de l'expéditeur" - -# @ edd -#: includes/register-settings.php:180 -msgid "" -"Email to send purchase receipts from. This will act as the \"from\" and " -"\"reply-to\" address." -msgstr "Adresse e-mail de l'expéditeur." - -# @ edd -#: includes/register-settings.php:185 -msgid "Purchase Email Subject" -msgstr "Sujet de l'e-mail relatif à l'achat" - -# @ edd -#: includes/register-settings.php:186 -msgid "Enter the subject line for the purchase receipt email" -msgstr "Le sujet de l'e-mail correspondant à la facture de l'achat." - -# @ edd -#: includes/register-settings.php:192 -msgid "" -"Enter the email that is sent to users after completing a successful " -"purchase. HTML is accepted. Available template tags:" -msgstr "" -"Fournissez l'e-mail qui sera envoyé aux utilisateurs après un achat réussi. " -"HTML autorisé. Balises autorisées :" - -# @ edd -#: includes/register-settings.php:193 -msgid "A list of download URLs for each download purchased" -msgstr "Liste des URL de téléchargements pour chaque téléchargement acheté" - -# @ edd -#: includes/register-settings.php:194 -msgid "The buyer's name" -msgstr "Nom de l'acheteur" - -# @ edd -#: includes/register-settings.php:195 -msgid "The date of the purchase" -msgstr "Date de l'achat" - -# @ edd -#: includes/register-settings.php:196 -msgid "The total price of the purchase" -msgstr "Le prix total de l'achat" - -# @ edd -#: includes/register-settings.php:197 -msgid "The unique ID number for this purchase receipt" -msgstr "Le nombre de l'ID unique pour cette facture d'achat." - -# @ edd -#: includes/register-settings.php:198 -msgid "The method of payment used for this purchase" -msgstr "La méthode de paiement utilisée pour cet achat" - -# @ edd -#: includes/register-settings.php:199 -msgid "Your site name" -msgstr "Nom du site web" - -#: includes/register-settings.php:208 -msgid "Disable Styles" -msgstr "Désactiver les styles" - -#: includes/register-settings.php:209 -msgid "Check this to disable all included styling" -msgstr "Cocher cette case pour désactiver tous les style inclus." - -# @ edd -#: includes/register-settings.php:214 -msgid "Buttons" -msgstr "Boutons" - -#: includes/register-settings.php:215 -msgid "Options for add to cart and purchase buttons" -msgstr "Options pour les boutons ajouter au panier et achater" - -# @ edd -#: includes/register-settings.php:220 -msgid "Default Button Style" -msgstr "Style par défaut du bouton" - -#: includes/register-settings.php:221 -msgid "Choose the style you want to use for the buttons." -msgstr "Choisir le style que vous souhaitez utiliser pour les boutons." - -# @ edd -#: includes/register-settings.php:227 -msgid "Default Button Color" -msgstr "Couleur par défaut du bouton" - -#: includes/register-settings.php:228 -msgid "Choose the color you want to use for the buttons." -msgstr "Choisir la couleur que vous souhaitez utiliser pour les boutons." - -# @ edd -#: includes/register-settings.php:238 -msgid "Disable Ajax" -msgstr "Desactiver Ajax" - -# @ edd -#: includes/register-settings.php:239 -msgid "Check this to disable AJAX for the shopping cart." -msgstr "Cocher pour desactiver AJAX pour le panier." - -# @ edd -#: includes/register-settings.php:244 -msgid "Enable jQuery Validation" -msgstr "Activer la validation jQuery" - -# @ edd -#: includes/register-settings.php:245 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "" -"Cocher pour activer la validation jQuery sur le formulaire de commande." - -# @ edd -#: includes/register-settings.php:250 -msgid "Disable Guest Checkout" -msgstr "Désactiver la commande pour les comptes invité" - -# @ edd -#: includes/register-settings.php:251 -msgid "Require that users be logged-in to purchase files." -msgstr "" -"Exiger que les utilisateurs soient connectés pour acheter des fichiers." - -# @ edd -#: includes/register-settings.php:256 -msgid "Show Register / Login Form?" -msgstr "Montrer le fomualire de connection / inscription ?" - -# @ edd -#: includes/register-settings.php:257 -msgid "" -"Display the registration and login forms on the checkout page for non-logged-" -"in users." -msgstr "" -"Afficher les formulaires d'inscription et de connection sur la page de " -"commande pour les utilisateurs non connectés." - -# @ edd -#: includes/register-settings.php:262 -msgid "Download Link Expiration" -msgstr "Expiration du lien de téléchargement" - -#: includes/register-settings.php:263 -msgid "" -"How long should download links be valid for? Default is 24 hours from the " -"time they are generated. Enter a time in hours." -msgstr "" -"Combien de temps les liens de téléchargement sont-ils disponibles ? La " -"valeur par défaut est de 24 heures à partir du moment où ils sont générés. " -"Entrez un temps en heures." - -# @ edd -#: includes/register-settings.php:269 -msgid "Disable Redownload?" -msgstr "Désactiver le re-téléchargement" - -# @ edd -#: includes/register-settings.php:270 -msgid "" -"Check this if you do not want to allow users to redownload items from their " -"purchase history." -msgstr "" -"Cocher si vous ne désirez pas autoriser les utilisateurs à re-télécharger " -"les articles de leur historique d'achat." - -#: includes/register-settings.php:275 -msgid "Terms of Agreement" -msgstr "Termes du Contrat" - -#: includes/register-settings.php:281 -msgid "Agree to Terms" -msgstr "Accepter les termes" - -#: includes/register-settings.php:282 -msgid "" -"Check this to show an agree to terms on the checkout that users must agree " -"to before purchasing." -msgstr "" -"Cocher pour afficher un accord aux termes du contrat que les utilisateurs " -"doivent accepter avant l'achat." - -#: includes/register-settings.php:287 -msgid "Agree to Terms Label" -msgstr "Accepter les termes du contrat" - -#: includes/register-settings.php:288 -msgid "Label shown next to the agree to terms check box." -msgstr "Étiquette affiché à côté de la case accepter les termes du contrat." - -#: includes/register-settings.php:294 -msgid "Agreement Text" -msgstr "Texte du contrat" - -#: includes/register-settings.php:295 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "" -"Si accepter les termes du contrat est coché, entrez les termes de l'accord " -"ci-dessus." - -# @ edd -#: includes/register-settings.php:300 -msgid "Complete Purchase Text" -msgstr "Texte pour un achat terminé" - -# @ edd -#: includes/register-settings.php:301 -msgid "The button label for completing a purchase." -msgstr "Le libellé du bouton pour compléter un achat." - -#: includes/register-settings.php:306 -msgid "Add to Cart Text" -msgstr "Texte pour ajouter au panier" - -#: includes/register-settings.php:307 -msgid "Text shown on the Add to Cart Buttons" -msgstr "Le texte affiché sur les boutons ajouter au panier" - -# @ edd -#: includes/register-settings.php:333 -msgid "General Settings" -msgstr "Réglages généraux" - -# @ edd -#: includes/register-settings.php:359 -msgid "Payment Gateway Settings" -msgstr "Réglages des passerelles de paiement" - -# @ edd -#: includes/register-settings.php:385 -msgid "Email Settings" -msgstr "Réglages de l'e-mail" - -# @ edd -#: includes/register-settings.php:411 -msgid "Style Settings" -msgstr "Réglages de style" - -# @ edd -#: includes/register-settings.php:438 -msgid "Misc Settings" -msgstr "Réglages divers" - -# @ edd -#: includes/register-settings.php:727 -msgid "Upload File" -msgstr "Télécharger fichier" - -# @ edd -#: includes/register-settings.php:765 -msgid "Settings Updated" -msgstr "Réglages mis à jour" - -# @ edd -#: includes/scripts.php:40 -msgid "Please enter a discount code" -msgstr "Veuillez fournir un code promo" - -# @ edd -#: includes/scripts.php:41 -msgid "Discount Applied" -msgstr "Code promo appliqué" - -# @ edd -#: includes/scripts.php:42 -msgid "Please enter an email address before applying a discount code" -msgstr "Veuillez fournir une adresse e-mail avant d'appliquer un code promo" - -# @ edd -#: includes/scripts.php:44 -msgid "You have already added this item to your cart" -msgstr "Vous avez déjà ajouté cet article à votre panier" - -# @ edd -#: includes/scripts.php:45 -msgid "Your cart is empty" -msgstr "Votre panier est vide" - -# @ edd -#: includes/scripts.php:46 -msgid "Loading" -msgstr "Chargement en cours" - -# @ edd -#: includes/scripts.php:123 -msgid "Add New Download" -msgstr "Ajouter un téléchargement" - -#: includes/scripts.php:124 -msgid "Use This File" -msgstr "Utiliser ce fichier" - -#: includes/scripts.php:125 -msgid "Sorry, not available for variable priced products." -msgstr "Désolé, non disponible pour des produits à prix variables." - -#: includes/scripts.php:126 -msgid "Are you sure you wish to delete this payment?" -msgstr "Etes-vous sûr de vouloir supprimer ce paiement ?" - -#: includes/scripts.php:127 -msgid "You must have at least one price" -msgstr "Vous devez avoir au moins un prix" - -#: includes/scripts.php:128 -msgid "You must have at least one file" -msgstr "Vous devez avoir au moins un fichier" - -# @ edd -#: includes/scripts.php:129 -msgid "You must have at least one field" -msgstr "Vous devez avoir au moins un champ" - -# @ edd -#: includes/shortcodes.php:194 -msgid "Purchase All Items" -msgstr "Tout Acheter" - -# @ edd -#: includes/shortcodes.php:336 -#, php-format -msgctxt "download post type name" -msgid "No %s found" -msgstr "Aucun %s trouvé" - -#: includes/template-functions.php:48 -#, php-format -msgid "" -"No checkout page has been configured. Visit Settings to " -"set one." -msgstr "" -"Aucune page de commande n'a été configurée. Se rendre dans les Réglages pour en configurer une." - -# @ edd -#: includes/template-functions.php:167 -msgid "added to your cart" -msgstr "ajouté au panier" - -#: includes/template-functions.php:263 -msgid "Gray" -msgstr "Gris" - -#: includes/template-functions.php:264 -msgid "Pink" -msgstr "Rose" - -#: includes/template-functions.php:265 -msgid "Blue" -msgstr "Bleu" - -#: includes/template-functions.php:266 -msgid "Green" -msgstr "Vert" - -# @ edd -#: includes/template-functions.php:267 -msgid "Teal" -msgstr "Sarcelle (bleu-vert)" - -# @ edd -#: includes/template-functions.php:268 -msgid "Black" -msgstr "Noir" - -#: includes/template-functions.php:269 -msgid "Dark Gray" -msgstr "Gris foncé" - -#: includes/template-functions.php:270 -msgid "Orange" -msgstr "Orange" - -#: includes/template-functions.php:271 -msgid "Purple" -msgstr "Violet" - -# @ edd -#: includes/template-functions.php:272 -msgid "Slate" -msgstr "Ardoise" - -# @ edd -#: includes/template-functions.php:293 -msgid "Button" -msgstr "Bouton" - -# @ edd -#: includes/template-functions.php:294 -msgid "Plain Text" -msgstr "Texte brut" - -# @ edd -#: includes/template-functions.php:317 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "" -"Vous avez déjà acheté cet article, mais vous pouvez l'acheter à nouveau." - -# @ edd -#: includes/widgets.php:39 -msgid "Downloads Cart" -msgstr "Panier des téléchargements" - -# @ edd -#: includes/widgets.php:39 -msgid "Display the downloads shopping cart" -msgstr "Afficher le panier des téléchargements" - -# @ edd -#: includes/widgets.php:82 includes/widgets.php:162 -msgid "Title:" -msgstr "Titre :" - -# @ edd -#: includes/widgets.php:87 -msgid "Show Quantity:" -msgstr "Montrer la quantité :" - -# @ edd -#: includes/widgets.php:112 -msgid "Downloads Categories / Tags" -msgstr "Catégories / Mots-clefs des téléchargements" - -# @ edd -#: includes/widgets.php:112 -msgid "Display the downloads categories or tags" -msgstr "Afficher les catégories ou les mots-clefs des téléchargements" - -#: includes/widgets.php:167 -msgid "Taxonomy:" -msgstr "Taxinomie :" - -#: includes/widgets.php:193 -msgid "Display a user's purchase history" -msgstr "Affiche un historique d'achat de l'utilisateur" - -# @ edd -#: includes/widgets.php:232 includes/templates/history-downloads.php:48 -#: includes/templates/history-purchases.php:48 -msgid "No downloadable files found." -msgstr "Aucun fichier téléchargeable trouvé." - -# @ edd -#: includes/widgets.php:259 -msgid "Title" -msgstr "Titre" - -# @ edd -#: includes/widgets.php:299 -msgid "Easy Digital Downloads Sales Summary" -msgstr "Résumé des ventes d'Easy Digital Downloads" - -# @ edd -#: includes/widgets.php:318 -msgid "Current Month" -msgstr "Mois en cours" - -# @ edd -#: includes/widgets.php:323 includes/admin/downloads/dashboard-columns.php:31 -#: includes/admin/reporting/graphing.php:78 -#: includes/admin/reporting/graphing.php:122 -#: includes/admin/reporting/graphing.php:169 -#: includes/admin/reporting/pdf-reports.php:191 -msgid "Earnings" -msgstr "Revenus" - -# @ edd -#: includes/widgets.php:327 includes/admin/downloads/dashboard-columns.php:30 -#: includes/admin/reporting/graphing.php:32 -#: includes/admin/reporting/graphing.php:222 -#: includes/admin/reporting/pdf-reports.php:208 -msgid "Sales" -msgstr "Ventes" - -# @ edd -#: includes/widgets.php:333 -msgid "Totals" -msgstr "Totaux" - -# @ edd -#: includes/widgets.php:338 -msgid "Total Earnings" -msgstr "Total des gains" - -# @ edd -#: includes/widgets.php:342 -msgid "Total Sales" -msgstr "Total des ventes" - -# @ edd -#: includes/admin/add-ons.php:69 -msgid "Add Ons for Easy Digital Downloads" -msgstr "Modules complémentaires pour Easy Digital Downloads" - -#: includes/admin/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "" -"Ces modules complémentaires étendent les fonctionnalités d'Easy Digital " -"Downloads." - -#: includes/admin/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "Parcourir toutes les extensions" - -# @ edd -#: includes/admin/admin-notices.php:30 -msgid "Discount code updated." -msgstr "Code promo mis à jour." - -# @ edd -#: includes/admin/admin-notices.php:33 -msgid "There was a problem updating your discount code, please try again." -msgstr "" -"Une erreur s'est produite lors de la mise à jour de votre code promotionnel, " -"veuillez réessayer." - -#: includes/admin/admin-notices.php:36 -msgid "The payment has been deleted." -msgstr "Le paiement a été supprimé." - -#: includes/admin/admin-notices.php:39 -msgid "The purchase receipt has been resent." -msgstr "La facture d'achat a été renvoyée." - -#: includes/admin/admin-notices.php:44 -#, php-format -msgid "The payment history needs updated. %s" -msgstr "L'historique des paiements doit être mise à jour. %s" - -#: includes/admin/admin-notices.php:44 -msgid "Click to Upgrade" -msgstr "Cliquez pour mettre à niveau" - -#: includes/admin/admin-notices.php:62 -msgid "" -"There seems to be an issue with the server. Please try again in a few " -"minutes." -msgstr "" -"Il semble y avoir un problème avec le serveur. S'il vous plaît essayez de " -"nouveau dans quelques minutes." - -# @ edd -#: includes/admin/admin-pages.php:27 -#: includes/admin/discounts/discount-codes.php:34 -msgid "Discount Codes" -msgstr "Codes promo" - -# @ edd -#: includes/admin/admin-pages.php:28 -msgid "Earnings and Sales Reports" -msgstr "Rapports de revenus et de ventes" - -# @ edd -#: includes/admin/admin-pages.php:28 includes/admin/reporting/reports.php:28 -msgid "Reports" -msgstr "Rapports" - -# @ edd -#: includes/admin/admin-pages.php:29 -msgid "Easy Digital Download Settings" -msgstr "Réglages Easy Digital Download" - -# @ edd -#: includes/admin/admin-pages.php:29 -msgid "Settings" -msgstr "Réglages" - -# @ edd -#: includes/admin/admin-pages.php:30 -msgid "Easy Digital Download Add Ons" -msgstr "Modules Easy Digital Downloads" - -#: includes/admin/admin-pages.php:30 -msgid "Add Ons" -msgstr "Modules complémentaires" - -# @ edd -#: includes/admin/export-functions.php:39 -#: includes/admin/payments/payments-history.php:137 -#: includes/admin/payments/payments-history.php:155 -msgid "ID" -msgstr "ID" - -# @ edd -#: includes/admin/export-functions.php:43 -#: includes/admin/payments/payments-history.php:140 -#: includes/admin/payments/payments-history.php:157 -msgid "Products" -msgstr "Produits" - -# @ edd -#: includes/admin/export-functions.php:44 -msgid "Discounts," -msgstr "Remises," - -# @ edd -#: includes/admin/export-functions.php:45 -msgid "Amount paid" -msgstr "Somme payée" - -# @ edd -#: includes/admin/export-functions.php:46 -msgid "Payment method" -msgstr "Méthode de paiement" - -# @ edd -#: includes/admin/export-functions.php:47 -msgid "Key" -msgstr "Clé" - -# @ edd -#: includes/admin/export-functions.php:48 -#: includes/admin/downloads/dashboard-columns.php:33 -#: includes/admin/payments/payments-history.php:145 -#: includes/admin/payments/payments-history.php:159 -#: includes/templates/history-purchases.php:12 -msgid "Date" -msgstr "Date" - -# @ edd -#: includes/admin/export-functions.php:49 -#: includes/admin/payments/payments-history.php:147 -#: includes/admin/payments/payments-history.php:160 -msgid "User" -msgstr "Utilisateur" - -# @ edd -#: includes/admin/export-functions.php:50 -#: includes/admin/discounts/discount-codes.php:46 -#: includes/admin/discounts/discount-codes.php:59 -#: includes/admin/discounts/edit-discount.php:98 -#: includes/admin/payments/payments-history.php:149 -#: includes/admin/payments/payments-history.php:161 -msgid "Status" -msgstr "État" - -# @ edd -#: includes/admin/export-functions.php:111 -#: includes/admin/export-functions.php:119 -#: includes/admin/payments/payments-history.php:250 -msgid "none" -msgstr "aucun" - -# @ edd -#: includes/admin/export-functions.php:127 -#: includes/admin/payments/payments-history.php:285 -#: includes/admin/payments/payments-history.php:287 -msgid "guest" -msgstr "invité" - -# @ edd -#: includes/admin/export-functions.php:135 -#: includes/admin/payments/payments-history.php:298 -msgid "No payments recorded yet" -msgstr "Aucun paiement encore enregistré" - -#: includes/admin/export-functions.php:169 -msgid "Export not allowed for non-administrators." -msgstr "L'exportation n'est pas autorisé pour les non-administrateurs." - -#: includes/admin/thickbox.php:29 includes/admin/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "Insérer %s" - -# @ edd -#: includes/admin/thickbox.php:30 -msgid "Insert Download" -msgstr "Insérer téléchargement" - -# @ edd -#: includes/admin/thickbox.php:65 -msgid "You must choose a download" -msgstr "Vous devez sélectionner un téléchargement" - -# @ edd -#: includes/admin/thickbox.php:88 -#, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "" -"Utiliser le formulaire ci-dessous pour insérer le code macro pour acheter un " -"%s." - -# @ edd -#: includes/admin/thickbox.php:91 -#, php-format -msgid "Choose a %s" -msgstr "Choisir un %s" - -# @ edd -#: includes/admin/thickbox.php:100 -msgid "Choose a style" -msgstr "Sélectionner un style" - -# @ edd -#: includes/admin/thickbox.php:111 -msgid "Choose a button color" -msgstr "Sélectionnez une couleur pour le bouton" - -# @ edd -#: includes/admin/thickbox.php:120 -msgid "Link text . . ." -msgstr "Texte du lien . . ." - -# @ edd -#: includes/admin/thickbox.php:124 -msgid "Cancel" -msgstr "Annuler" - -# @ edd -#: includes/admin/thickbox.php:126 -msgid "Button Styles" -msgstr "Styles des boutons" - -# @ edd -#: includes/admin/discounts/add-discount.php:12 -msgid "Add New Discount" -msgstr "Ajouter une Nouvelle Promo" - -# @ edd -#: includes/admin/discounts/add-discount.php:18 -#: includes/admin/discounts/discount-codes.php:39 -#: includes/admin/discounts/discount-codes.php:52 -#: includes/admin/discounts/edit-discount.php:23 -#: includes/admin/downloads/dashboard-columns.php:26 -msgid "Name" -msgstr "Nom" - -# @ edd -#: includes/admin/discounts/add-discount.php:22 -#: includes/admin/discounts/edit-discount.php:27 -msgid "The name of this discount" -msgstr "Le nom de cette promo." - -# @ edd -#: includes/admin/discounts/add-discount.php:27 -#: includes/admin/discounts/discount-codes.php:40 -#: includes/admin/discounts/discount-codes.php:53 -#: includes/admin/discounts/edit-discount.php:32 -msgid "Code" -msgstr "Code" - -# @ edd -#: includes/admin/discounts/add-discount.php:31 -#: includes/admin/discounts/edit-discount.php:36 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "Encoder un code pour cette promo, par exemple 10POURCENT." - -# @ edd -#: includes/admin/discounts/add-discount.php:36 -#: includes/admin/discounts/edit-discount.php:41 -msgid "Type" -msgstr "Type" - -# @ edd -#: includes/admin/discounts/add-discount.php:40 -#: includes/admin/discounts/edit-discount.php:45 -msgid "Percentage" -msgstr "Pourcentage" - -# @ edd -#: includes/admin/discounts/add-discount.php:41 -#: includes/admin/discounts/edit-discount.php:46 -msgid "Flat amount" -msgstr "Montant fixe" - -# @ edd -#: includes/admin/discounts/add-discount.php:43 -#: includes/admin/discounts/edit-discount.php:48 -msgid "The kind of discount to apply for this discount." -msgstr "Le type de promo à lui appliquer." - -# @ edd -#: includes/admin/discounts/add-discount.php:48 -#: includes/admin/discounts/discount-codes.php:41 -#: includes/admin/discounts/discount-codes.php:54 -#: includes/admin/discounts/edit-discount.php:53 -#: includes/templates/history-purchases.php:13 -msgid "Amount" -msgstr "Montant" - -# @ edd -#: includes/admin/discounts/add-discount.php:52 -#: includes/admin/discounts/edit-discount.php:57 -msgid "The amount of this discount code." -msgstr "Le montant de ce code promo." - -# @ edd -#: includes/admin/discounts/add-discount.php:57 -#: includes/admin/discounts/edit-discount.php:62 -msgid "Start date" -msgstr "Date de début" - -# @ edd -#: includes/admin/discounts/add-discount.php:61 -#: includes/admin/discounts/edit-discount.php:66 -msgid "" -"Enter the start date for this discount code in the format of mm/dd/yyyy. For " -"no start date, leave blank. If entered, the discount can only be used after " -"or on this date." -msgstr "" -"Entrer la date de début pour ce code promo au format mm/jj/aaaa. Laissez le " -"champ vide pour ne pas appliquer de date de début. Si rempli, la promo ne " -"pourra être utilisée qu'à partir de cette date." - -# @ edd -#: includes/admin/discounts/add-discount.php:66 -#: includes/admin/discounts/edit-discount.php:71 -msgid "Expiration date" -msgstr "Date d'expiration" - -# @ edd -#: includes/admin/discounts/add-discount.php:70 -#: includes/admin/discounts/edit-discount.php:75 -msgid "" -"Enter the expiration date for this discount code in the format of mm/dd/" -"yyyy. For no expiration, leave blank" -msgstr "" -"Entrer la date d'expiration pour ce code promo au format mm/jj/aaaa. Laissez " -"vide pour ne pas appliquer de date d'expiration." - -# @ edd -#: includes/admin/discounts/add-discount.php:75 -#: includes/admin/discounts/edit-discount.php:89 -msgid "Minimum Amount" -msgstr "Montant minimum" - -# @ edd -#: includes/admin/discounts/add-discount.php:79 -#: includes/admin/discounts/edit-discount.php:93 -msgid "" -"The minimum amount that must be purchased before this discount can be used. " -"Leave blank for no minimum." -msgstr "" -"Le montant minimum qui doit être acheté avant que cette remise ne puisse " -"être utilisée. Laisser vide pour aucun minimum." - -# @ edd -#: includes/admin/discounts/add-discount.php:84 -#: includes/admin/discounts/discount-codes.php:43 -#: includes/admin/discounts/discount-codes.php:56 -#: includes/admin/discounts/edit-discount.php:80 -msgid "Max Uses" -msgstr "Nombre maximum d'utilisations" - -# @ edd -#: includes/admin/discounts/add-discount.php:88 -#: includes/admin/discounts/edit-discount.php:84 -msgid "" -"The maximum number of times this discount can be used. Leave blank for " -"unlimited." -msgstr "" -"Nombre maximum d'utilisations autorisées pour ce code promo. Laissez vide " -"pour un nombre illimité." - -# @ edd -#: includes/admin/discounts/add-discount.php:96 -msgid "Add Discount Code" -msgstr "Ajouter Code Promo" - -# @ edd -#: includes/admin/discounts/discount-codes.php:42 -#: includes/admin/discounts/discount-codes.php:55 -msgid "Uses" -msgstr "Utilisations" - -# @ edd -#: includes/admin/discounts/discount-codes.php:44 -#: includes/admin/discounts/discount-codes.php:57 -msgid "Start Date" -msgstr "Date de début" - -# @ edd -#: includes/admin/discounts/discount-codes.php:45 -#: includes/admin/discounts/discount-codes.php:58 -msgid "Expiration" -msgstr "Expiration" - -# @ edd -#: includes/admin/discounts/discount-codes.php:47 -#: includes/admin/discounts/discount-codes.php:60 -#: includes/templates/checkout_cart.php:8 -msgid "Actions" -msgstr "Actions" - -# @ edd -#: includes/admin/discounts/discount-codes.php:82 -#: includes/admin/discounts/discount-codes.php:84 -msgid "unlimited" -msgstr "illimité" - -# @ edd -#: includes/admin/discounts/discount-codes.php:93 -msgid "No start date" -msgstr "Pas de date de début" - -# @ edd -#: includes/admin/discounts/discount-codes.php:100 -msgid "Expired" -msgstr "Expiré" - -# @ edd -#: includes/admin/discounts/discount-codes.php:102 -msgid "no expiration" -msgstr "pas d'expiration" - -# @ edd -#: includes/admin/discounts/discount-codes.php:108 -#: includes/admin/payments/payments-history.php:183 -msgid "Edit" -msgstr "Modifier" - -# @ edd -#: includes/admin/discounts/discount-codes.php:110 -msgid "Deactivate" -msgstr "Désactiver" - -# @ edd -#: includes/admin/discounts/discount-codes.php:112 -msgid "Activate" -msgstr "Activer" - -# @ edd -#: includes/admin/discounts/discount-codes.php:114 -#: includes/admin/payments/payments-history.php:185 -msgid "Delete" -msgstr "Supprimer" - -# @ edd -#: includes/admin/discounts/discount-codes.php:119 -msgid "No discount codes have been created." -msgstr "Aucun code promo créé." - -# @ edd -#: includes/admin/discounts/edit-discount.php:13 -msgid "Something went wrong." -msgstr "Une erreur s'est produite." - -# @ edd -#: includes/admin/discounts/edit-discount.php:17 -msgid "Edit Discount" -msgstr "Éditer la promo" - -# @ edd -#: includes/admin/discounts/edit-discount.php:17 -#: includes/admin/payments/edit-payment.php:16 -msgid "Go Back" -msgstr "Retourner" - -# @ edd -#: includes/admin/discounts/edit-discount.php:102 -msgid "Active" -msgstr "Actif" - -# @ edd -#: includes/admin/discounts/edit-discount.php:103 -msgid "Inactive" -msgstr "Inactif" - -# @ edd -#: includes/admin/discounts/edit-discount.php:105 -msgid "The status of this discount code." -msgstr "L'état de ce code promo." - -# @ edd -#: includes/admin/discounts/edit-discount.php:115 -msgid "Update Discount Code" -msgstr "Mettre à jour Code Promo" - -# @ edd -#: includes/admin/downloads/dashboard-columns.php:29 -#: includes/admin/downloads/dashboard-columns.php:242 -#: includes/admin/downloads/metabox.php:171 -#: includes/admin/payments/payments-history.php:142 -#: includes/admin/payments/payments-history.php:158 -#: includes/admin/reporting/pdf-reports.php:66 -msgid "Price" -msgstr "Prix" - -# @ edd -#: includes/admin/downloads/dashboard-columns.php:32 -msgid "Short Code" -msgstr "Code de raccourci" - -# @ edd -#: includes/admin/downloads/dashboard-columns.php:200 -msgid "Show all categories" -msgstr "Montrer toutes les catégories" - -# @ edd -#: includes/admin/downloads/dashboard-columns.php:211 -msgid "Show all tags" -msgstr "Montrer tous les tags" - -#: includes/admin/downloads/dashboard-columns.php:239 -#, php-format -msgid "%s Data" -msgstr "%s Data" - -# @ edd -#: includes/admin/downloads/metabox.php:23 -#, php-format -msgid "%1$s Configuration" -msgstr "Configuration %1$s " - -# @ edd -#: includes/admin/downloads/metabox.php:26 -msgid "Product Notes" -msgstr "Notes du produit" - -# @ edd -#: includes/admin/downloads/metabox.php:29 -#, php-format -msgid "%1$s Stats" -msgstr "Statistiques %1$s " - -# @ edd -#: includes/admin/downloads/metabox.php:32 -msgid "Purchase Log" -msgstr "Compte-rendu d'achats" - -# @ edd -#: includes/admin/downloads/metabox.php:35 -msgid "File Download Log" -msgstr "Compte-rendu de téléchargement de fichiers" - -# @ edd -#: includes/admin/downloads/metabox.php:145 -msgid "Pricing Options:" -msgstr "Options de prix :" - -# @ edd -#: includes/admin/downloads/metabox.php:151 -msgid "Enable variable pricing" -msgstr "Permettre la tarification variable" - -#: includes/admin/downloads/metabox.php:170 -#: includes/admin/downloads/metabox.php:233 -msgid "Option Name" -msgstr "Nom de l'option" - -# @ edd -#: includes/admin/downloads/metabox.php:199 -msgid "Add New Price" -msgstr "Ajouter un nouveau prix" - -# @ edd -#: includes/admin/downloads/metabox.php:275 -msgid "File Downloads:" -msgstr "Téléchargements de fichiers :" - -# @ edd -#: includes/admin/downloads/metabox.php:284 -#: includes/admin/downloads/metabox.php:351 -msgid "File Name" -msgstr "Nom du fichier" - -# @ edd -#: includes/admin/downloads/metabox.php:285 -msgid "File URL" -msgstr "Adresse url du fichier" - -#: includes/admin/downloads/metabox.php:286 -msgid "Price Assignment" -msgstr "Affectation de prix" - -# @ edd -#: includes/admin/downloads/metabox.php:314 -msgid "Add New File" -msgstr "Ajouter un nouveau fichier" - -#: includes/admin/downloads/metabox.php:355 -msgid "http://" -msgstr "http://" - -# @ edd -#: includes/admin/downloads/metabox.php:358 -msgid "Upload a File" -msgstr "Télécharger un fichier" - -# @ edd -#: includes/admin/downloads/metabox.php:364 -msgid "All Prices" -msgstr "Prix catalogue" - -# @ edd -#: includes/admin/downloads/metabox.php:391 -msgid "Button Options:" -msgstr "Options du bouton :" - -# @ edd -#: includes/admin/downloads/metabox.php:397 -msgid "Disable the automatic output of the purchase button" -msgstr "Désactiver le formatage automatique du bouton d'achat" - -#: includes/admin/downloads/metabox.php:457 -msgid "" -"Special notes or instructions for this product. These notes will be added to " -"the purchase receipt." -msgstr "" -"Remarques particulières ou des instructions pour ce produit. Ces remarques " -"seront ajoutées à la facture d'achat." - -# @ edd -#: includes/admin/downloads/metabox.php:479 -msgid "Sales:" -msgstr "Ventes :" - -# @ edd -#: includes/admin/downloads/metabox.php:485 -msgid "Earnings:" -msgstr "Revenus :" - -# @ edd -#: includes/admin/downloads/metabox.php:521 -msgid "Sales Log" -msgstr "Compte-rendu de ventes" - -# @ edd -#: includes/admin/downloads/metabox.php:523 -msgid "Each sale for this download is listed below." -msgstr "Chaque vente pour ce téléchargement est affiché ci-dessous." - -# @ edd -#: includes/admin/downloads/metabox.php:537 -#: includes/admin/downloads/metabox.php:629 -msgid "Date:" -msgstr "Date:" - -# @ edd -#: includes/admin/downloads/metabox.php:541 -msgid "Buyer:" -msgstr "Acheteur :" - -# @ edd -#: includes/admin/downloads/metabox.php:545 -msgid "Purchase ID:" -msgstr "ID de l'achat :" - -# @ edd -#: includes/admin/downloads/metabox.php:553 -msgid "No sales yet" -msgstr "Pas encore de ventes" - -# @ edd -#: includes/admin/downloads/metabox.php:569 -#: includes/admin/downloads/metabox.php:666 -#: includes/admin/payments/payments-history.php:318 -msgid "Previous" -msgstr "Précédent" - -# @ edd -#: includes/admin/downloads/metabox.php:610 -msgid "Download Log" -msgstr "Compte-rendu de téléchargement" - -# @ edd -#: includes/admin/downloads/metabox.php:612 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "" -"Chaque fois qu'un fichier est téléchargé, l'opération est enregistrée ci-" -"dessous." - -# @ edd -#: includes/admin/downloads/metabox.php:633 -msgid "Downloaded by:" -msgstr "Téléchargé par :" - -# @ edd -#: includes/admin/downloads/metabox.php:637 -msgid "IP Address:" -msgstr "Adresse IP :" - -# @ edd -#: includes/admin/downloads/metabox.php:641 -msgid "File: " -msgstr "Fichier :" - -# @ edd -#: includes/admin/downloads/metabox.php:650 -msgid "No file downloads yet yet" -msgstr "Aucun fichier encore téléchargé" - -# @ edd -#: includes/admin/payments/edit-payment.php:22 -msgid "Buyer's Email" -msgstr "Adresse e-mail de l'acheteur" - -#: includes/admin/payments/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "Si nécessaire, vous pouvez mettre à jour l'e-mail de l'acheteur ici." - -# @ edd -#: includes/admin/payments/edit-payment.php:31 -msgid "Downloads Purchased" -msgstr "Téléchargements achetés" - -# @ edd -#: includes/admin/payments/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "Ajouter téléchargement à l'achat #%s" - -# @ edd -#: includes/admin/payments/edit-payment.php:43 -msgid "Add download to purchase" -msgstr "Ajouter téléchargement à l'achat" - -# @ edd -#: includes/admin/payments/edit-payment.php:48 -msgid "Payment Status" -msgstr "État du paiement" - -# @ edd -#: includes/admin/payments/edit-payment.php:64 -msgid "Send Purchase Receipt" -msgstr "Envoyer le reçu de paiement" - -#: includes/admin/payments/edit-payment.php:68 -msgid "" -"Check this box to send the purchase receipt, including all download links." -msgstr "" -"Cocher cette case pour envoyer le reçu d'achat, avec tous les liens de " -"téléchargement." - -# @ edd -#: includes/admin/payments/edit-payment.php:78 -msgid "Update Payment" -msgstr "Mettre à jour paiement" - -# @ edd -#: includes/admin/payments/edit-payment.php:91 -msgid "Add Selected Downloads" -msgstr "Ajouter les téléchargements sélectionnés" - -# @ edd -#: includes/admin/payments/edit-payment.php:92 -#: includes/admin/payments/payments-history.php:274 -msgid "Close" -msgstr "Fermer" - -#: includes/admin/payments/payments-history.php:90 -msgid "All" -msgstr "Tous" - -# @ edd -#: includes/admin/payments/payments-history.php:95 -msgid "Completed" -msgstr "Terminé" - -# @ edd -#: includes/admin/payments/payments-history.php:106 -msgid "Export" -msgstr "Exporté" - -# @ edd -#: includes/admin/payments/payments-history.php:110 -msgid "Payment mode" -msgstr "Mode de paiement" - -# @ edd -#: includes/admin/payments/payments-history.php:112 -msgid "Live" -msgstr "Réel" - -# @ edd -#: includes/admin/payments/payments-history.php:113 -msgid "Test" -msgstr "Test" - -# @ edd -#: includes/admin/payments/payments-history.php:123 -msgid "Payments per page" -msgstr "Paiements par page" - -# @ edd -#: includes/admin/payments/payments-history.php:125 -msgid "Show" -msgstr "Afficher" - -# @ edd -#: includes/admin/payments/payments-history.php:131 -msgid "Showing payments for: " -msgstr "Afficher les paiements de :" - -# @ edd -#: includes/admin/payments/payments-history.php:131 -msgid "clear" -msgstr "effacer" - -# @ edd -#: includes/admin/payments/payments-history.php:184 -msgid "Resend Purchase Receipt" -msgstr "Renvoyer la facture d'achat" - -# @ edd -#: includes/admin/payments/payments-history.php:197 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "Détails de la commande pour le paiement #%s" - -# @ edd -#: includes/admin/payments/payments-history.php:197 -msgid "View Order Details" -msgstr "Afficher les détails de la commande" - -# @ edd -#: includes/admin/payments/payments-history.php:205 -msgid "Purchased File" -msgstr "Fichier acheté" - -# @ edd -#: includes/admin/payments/payments-history.php:205 -msgid "Purchased Files" -msgstr "Fichiers achetés" - -#: includes/admin/payments/payments-history.php:249 -msgid "Date and Time:" -msgstr "Date et Heure :" - -# @ edd -#: includes/admin/payments/payments-history.php:250 -msgid "Discount used:" -msgstr "Code promo utilisé :" - -# @ edd -#: includes/admin/payments/payments-history.php:251 -msgid "Total:" -msgstr "Total :" - -# @ edd -#: includes/admin/payments/payments-history.php:254 -msgid "Buyer's Personal Details:" -msgstr "Données personnelles de l'acheteur :" - -# @ edd -#: includes/admin/payments/payments-history.php:256 -msgid "Name:" -msgstr "Nom :" - -# @ edd -#: includes/admin/payments/payments-history.php:257 -msgid "Email:" -msgstr "E-mail :" - -# @ edd -#: includes/admin/payments/payments-history.php:266 -msgid "Payment Method:" -msgstr "Méthode de Paiement :" - -# @ edd -#: includes/admin/payments/payments-history.php:271 -msgid "Purchase Key" -msgstr "Clef d'achat" - -# @ edd -#: includes/admin/payments/payments-history.php:304 -msgid "Total Earnings:" -msgstr "Total des gains :" - -# @ edd -#: includes/admin/reporting/graphing.php:42 -#, php-format -msgid "%s Performance in Sales" -msgstr "%s Rendement des Ventes" - -# @ edd -#: includes/admin/reporting/graphing.php:88 -#, php-format -msgid "%s Performance in Earnings" -msgstr "%s Rendement des Revenus" - -# @ edd -#: includes/admin/reporting/graphing.php:136 -msgid "Earnings per month" -msgstr "Revenus mensuels" - -#: includes/admin/reporting/graphing.php:168 -msgid "Day" -msgstr "Jour" - -#: includes/admin/reporting/graphing.php:189 -#, php-format -msgid "Earnings per day for last %s days" -msgstr "Bénéfice par jour pour les %s derniers jours" - -# @ edd -#: includes/admin/reporting/graphing.php:236 -msgid "Sales per month" -msgstr "Revenus mensuel" - -#: includes/admin/reporting/pdf-reports.php:36 -msgid "to" -msgstr "à" - -#: includes/admin/reporting/pdf-reports.php:41 -#: includes/admin/reporting/pdf-reports.php:52 -msgid "Sales and earnings reports for the current year for all products" -msgstr "" -"Rapports des ventes et des bénéfices pour l'année en cours pour tous les " -"produits" - -# @ edd -#: includes/admin/reporting/pdf-reports.php:42 -#: includes/admin/reporting/pdf-reports.php:43 -msgid "Easy Digital Downloads" -msgstr "Easy Digital Downloads" - -#: includes/admin/reporting/pdf-reports.php:57 -msgid "Date Range: " -msgstr "Plage de dates :" - -#: includes/admin/reporting/pdf-reports.php:61 -msgid "Table View" -msgstr "Vu en tableau" - -# @ edd -#: includes/admin/reporting/pdf-reports.php:65 -msgid "Product Name" -msgstr "Nom du produit" - -#: includes/admin/reporting/pdf-reports.php:69 -msgid "Number of Sales" -msgstr "Nombre de ventes" - -# @ edd -#: includes/admin/reporting/pdf-reports.php:70 -msgid "Earnings to Date" -msgstr "Gains à ce jour" - -# @ edd -#: includes/admin/reporting/pdf-reports.php:112 -msgid "No Downloads found." -msgstr "Aucun téléchargement trouvé." - -#: includes/admin/reporting/pdf-reports.php:119 -msgid "Graph View" -msgstr "Vu en graphique" - -#: includes/admin/reporting/pdf-reports.php:212 -msgid "Sales and Earnings by Month for all Products" -msgstr "Ventes et bénéfices par mois pour tous les produits" - -#: includes/admin/reporting/pdf-reports.php:226 -msgid "Jan" -msgstr "Jan" - -#: includes/admin/reporting/pdf-reports.php:227 -msgid "Feb" -msgstr "Fév" - -#: includes/admin/reporting/pdf-reports.php:228 -msgid "Mar" -msgstr "Mar" - -#: includes/admin/reporting/pdf-reports.php:229 -msgid "Apr" -msgstr "Avr" - -#: includes/admin/reporting/pdf-reports.php:230 -msgid "May" -msgstr "Mai" - -#: includes/admin/reporting/pdf-reports.php:231 -msgid "June" -msgstr "Juin" - -#: includes/admin/reporting/pdf-reports.php:232 -msgid "July" -msgstr "Juillet" - -#: includes/admin/reporting/pdf-reports.php:233 -msgid "Aug" -msgstr "Août" - -#: includes/admin/reporting/pdf-reports.php:234 -msgid "Sept" -msgstr "Sept" - -#: includes/admin/reporting/pdf-reports.php:235 -msgid "Oct" -msgstr "Oct" - -#: includes/admin/reporting/pdf-reports.php:236 -msgid "Nov" -msgstr "Nov" - -#: includes/admin/reporting/pdf-reports.php:237 -msgid "Dec" -msgstr "Déc" - -#: includes/admin/reporting/reports.php:41 -msgid "Download Sales and Earnings PDF Report for all Products" -msgstr "" -"Télécharger le PDF du rapport des ventes et des bénéfices pour tous les " -"produits" - -#: includes/admin/reporting/reports.php:42 -msgid "Download a CSV Customers List" -msgstr "Télécharger une liste CSV de clients" - -# @ edd -#: includes/admin/reporting/reports.php:44 -msgid "" -"Please Note: Transactions created while in test mode are not included on " -"this page or in the PDF reports." -msgstr "" -"Veuillez noter : Les transactions créées en mode test ne sont pas incluses " -"sur cette page ou dans les rapports en PDF." - -# @ edd -#: includes/admin/settings/settings.php:33 -msgid "General" -msgstr "Généraux" - -# @ edd -#: includes/admin/settings/settings.php:35 -msgid "Emails" -msgstr "E-mails" - -# @ edd -#: includes/admin/settings/settings.php:36 -msgid "Styles" -msgstr "Styles" - -# @ edd -#: includes/admin/settings/settings.php:37 -msgid "Misc" -msgstr "Divers" - -# @ edd -#: includes/gateways/paypal.php:271 -msgid "Invalid IPN" -msgstr "IPN incorrect" - -# @ edd -#: includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "Nom de l'article" - -# @ edd -#: includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "Prix de l'article" - -# @ edd -#: includes/templates/checkout_cart.php:49 -msgid "Total" -msgstr "Total" - -# @ edd -#: includes/templates/history-downloads.php:10 -msgid "Download Name" -msgstr "Nom du téléchargement" - -# @ edd -#: includes/templates/history-downloads.php:12 -#: includes/templates/history-purchases.php:14 -msgid "Files" -msgstr "Fichiers" - -# @ edd -#: includes/templates/history-downloads.php:68 -msgid "You have not purchased any downloads" -msgstr "Vous n'avez acheté aucun téléchargement" - -# @ edd -#: includes/templates/history-purchases.php:11 -msgid "Purchase ID" -msgstr "ID de l'achat " - -# @ edd -#: includes/templates/history-purchases.php:61 -msgid "You have not made any purchases" -msgstr "Vous n'avez fait aucun achat" - -#~ msgid "9.99" -#~ msgstr "9.99" - -#~ msgctxt "Variable price preposition. $2.99 for {X}" -#~ msgid "for" -#~ msgstr "pour" diff --git a/languages/edd-it_IT.mo b/languages/edd-it_IT.mo deleted file mode 100755 index 4afb89d48bc..00000000000 Binary files a/languages/edd-it_IT.mo and /dev/null differ diff --git a/languages/edd-it_IT.po b/languages/edd-it_IT.po deleted file mode 100755 index 05ba06fec67..00000000000 --- a/languages/edd-it_IT.po +++ /dev/null @@ -1,4667 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -<<<<<<< HEAD -"POT-Creation-Date: 2012-09-11 14:07-0600\n" -"PO-Revision-Date: 2012-09-11 14:07-0600\n" -======= -"POT-Creation-Date: 2012-09-11 17:41-0600\n" -"PO-Revision-Date: 2012-09-11 17:41-0600\n" ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -"Last-Translator: Pippin Williamson \n" -"Language-Team: Tronic Labs \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_n;_x\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-Language: Italian\n" -"X-Poedit-Country: ITALY\n" -"X-Poedit-SearchPath-0: ..\n" -"X-Poedit-SearchPath-1: ../includes\n" -"X-Poedit-SearchPath-2: ../includes/admin-pages\n" -"X-Poedit-SearchPath-3: ../includes/admin-pages/forms\n" -"X-Poedit-SearchPath-4: ../includes/gateways\n" -"X-Poedit-SearchPath-5: .\n" - -<<<<<<< HEAD -#: ../includes/admin-notices.php:24 -======= -#: ../includes/template-functions.php:48 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "No checkout page has been configured." -msgstr "Non ci sono codici sconto che sono stati creati." - -<<<<<<< HEAD -#: ../includes/admin-notices.php:27 -#, fuzzy -msgid "There was a problem updating your discount code, please try again." -msgstr "C'è stato un problema aggiornare il codice di sconto, si prega di riprovare." - -#: ../includes/admin-notices.php:30 -msgid "The payment has been deleted." -msgstr "" - -#: ../includes/admin-notices.php:33 -msgid "The purchase receipt has been resent." -msgstr "" - -#: ../includes/admin-notices.php:38 -#, php-format -msgid "The payment history needs updated. %s" -msgstr "" - -#: ../includes/admin-notices.php:38 -#, fuzzy -msgid "Click to Upgrade" -msgstr "Clicca %s per l'acquisto di nuovo." - -#: ../includes/admin-notices.php:55 -msgid "There seems to be an issue with the server. Please try again in a few minutes." -msgstr "" - -#: ../includes/admin-pages.php:26 -======= -#: ../includes/template-functions.php:60 -#: ../includes/thickbox.php:61 -msgid "Purchase" -msgstr "Acquista" - -#: ../includes/template-functions.php:137 -#: ../includes/template-functions.php:158 -#: ../includes/cart-template.php:46 -#: ../includes/cart-template.php:49 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Checkout" -msgstr "Paga" - -<<<<<<< HEAD -#: ../includes/admin-pages.php:27 -======= -#: ../includes/template-functions.php:167 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "added to your cart" -msgstr "aggiunto al tuo carrello" - -<<<<<<< HEAD -#: ../includes/admin-pages.php:28 -#, fuzzy -msgid "Earnings and Sales Reports" -msgstr "Utili e rapporti di vendita" - -#: ../includes/admin-pages.php:28 -======= -#: ../includes/template-functions.php:263 -msgid "Gray" -msgstr "" - -#: ../includes/template-functions.php:264 -msgid "Pink" -msgstr "" - -#: ../includes/template-functions.php:265 -msgid "Blue" -msgstr "" - -#: ../includes/template-functions.php:266 -msgid "Green" -msgstr "" - -#: ../includes/template-functions.php:267 -msgid "Teal" -msgstr "" - -#: ../includes/template-functions.php:268 -msgid "Black" -msgstr "" - -#: ../includes/template-functions.php:269 -msgid "Dark Gray" -msgstr "" - -#: ../includes/template-functions.php:270 -msgid "Orange" -msgstr "" - -#: ../includes/template-functions.php:271 -msgid "Purple" -msgstr "" - -#: ../includes/template-functions.php:272 -msgid "Slate" -msgstr "" - -#: ../includes/template-functions.php:295 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "You have already purchased this item, but you may purchase it again." -msgstr "Hai già aggiunto questo prodotto al tuo carrello" - -<<<<<<< HEAD -#: ../includes/admin-pages.php:29 -======= -#: ../includes/export-functions.php:31 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "ID" -msgstr "ID" - -<<<<<<< HEAD -#: ../includes/admin-pages.php:29 -======= -#: ../includes/export-functions.php:32 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Email" -msgstr "Email" - -<<<<<<< HEAD -#: ../includes/admin-pages.php:30 -#, fuzzy -msgid "Easy Digital Download Add Ons" -msgstr "Easy Digital Download Impostazioni" - -#: ../includes/admin-pages.php:30 -msgid "Add Ons" -msgstr "" - -#: ../includes/ajax-functions.php:102 -#, fuzzy -msgid "This discount code has been used already" -msgstr "Non ci sono codici sconto che sono stati creati." - -#: ../includes/ajax-functions.php:118 -#, fuzzy -msgid "The discount you entered is invalid" -msgstr "Lo sconto che hai inserito non è valido" - -#: ../includes/cart-functions.php:435 -#, fuzzy, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "Hai già aggiunto questo prodotto al tuo carrello" - -#: ../includes/cart-functions.php:436 -#, fuzzy -msgid "Checkout." -msgstr "Paga" - -#: ../includes/cart-template.php:46 -#: ../includes/cart-template.php:49 -#, fuzzy -msgid "Checkout" -msgstr "Paga" - -#: ../includes/cart-template.php:80 -======= -#: ../includes/export-functions.php:33 -#, fuzzy -msgid "First Name" -msgstr "Nome" - -#: ../includes/export-functions.php:34 -#, fuzzy -msgid "Last Name" -msgstr "Cognome" - -#: ../includes/export-functions.php:35 -#, fuzzy -msgid "Products" -msgstr "Prodotti" - -#: ../includes/export-functions.php:36 -#, fuzzy -msgid "Discounts," -msgstr "Sconto" - -#: ../includes/export-functions.php:37 -#, fuzzy -msgid "Amount paid" -msgstr "Importo:" - -#: ../includes/export-functions.php:38 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Payment method" -msgstr "Modalità di pagamento" - -<<<<<<< HEAD -#: ../includes/cart-template.php:99 -#, fuzzy -msgid "Your cart is empty." -msgstr "Il carrello è vuoto" - -#: ../includes/checkout-template.php:75 -======= -#: ../includes/export-functions.php:39 -#, fuzzy -msgid "Key" -msgstr "Chiave" - -#: ../includes/export-functions.php:40 -#, fuzzy -msgid "Date" -msgstr "Data" - -#: ../includes/export-functions.php:41 -#, fuzzy -msgid "User" -msgstr "Utente" - -#: ../includes/export-functions.php:42 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Status" -msgstr "Stato" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:86 -======= -#: ../includes/export-functions.php:103 -#: ../includes/export-functions.php:111 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "none" -msgstr "nessuno" - -#: ../includes/export-functions.php:119 -msgid "guest" -msgstr "" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:141 -#, fuzzy -msgid "Personal Info" -msgstr "Pagamenti Info" - -#: ../includes/checkout-template.php:144 -======= -#: ../includes/export-functions.php:127 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "No payments recorded yet" -msgstr "Nessun pagamento ancora registrato" - -#: ../includes/export-functions.php:161 -msgid "Export not allowed for non-administrators." -msgstr "" - -#: ../includes/payment-functions.php:278 -msgid "Pending" -msgstr "In attesa" - -#: ../includes/payment-functions.php:279 -msgid "Complete" -msgstr "Completato" - -#: ../includes/payment-functions.php:280 -msgid "Refunded" -msgstr "" - -#: ../includes/email-template.php:23 -msgid "Default Template" -msgstr "" - -#: ../includes/email-template.php:24 -msgid "No template, plain text only" -msgstr "" - -#: ../includes/email-template.php:107 -msgid "Sample Product Title" -msgstr "" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:145 -======= -#: ../includes/email-template.php:110 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Sample Download File Name" -msgstr "Download Nome" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:149 -#: ../includes/checkout-template.php:150 -#: ../includes/checkout-template.php:403 -#: ../includes/checkout-template.php:404 -======= -#: ../includes/email-template.php:158 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Purchase Receipt Preview" -msgstr "Ricevuta d'acquisto" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:153 -#: ../includes/checkout-template.php:407 -======= -#: ../includes/email-template.php:158 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Preview Purchase Receipt" -msgstr "Rispedisci ricevuta d'acquisto" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:154 -#: ../includes/checkout-template.php:408 -======= -#: ../includes/email-template.php:199 -msgid "Dear" -msgstr "" - -#: ../includes/email-template.php:200 -msgid "Thank you for your purchase. Please click on the link(s) below to download your files." -msgstr "" - -#: ../includes/cart-template.php:80 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "remove" -msgstr "rimuovere" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:166 -======= -#: ../includes/cart-template.php:99 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Your cart is empty." -msgstr "Il carrello è vuoto" - -#: ../includes/thickbox.php:29 -#: ../includes/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:168 -======= -#: ../includes/thickbox.php:30 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Insert Download" -msgstr "Inserisci Download" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:170 -======= -#: ../includes/thickbox.php:65 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "You must choose a download" -msgstr "Devi scegliere un download" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:197 -msgid "Show Terms" -msgstr "" - -#: ../includes/checkout-template.php:198 -msgid "Hide Terms" -msgstr "" - -#: ../includes/checkout-template.php:201 -msgid "Agree to Terms?" -msgstr "" - -#: ../includes/checkout-template.php:218 -#: ../includes/dashboard-columns.php:58 -msgid "Purchase" -msgstr "Acquista" - -#: ../includes/checkout-template.php:226 -msgid "Go back" -msgstr "Torna dietro" - -#: ../includes/checkout-template.php:230 -======= -#: ../includes/thickbox.php:88 -#, fuzzy, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "Usa il modulo qui sotto per inserire lo short code per l'acquisto di un download." - -#: ../includes/thickbox.php:91 -#, fuzzy, php-format -msgid "Choose a %s" -msgstr "Scegli uno stile" - -#: ../includes/thickbox.php:100 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Choose a style" -msgstr "Scegli uno stile" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:259 -#, fuzzy -msgid "Credit Card Info" -msgstr "Carta di Credito Info" - -#: ../includes/checkout-template.php:261 -#, fuzzy -msgid "Card name" -msgstr "Biglietto da visita" - -#: ../includes/checkout-template.php:262 -======= -#: ../includes/thickbox.php:111 -msgid "Choose a button color" -msgstr "Scegli un colore per il pulsante" - -#: ../includes/thickbox.php:120 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Link text . . ." -msgstr "Link testo. . ." - -<<<<<<< HEAD -#: ../includes/checkout-template.php:265 -======= -#: ../includes/thickbox.php:124 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Cancel" -msgstr "Annullare" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:266 -======= -#: ../includes/thickbox.php:126 -msgid "Button Styles" -msgstr "Stili Pulsante" - -#: ../includes/register-settings.php:41 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Test Mode" -msgstr "Test" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:269 -======= -#: ../includes/register-settings.php:42 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "Mentre si è in modalità di prova non vengono elaborate le transazioni reali. Per utilizzare appieno la modalità di test, è necessario disporre di uno (test) sandbox account per il gateway di pagamento che si sta testando." - -<<<<<<< HEAD -#: ../includes/checkout-template.php:270 -======= -#: ../includes/register-settings.php:47 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Checkout Page" -msgstr "Paga" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:276 -======= -#: ../includes/register-settings.php:48 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "Questa è la pagina di pagamento in cui gli acquirenti porteranno a termine gli acquisti" - -#: ../includes/register-settings.php:54 -msgid "Success Page" -msgstr "Pagina Successo" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:278 -======= -#: ../includes/register-settings.php:55 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "Questa è la pagina acquirenti in cui saranno reindirizzati dopo aver completato i loro acquisti" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:279 -msgid "Expiration (MM/YYYY)" -msgstr "Scadenza (MM/AAAA)" - -#: ../includes/checkout-template.php:309 -======= -#: ../includes/register-settings.php:61 -msgid "Download Links on Success Page" -msgstr "" - -#: ../includes/register-settings.php:62 -msgid "Show a list of all download links on the success page after completing a purchase?" -msgstr "" - -#: ../includes/register-settings.php:67 -#, fuzzy -msgid "Currency Settings" -msgstr "Impostazioni Valuta" - -#: ../includes/register-settings.php:68 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Configure the currency options" -msgstr "Configurare le opzioni su valute" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:310 -======= -#: ../includes/register-settings.php:73 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Currency" -msgstr "Valuta" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:313 -======= -#: ../includes/register-settings.php:74 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "Scegli la tua valuta. Si noti che alcuni gateway di pagamento hanno delle restrizioni valutarie." - -<<<<<<< HEAD -#: ../includes/checkout-template.php:314 -======= -#: ../includes/register-settings.php:80 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Currency Position" -msgstr "Posizione Valuta" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:317 -======= -#: ../includes/register-settings.php:81 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Choose the location of the currency sign." -msgstr "Scegliere la posizione del simbolo della valuta." - -<<<<<<< HEAD -#: ../includes/checkout-template.php:318 -======= -#: ../includes/register-settings.php:84 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Before - $10" -msgstr "Prima - $10" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:329 -#, fuzzy -msgid "Billing Country" -msgstr "Città per la Fatturazione" - -#: ../includes/checkout-template.php:332 -======= -#: ../includes/register-settings.php:85 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "After - 10$" -msgstr "Dopo - 10$" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:349 -======= -#: ../includes/register-settings.php:90 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Thousands Separator" -msgstr "Separatore delle migliaia" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:352 -======= -#: ../includes/register-settings.php:91 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The symbol (usually , or .) to separate thousands" -msgstr "Il simbolo (di solito , oppure .) per separare migliaia" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:353 -======= -#: ../includes/register-settings.php:98 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Decimal Separator" -msgstr "Separatore decimale" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:380 -#, fuzzy -msgid "Already have an account?" -msgstr "Hai già un account." - -#: ../includes/checkout-template.php:380 -#, fuzzy -msgid "Login" -msgstr "Accesso" - -#: ../includes/checkout-template.php:382 -#, fuzzy -msgid "Create an account" -msgstr "Crea un account" - -#: ../includes/checkout-template.php:382 -msgid "(optional)" -msgstr "" - -#: ../includes/checkout-template.php:385 -#: ../includes/checkout-template.php:386 -#: ../includes/checkout-template.php:433 -#, fuzzy -msgid "Username" -msgstr "Nome utente" - -#: ../includes/checkout-template.php:389 -#: ../includes/checkout-template.php:390 -#: ../includes/checkout-template.php:437 -======= -#: ../includes/register-settings.php:99 -#, fuzzy -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "Il simbolo (di solito , oppure .) per separare le cifre decimale" - -#: ../includes/register-settings.php:110 -#, fuzzy -msgid "Payment Gateways" -msgstr "Gateway di pagamento" - -#: ../includes/register-settings.php:111 -#, fuzzy -msgid "Choose the payment gateways you want to enable." -msgstr "Scegli i gateway di pagamento che si desidera attivare." - -#: ../includes/register-settings.php:117 -#, fuzzy -msgid "Accepted Payment Method Icons" -msgstr "Metodi di pagamento accettati" - -#: ../includes/register-settings.php:118 -#, fuzzy -msgid "Display icons for the selected payment methods" -msgstr "Visualizzare le icone per i metodi di pagamento selezionati" - -#: ../includes/register-settings.php:118 -#, fuzzy -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "Sarà inoltre necessario configurare le impostazioni del gateway se si accetta carte di credito" - -#: ../includes/register-settings.php:131 -#, fuzzy -msgid "PayPal Settings" -msgstr "Impostazioni PayPal" - -#: ../includes/register-settings.php:132 -#, fuzzy -msgid "Configure the PayPal settings" -msgstr "Configurare le impostazioni PayPal" - -#: ../includes/register-settings.php:137 -#, fuzzy -msgid "PayPal Email" -msgstr "Email PayPal" - -#: ../includes/register-settings.php:138 -#, fuzzy -msgid "Enter your PayPal account's email" -msgstr "Inserisci la tua email account PayPal" - -#: ../includes/register-settings.php:144 -#, fuzzy -msgid "Alternate PayPal Purchase Verification" -msgstr "Alternare verifica Acquisto PayPal" - -#: ../includes/register-settings.php:145 -#, fuzzy -msgid "If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal." -msgstr "Se i pagamenti non sono sempre contrassegnati come completati, selezionare questa opzione" - -#: ../includes/register-settings.php:150 -#, fuzzy -msgid "Disable PayPal IPN Verification" -msgstr "Alternare verifica Acquisto PayPal" - -#: ../includes/register-settings.php:151 -#, fuzzy -msgid "If payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases." -msgstr "Se i pagamenti non sono sempre contrassegnati come completati, e la disattivazione cURL non risolve il problema, quindi selezionare questa casella" - -#: ../includes/register-settings.php:160 -msgid "Email Template" -msgstr "" - -#: ../includes/register-settings.php:161 -msgid "Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" to see the new template." -msgstr "" - -#: ../includes/register-settings.php:173 -#, fuzzy -msgid "From Name" -msgstr "Da Nome" - -#: ../includes/register-settings.php:174 -#, fuzzy -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "Nome da cui proviene la vendita. Questo probabilmente dovrebbe essere il vostro sito o nome del negozio." - -#: ../includes/register-settings.php:179 -#, fuzzy -msgid "From Email" -msgstr "Da E-mail" - -#: ../includes/register-settings.php:180 -#, fuzzy -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "Email da usare nel modulo della ricevuta. Questo funzionerà come \"da\" e \"rispondere-a\" indirizzo." - -#: ../includes/register-settings.php:185 -#, fuzzy -msgid "Purchase Email Subject" -msgstr "Soggetto E-Mail Acquisto" - -#: ../includes/register-settings.php:186 -#, fuzzy -msgid "Enter the subject line for the purchase receipt email" -msgstr "Inserire la liena del soggetto della e-mail per la ricevuta d'acquisto" - -#: ../includes/register-settings.php:191 -#, fuzzy -msgid "Purchase Receipt" -msgstr "Ricevuta d'acquisto" - -#: ../includes/register-settings.php:192 -#, fuzzy -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "Inserisci l'indirizzo email che viene inviato agli utenti dopo aver completato un acquisto di successo. HTML è accettata. Tags template disponibili:" - -#: ../includes/register-settings.php:193 -#, fuzzy -msgid "A list of download URLs for each download purchased" -msgstr "Un elenco di URL di download per ogni download acquistato" - -#: ../includes/register-settings.php:194 -#, fuzzy -msgid "The buyer's name" -msgstr "Nome Articolo" - -#: ../includes/register-settings.php:195 -#, fuzzy -msgid "The date of the purchase" -msgstr "La data di acquisto" - -#: ../includes/register-settings.php:196 -#, fuzzy -msgid "The total price of the purchase" -msgstr "La data di acquisto" - -#: ../includes/register-settings.php:197 -#, fuzzy -msgid "The unique ID number for this purchase receipt" -msgstr "Inserire la liena del soggetto della e-mail per la ricevuta d'acquisto" - -#: ../includes/register-settings.php:198 -#, fuzzy -msgid "The method of payment used for this purchase" -msgstr "La data di acquisto" - -#: ../includes/register-settings.php:199 -#, fuzzy -msgid "Your site name" -msgstr "Il tuo nome del sito" - -#: ../includes/register-settings.php:208 -msgid "Disable Styles" -msgstr "" - -#: ../includes/register-settings.php:209 -msgid "Check this to disable all included styling" -msgstr "" - -#: ../includes/register-settings.php:214 -#, fuzzy -msgid "Checkout Button Color" -msgstr "Colore Pulsante " - -#: ../includes/register-settings.php:215 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "" - -#: ../includes/register-settings.php:225 -#, fuzzy -msgid "Disable Ajax" -msgstr "Abilitare Ajax" - -#: ../includes/register-settings.php:226 -#, fuzzy -msgid "Check this to disable AJAX for the shopping cart." -msgstr "Selezionare questa opzione per abilitare AJAX per il carrello della spesa." - -#: ../includes/register-settings.php:231 -#, fuzzy -msgid "Enable jQuery Validation" -msgstr "Abilita convalida jQuery" - -#: ../includes/register-settings.php:232 -#, fuzzy -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "Selezionare questa opzione per abilitare la convalida jQuery sul modulo di pagamento." - -#: ../includes/register-settings.php:237 -#, fuzzy -msgid "Disable Guest Checkout" -msgstr "Disabilita Ospiti Pagamento" - -#: ../includes/register-settings.php:238 -msgid "Require that users be logged-in to purchase files." -msgstr "Si richiede che gli utenti siano registrati per l'acquisto di file." - -#: ../includes/register-settings.php:243 -#, fuzzy -msgid "Show Register / Login Form?" -msgstr "Mostra Registrati / Registrazione Modulo?" - -#: ../includes/register-settings.php:244 -#, fuzzy -msgid "Display the registration and login forms on the checkout page for non-logged-in users." -msgstr "Visualizzare i moduli registrazione e di login nella pagina di pagamento per gli utenti non loggati." - -#: ../includes/register-settings.php:249 -#, fuzzy -msgid "Download Link Expiration" -msgstr "Configurazione Download" - -#: ../includes/register-settings.php:250 -msgid "How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours." -msgstr "" - -#: ../includes/register-settings.php:256 -#, fuzzy -msgid "Disable Redownload?" -msgstr "Disabilita Redownload?" - -#: ../includes/register-settings.php:257 -#, fuzzy -msgid "Check this if you do not want to allow users to redownload items from their purchase history." -msgstr "Selezionare questa opzione se non si desidera consentire agli utenti di scaricare di nuovo gli elementi dalla loro cronologia degli acquisti" - -#: ../includes/register-settings.php:262 -msgid "Terms of Agreement" -msgstr "" - -#: ../includes/register-settings.php:268 -msgid "Agree to Terms" -msgstr "" - -#: ../includes/register-settings.php:269 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing." -msgstr "" - -#: ../includes/register-settings.php:274 -msgid "Agree to Terms Label" -msgstr "" - -#: ../includes/register-settings.php:275 -msgid "Label shown next to the agree to terms check box." -msgstr "" - -#: ../includes/register-settings.php:281 -msgid "Agreement Text" -msgstr "" - -#: ../includes/register-settings.php:282 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "" - -#: ../includes/register-settings.php:287 -#, fuzzy -msgid "Complete Purchase Text" -msgstr "Testo per tasto Acquisto" - -#: ../includes/register-settings.php:288 -#, fuzzy -msgid "The button label for completing a purchase." -msgstr "Questa è la pagina acquirenti in cui saranno reindirizzati dopo aver completato i loro acquisti" - -#: ../includes/register-settings.php:314 -#, fuzzy -msgid "General Settings" -msgstr "Impostazioni generali" - -#: ../includes/register-settings.php:340 -#, fuzzy -msgid "Payment Gateway Settings" -msgstr "Impostazioni gateway di pagamento" - -#: ../includes/register-settings.php:366 -#, fuzzy -msgid "Email Settings" -msgstr "Impostazioni e-mail" - -#: ../includes/register-settings.php:392 -#, fuzzy -msgid "Style Settings" -msgstr "Impostazioni PayPal" - -#: ../includes/register-settings.php:419 -#, fuzzy -msgid "Misc Settings" -msgstr "Impostazioni varie" - -#: ../includes/register-settings.php:708 -#, fuzzy -msgid "Upload File" -msgstr "Carica file" - -#: ../includes/register-settings.php:746 -#, fuzzy -msgid "Settings Updated" -msgstr "Impostazioni" - -#: ../includes/cart-functions.php:435 -#, fuzzy, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "Hai già aggiunto questo prodotto al tuo carrello" - -#: ../includes/cart-functions.php:436 -#, fuzzy -msgid "Checkout." -msgstr "Paga" - -#: ../includes/dashboard-columns.php:26 -#, fuzzy -msgid "Name" -msgstr "Nome" - -#: ../includes/dashboard-columns.php:27 -#: ../includes/pdf-reports.php:67 -#: ../includes/widgets.php:169 -#, fuzzy -msgid "Categories" -msgstr "Categorie" - -#: ../includes/dashboard-columns.php:28 -#: ../includes/pdf-reports.php:68 -#: ../includes/widgets.php:170 -#, fuzzy -msgid "Tags" -msgstr "Tags" - -#: ../includes/dashboard-columns.php:29 -#: ../includes/dashboard-columns.php:231 -#: ../includes/pdf-reports.php:66 -#, fuzzy -msgid "Price" -msgstr "Prezzo" - -#: ../includes/dashboard-columns.php:30 -#: ../includes/pdf-reports.php:208 -#: ../includes/graphing.php:32 -#: ../includes/graphing.php:222 -#, fuzzy -msgid "Sales" -msgstr "Vendite" - -#: ../includes/dashboard-columns.php:31 -#: ../includes/pdf-reports.php:191 -#: ../includes/graphing.php:78 -#: ../includes/graphing.php:122 -#: ../includes/graphing.php:169 -#, fuzzy -msgid "Earnings" -msgstr "Guadagni" - -#: ../includes/dashboard-columns.php:32 -#: ../includes/metabox.php:346 -#, fuzzy -msgid "Short Code" -msgstr "Short Code" - -#: ../includes/dashboard-columns.php:189 -#, fuzzy -msgid "Show all categories" -msgstr "Mostra tutte le categorie" - -#: ../includes/dashboard-columns.php:200 -#, fuzzy -msgid "Show all tags" -msgstr "Mostra tutte le tag" - -#: ../includes/dashboard-columns.php:228 -#, php-format -msgid "%s Data" -msgstr "" - -#: ../includes/pdf-reports.php:36 -msgid "to" -msgstr "" - -#: ../includes/pdf-reports.php:41 -#: ../includes/pdf-reports.php:52 -msgid "Sales and earnings reports for the current year for all products" -msgstr "" - -#: ../includes/pdf-reports.php:42 -#: ../includes/pdf-reports.php:43 -#, fuzzy -msgid "Easy Digital Downloads" -msgstr "Easy Digital Download Impostazioni" - -#: ../includes/pdf-reports.php:57 -msgid "Date Range: " -msgstr "" - -#: ../includes/pdf-reports.php:61 -msgid "Table View" -msgstr "" - -#: ../includes/pdf-reports.php:65 -#, fuzzy -msgid "Product Name" -msgstr "Prodotti" - -#: ../includes/pdf-reports.php:69 -msgid "Number of Sales" -msgstr "" - -#: ../includes/pdf-reports.php:70 -#, fuzzy -msgid "Earnings to Date" -msgstr "Guadagni" - -#: ../includes/pdf-reports.php:112 -#, fuzzy -msgid "No Downloads found." -msgstr "Nessun Downloads trovato" - -#: ../includes/pdf-reports.php:119 -msgid "Graph View" -msgstr "" - -#: ../includes/pdf-reports.php:212 -msgid "Sales and Earnings by Month for all Products" -msgstr "" - -#: ../includes/pdf-reports.php:226 -msgid "Jan" -msgstr "" - -#: ../includes/pdf-reports.php:227 -msgid "Feb" -msgstr "" - -#: ../includes/pdf-reports.php:228 -msgid "Mar" -msgstr "" - -#: ../includes/pdf-reports.php:229 -msgid "Apr" -msgstr "" - -#: ../includes/pdf-reports.php:230 -msgid "May" -msgstr "" - -#: ../includes/pdf-reports.php:231 -msgid "June" -msgstr "" - -#: ../includes/pdf-reports.php:232 -msgid "July" -msgstr "" - -#: ../includes/pdf-reports.php:233 -msgid "Aug" -msgstr "" - -#: ../includes/pdf-reports.php:234 -msgid "Sept" -msgstr "" - -#: ../includes/pdf-reports.php:235 -msgid "Oct" -msgstr "" - -#: ../includes/pdf-reports.php:236 -msgid "Nov" -msgstr "" - -#: ../includes/pdf-reports.php:237 -msgid "Dec" -msgstr "" - -#: ../includes/widgets.php:39 -#, fuzzy -msgid "Downloads Cart" -msgstr "Downloads Carello" - -#: ../includes/widgets.php:39 -#, fuzzy -msgid "Display the downloads shopping cart" -msgstr "Visualizzare il carrello della spesa download" - -#: ../includes/widgets.php:82 -#: ../includes/widgets.php:162 -#, fuzzy -msgid "Title:" -msgstr "Titolo:" - -#: ../includes/widgets.php:87 -#, fuzzy -msgid "Show Quantity:" -msgstr "Mostra Quantità:" - -#: ../includes/widgets.php:112 -#, fuzzy -msgid "Downloads Categories / Tags" -msgstr "Downloads Carello" - -#: ../includes/widgets.php:112 -#, fuzzy -msgid "Display the downloads categories or tags" -msgstr "Visualizzare il carrello della spesa download" - -#: ../includes/widgets.php:167 -msgid "Taxonomy:" -msgstr "" - -#: ../includes/widgets.php:193 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Purchase History" -msgstr "Acquisto Registro" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:393 -#, fuzzy -msgid "Confirm password" -msgstr "Conferma password" - -#: ../includes/checkout-template.php:394 -======= -#: ../includes/widgets.php:193 -msgid "Display a user's purchase history" -msgstr "" - -#: ../includes/widgets.php:232 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "No downloadable files found." -msgstr "Nessun file scaricabile trovato." - -<<<<<<< HEAD -#: ../includes/checkout-template.php:399 -#: ../includes/checkout-template.php:400 -#, fuzzy -msgid "Email" -msgstr "Email" - -#: ../includes/checkout-template.php:429 -======= -#: ../includes/widgets.php:259 -#, fuzzy -msgid "Title" -msgstr "Titolo:" - -#: ../includes/scripts.php:40 -#, fuzzy -msgid "Please enter a discount code" -msgstr "Si prega di inserire un codice di sconto" - -#: ../includes/scripts.php:41 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Discount Applied" -msgstr "Sconto applicato" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:432 -======= -#: ../includes/scripts.php:42 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Please enter an email address before applying a discount code" -msgstr "Si prega di inserire un codice di sconto" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:436 -#, fuzzy -msgid "Your password" -msgstr "La tua password" - -#: ../includes/checkout-template.php:444 -======= -#: ../includes/scripts.php:44 -msgid "You have already added this item to your cart" -msgstr "Hai già aggiunto questo prodotto al tuo carrello" - -#: ../includes/scripts.php:45 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Your cart is empty" -msgstr "Il carrello è vuoto" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:446 -======= -#: ../includes/scripts.php:46 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Loading" -msgstr "Caricamento" - -<<<<<<< HEAD -#: ../includes/checkout-template.php:446 -msgid "or checkout as a guest." -msgstr "" - -#: ../includes/dashboard-columns.php:26 -======= -#: ../includes/scripts.php:123 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Add New Download" -msgstr "Aggiungi Nuovo Download" - -<<<<<<< HEAD -#: ../includes/dashboard-columns.php:27 -#, fuzzy -msgid "Categories" -msgstr "Categorie" - -#: ../includes/dashboard-columns.php:28 -#, fuzzy -msgid "Tags" -msgstr "Tags" - -#: ../includes/dashboard-columns.php:29 -#: ../includes/dashboard-columns.php:231 -#, fuzzy -msgid "Price" -msgstr "Prezzo" - -#: ../includes/dashboard-columns.php:30 -#, fuzzy -msgid "Sales" -msgstr "Vendite" - -#: ../includes/dashboard-columns.php:31 -#, fuzzy -msgid "Earnings" -msgstr "Guadagni" - -#: ../includes/dashboard-columns.php:32 -======= -#: ../includes/scripts.php:124 -msgid "Use This File" -msgstr "" - -#: ../includes/scripts.php:125 -msgid "Sorry, not available for variable priced products." -msgstr "" - -#: ../includes/scripts.php:126 -msgid "Are you sure you wish to delete this payment?" -msgstr "" - -#: ../includes/graphing.php:42 -#, fuzzy, php-format -msgid "%s Performance in Sales" -msgstr "Prestazioni Vendite Downloads" - -#: ../includes/graphing.php:77 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Download" -msgstr "Download" - -<<<<<<< HEAD -#: ../includes/dashboard-columns.php:33 -#, fuzzy -msgid "Date" -msgstr "Data" - -#: ../includes/dashboard-columns.php:189 -======= -#: ../includes/graphing.php:88 -#, fuzzy, php-format -msgid "%s Performance in Earnings" -msgstr "Prestazioni Utili Downloads" - -#: ../includes/graphing.php:121 -#: ../includes/graphing.php:221 -#: ../includes/checkout-template.php:276 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Month" -msgstr "Mese" - -<<<<<<< HEAD -#: ../includes/dashboard-columns.php:200 -======= -#: ../includes/graphing.php:136 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Earnings per month" -msgstr "Utili Mensili" - -<<<<<<< HEAD -#: ../includes/dashboard-columns.php:228 -#, php-format -msgid "%s Data" -msgstr "" - -#: ../includes/email-functions.php:47 -#, fuzzy -msgid "Purchase Receipt" -msgstr "Ricevuta d'acquisto" - -#: ../includes/email-functions.php:59 -#, fuzzy -msgid "Hello" -msgstr "Ciao" - -#: ../includes/email-functions.php:59 -#, fuzzy -msgid "A download purchase has been made" -msgstr "Un download è stato acquistato" - -#: ../includes/email-functions.php:60 -======= -#: ../includes/graphing.php:168 -msgid "Day" -msgstr "" - -#: ../includes/graphing.php:189 -#, php-format -msgid "Earnings per day for last %s days" -msgstr "" - -#: ../includes/graphing.php:236 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Sales per month" -msgstr "Utili Mensili" - -<<<<<<< HEAD -#: ../includes/email-functions.php:71 -#, fuzzy -msgid "Purchased by: " -msgstr "Acquisto ID:" - -#: ../includes/email-functions.php:72 -#, fuzzy -msgid "Amount: " -msgstr "Importo:" - -#: ../includes/email-functions.php:73 -#, fuzzy -msgid "Payment Method: " -msgstr "Modalità di pagamento" - -#: ../includes/email-functions.php:74 -#, fuzzy -msgid "Thank you" -msgstr "Grazie" - -#: ../includes/email-functions.php:76 -======= -#: ../includes/metabox.php:22 -#, fuzzy, php-format -msgid "%1$s Configuration" -msgstr "Configurazione Download" - -#: ../includes/metabox.php:23 -#, fuzzy, php-format -msgid "%1$s Stats" -msgstr "Stato" - -#: ../includes/metabox.php:24 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Purchase Log" -msgstr "Acquisto Registro" - -<<<<<<< HEAD -#: ../includes/email-template.php:23 -msgid "Default Template" -msgstr "" - -#: ../includes/email-template.php:24 -msgid "No template, plain text only" -msgstr "" - -#: ../includes/email-template.php:107 -msgid "Sample Product Title" -msgstr "" - -#: ../includes/email-template.php:110 -#, fuzzy -msgid "Sample Download File Name" -msgstr "Download Nome" - -#: ../includes/email-template.php:158 -#, fuzzy -msgid "Purchase Receipt Preview" -msgstr "Ricevuta d'acquisto" - -#: ../includes/email-template.php:158 -#, fuzzy -msgid "Preview Purchase Receipt" -msgstr "Rispedisci ricevuta d'acquisto" - -#: ../includes/email-template.php:199 -#, fuzzy -msgid "Dear" -msgstr "Anno" - -#: ../includes/email-template.php:200 -msgid "Thank you for your purchase. Please click on the link(s) below to download your files." -msgstr "" - -#: ../includes/error-tracking.php:30 -#, fuzzy -msgid "Error" -msgstr "Errore" - -#: ../includes/export-functions.php:31 -#, fuzzy -msgid "ID" -msgstr "ID" - -#: ../includes/export-functions.php:35 -======= -#: ../includes/metabox.php:25 -#, fuzzy -msgid "File Download Log" -msgstr "Scaricare file di registro" - -#: ../includes/metabox.php:84 -#, fuzzy -msgid "Pricing" -msgstr "Prezzo" - -#: ../includes/metabox.php:98 -msgid "Enable variable pricing" -msgstr "" - -#: ../includes/metabox.php:113 -#: ../includes/metabox.php:115 -#: ../includes/metabox.php:135 -#: ../includes/metabox.php:137 -msgid "9.99" -msgstr "" - -#: ../includes/metabox.php:120 -#: ../includes/metabox.php:142 -#, fuzzy -msgid "Option Name" -msgstr "Data di scadenza" - -#: ../includes/metabox.php:149 -#: ../includes/metabox.php:220 -#, fuzzy -msgid "Add New" -msgstr "Aggiungi nuovo" - -#: ../includes/metabox.php:159 -msgid "for" -msgstr "" - -#: ../includes/metabox.php:181 -#, fuzzy -msgid "Download Files" -msgstr "Download Files" - -#: ../includes/metabox.php:184 -#, fuzzy -msgid "File Name" -msgstr "Nome del file" - -#: ../includes/metabox.php:185 -#, fuzzy -msgid "File URL" -msgstr "File:" - -#: ../includes/metabox.php:196 -#: ../includes/metabox.php:215 -#, fuzzy -msgid "file name" -msgstr "Nome del file" - -#: ../includes/metabox.php:197 -#: ../includes/metabox.php:216 -#, fuzzy -msgid "file url" -msgstr "url del file" - -#: ../includes/metabox.php:199 -#, fuzzy -msgid "All Prices" -msgstr "Prezzo" - -#: ../includes/metabox.php:220 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Products" -msgstr "Prodotti" - -<<<<<<< HEAD -#: ../includes/export-functions.php:36 -======= -#: ../includes/metabox.php:241 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Discounts," -msgstr "Sconto" - -<<<<<<< HEAD -#: ../includes/export-functions.php:37 -======= -#: ../includes/metabox.php:243 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Amount paid" -msgstr "Importo:" - -<<<<<<< HEAD -#: ../includes/export-functions.php:38 -======= -#: ../includes/metabox.php:262 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Payment method" -msgstr "Modalità di pagamento" - -<<<<<<< HEAD -#: ../includes/export-functions.php:39 -#, fuzzy -msgid "Key" -msgstr "Chiave" - -#: ../includes/export-functions.php:41 -======= -#: ../includes/metabox.php:264 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "User" -msgstr "Utente" - -<<<<<<< HEAD -#: ../includes/export-functions.php:42 -======= -#: ../includes/metabox.php:265 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Status" -msgstr "Stato" - -<<<<<<< HEAD -#: ../includes/export-functions.php:103 -#: ../includes/export-functions.php:111 -======= -#: ../includes/metabox.php:266 -#, fuzzy -msgid "Choose the style of the purchase link" -msgstr "Scegli lo stile del link di acquisto" - -#: ../includes/metabox.php:287 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "none" -msgstr "nessuno" - -<<<<<<< HEAD -#: ../includes/export-functions.php:119 -======= -#: ../includes/metabox.php:295 -#, fuzzy -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "Scegli lo stile del link di acquisto" - -#: ../includes/metabox.php:313 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "guest" -msgstr "Test" - -<<<<<<< HEAD -#: ../includes/export-functions.php:127 -#, fuzzy -msgid "No payments recorded yet" -msgstr "Nessun pagamento ancora registrato" - -#: ../includes/export-functions.php:161 -msgid "Export not allowed for non-administrators." -msgstr "" - -#: ../includes/gateway-functions.php:28 -#, fuzzy -msgid "Test Payment" -msgstr "Nuovo Pagamento" - -#: ../includes/graphing.php:42 -#, fuzzy, php-format -msgid "%s Performance in Sales" -msgstr "Prestazioni Vendite Downloads" - -#: ../includes/graphing.php:77 -#: ../includes/post-types.php:125 -#, fuzzy -msgid "Download" -msgstr "Download" - -#: ../includes/graphing.php:88 -#, fuzzy, php-format -msgid "%s Performance in Earnings" -msgstr "Prestazioni Utili Downloads" - -#: ../includes/graphing.php:136 -#, fuzzy -msgid "Earnings per month" -msgstr "Utili Mensili" - -#: ../includes/graphing.php:168 -msgid "Day" -msgstr "" - -#: ../includes/graphing.php:189 -#, php-format -msgid "Earnings per day for last %s days" -msgstr "" - -#: ../includes/graphing.php:236 -#, fuzzy -msgid "Sales per month" -msgstr "Utili Mensili" - -#: ../includes/install.php:42 -#, fuzzy -msgid "Purchase Confirmation" -msgstr "Verifica di Acquisto non riuscita" - -#: ../includes/install.php:43 -#, fuzzy -msgid "Thank you for your purchase!" -msgstr "La data di acquisto" - -#: ../includes/install.php:53 -#, fuzzy -msgid "Purchase History" -msgstr "Acquisto Registro" - -#: ../includes/login-register.php:45 -#, fuzzy -msgid "Log into Your Account" -msgstr "Accedi al tuo account" - -#: ../includes/login-register.php:61 -#, fuzzy -msgid "Lost Password" -msgstr "Password" - -#: ../includes/login-register.php:62 -#, fuzzy -msgid "Lost Password?" -msgstr "Password" - -#: ../includes/login-register.php:69 -#, fuzzy -msgid "You are already logged in" -msgstr "Hai già acquistato questo articolo." - -#: ../includes/login-register.php:92 -#, fuzzy -msgid "The password you entered is incorrect" -msgstr "La password inserita non è corretta" - -#: ../includes/login-register.php:95 -#, fuzzy -msgid "The username you entered does not exist" -msgstr "Il nome utente che hai inserito non esiste" - -#: ../includes/metabox.php:22 -#, fuzzy, php-format -msgid "%1$s Configuration" -msgstr "Configurazione Download" - -#: ../includes/metabox.php:23 -#, fuzzy, php-format -msgid "%1$s Stats" -msgstr "Stato" - -#: ../includes/metabox.php:24 -#, fuzzy -msgid "Purchase Log" -msgstr "Acquisto Registro" - -#: ../includes/metabox.php:25 -#, fuzzy -msgid "File Download Log" -msgstr "Scaricare file di registro" - -#: ../includes/metabox.php:84 -#, fuzzy -msgid "Pricing" -msgstr "Prezzo" - -#: ../includes/metabox.php:98 -msgid "Enable variable pricing" -msgstr "" - -#: ../includes/metabox.php:113 -#: ../includes/metabox.php:115 -#: ../includes/metabox.php:135 -#: ../includes/metabox.php:137 -msgid "9.99" -msgstr "" - -#: ../includes/metabox.php:120 -#: ../includes/metabox.php:142 -#, fuzzy -msgid "Option Name" -msgstr "Data di scadenza" - -#: ../includes/metabox.php:149 -#: ../includes/metabox.php:220 -#: ../includes/post-types.php:43 -#: ../includes/post-types.php:81 -#, fuzzy -msgid "Add New" -msgstr "Aggiungi nuovo" - -#: ../includes/metabox.php:159 -msgid "for" -msgstr "" - -#: ../includes/metabox.php:181 -#, fuzzy -msgid "Download Files" -msgstr "Download Files" - -#: ../includes/metabox.php:184 -#, fuzzy -msgid "File Name" -msgstr "Nome del file" - -#: ../includes/metabox.php:185 -#, fuzzy -msgid "File URL" -msgstr "File:" - -#: ../includes/metabox.php:196 -#: ../includes/metabox.php:215 -#, fuzzy -msgid "file name" -msgstr "Nome del file" - -#: ../includes/metabox.php:197 -#: ../includes/metabox.php:216 -#, fuzzy -msgid "file url" -msgstr "url del file" - -#: ../includes/metabox.php:199 -#, fuzzy -msgid "All Prices" -msgstr "Prezzo" - -#: ../includes/metabox.php:206 -#: ../includes/metabox.php:217 -#, fuzzy -msgid "Upload File" -msgstr "Carica file" - -#: ../includes/metabox.php:220 -#, fuzzy -msgid "Upload the downloadable files." -msgstr "Caricare i file scaricabili." - -#: ../includes/metabox.php:241 -#, fuzzy -msgid "Purchase Text" -msgstr "Testo per tasto Acquisto" - -#: ../includes/metabox.php:243 -#, fuzzy -msgid "Add the text you would like displayed for the purchase text" -msgstr "Aggiungere il testo che si desidera visualizzare per il tasto acquisto" - -#: ../includes/metabox.php:262 -#, fuzzy -msgid "Link Style" -msgstr "Collegamento Style" - -#: ../includes/metabox.php:264 -#, fuzzy -msgid "Button" -msgstr "Pulsante" - -#: ../includes/metabox.php:265 -#, fuzzy -msgid "Text" -msgstr "Testo" - -#: ../includes/metabox.php:266 -#, fuzzy -msgid "Choose the style of the purchase link" -msgstr "Scegli lo stile del link di acquisto" - -#: ../includes/metabox.php:287 -#, fuzzy -msgid "Button Color" -msgstr "Colore Pulsante " - -#: ../includes/metabox.php:295 -#, fuzzy -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "Scegli lo stile del link di acquisto" - -#: ../includes/metabox.php:313 -#, fuzzy -msgid "Disable the purchase button?" -msgstr "Disattivare il pulsante di acquisto." - -#: ../includes/metabox.php:316 -#, fuzzy -msgid "Check this if you do not want the purchase button displayed." -msgstr "Spuntare questo se non si desidera che il pulsante di acquisto sia aggiunto automaticamente" - -#: ../includes/metabox.php:338 -#, fuzzy -msgid "Notes" -msgstr "Note" - -#: ../includes/metabox.php:341 -#, fuzzy -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "Le opzioni di stile di cui sopra non riflettono lo stile dello short code. Lo short code ti permette di inserire un pulsante di acquisto per il download in qualsiasi punto del sito." - -#: ../includes/metabox.php:347 -msgid "This short code can be placed anywhere on your site" -msgstr "" - -#: ../includes/metabox.php:434 -#, fuzzy -msgid "Sales:" -msgstr "Vendite:" - -#: ../includes/metabox.php:440 -#, fuzzy -msgid "Earnings:" -msgstr "Risultato:" - -#: ../includes/metabox.php:476 -#, fuzzy -msgid "Sales Log" -msgstr "Vendite Registro" - -#: ../includes/metabox.php:478 -#, fuzzy -msgid "Each sale for this download is listed below." -msgstr "Ogni vendita per questo download è elencato di seguito." - -#: ../includes/metabox.php:492 -#: ../includes/metabox.php:584 -#, fuzzy -msgid "Date:" -msgstr "Date:" - -#: ../includes/metabox.php:496 -#, fuzzy -msgid "Buyer:" -msgstr "Acquirente:" - -#: ../includes/metabox.php:500 -#, fuzzy -msgid "Purchase ID:" -msgstr "Acquisto ID:" - -#: ../includes/metabox.php:508 -#, fuzzy -msgid "No sales yet" -msgstr "Non ci sono ancora le vendite" - -#: ../includes/metabox.php:524 -#: ../includes/metabox.php:621 -#, fuzzy -msgid "Previous" -msgstr "Precedente" - -#: ../includes/metabox.php:565 -#, fuzzy -msgid "Download Log" -msgstr "Scarica Log" - -#: ../includes/metabox.php:567 -======= -#: ../includes/metabox.php:316 -#, fuzzy -msgid "Check this if you do not want the purchase button displayed." -msgstr "Spuntare questo se non si desidera che il pulsante di acquisto sia aggiunto automaticamente" - -#: ../includes/metabox.php:338 -#, fuzzy -msgid "Notes" -msgstr "Note" - -#: ../includes/metabox.php:341 -#, fuzzy -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "Le opzioni di stile di cui sopra non riflettono lo stile dello short code. Lo short code ti permette di inserire un pulsante di acquisto per il download in qualsiasi punto del sito." - -#: ../includes/metabox.php:347 -msgid "This short code can be placed anywhere on your site" -msgstr "" - -#: ../includes/metabox.php:434 -#, fuzzy -msgid "Sales:" -msgstr "Vendite:" - -#: ../includes/metabox.php:440 -#, fuzzy -msgid "Earnings:" -msgstr "Risultato:" - -#: ../includes/metabox.php:476 -#, fuzzy -msgid "Sales Log" -msgstr "Vendite Registro" - -#: ../includes/metabox.php:478 -#, fuzzy -msgid "Each sale for this download is listed below." -msgstr "Ogni vendita per questo download è elencato di seguito." - -#: ../includes/metabox.php:492 -#: ../includes/metabox.php:584 -#, fuzzy -msgid "Date:" -msgstr "Date:" - -#: ../includes/metabox.php:496 -#, fuzzy -msgid "Buyer:" -msgstr "Acquirente:" - -#: ../includes/metabox.php:500 -#, fuzzy -msgid "Purchase ID:" -msgstr "Acquisto ID:" - -#: ../includes/metabox.php:508 -#, fuzzy -msgid "No sales yet" -msgstr "Non ci sono ancora le vendite" - -#: ../includes/metabox.php:524 -#: ../includes/metabox.php:621 -#, fuzzy -msgid "Previous" -msgstr "Precedente" - -#: ../includes/metabox.php:525 -#: ../includes/metabox.php:622 -#: ../includes/checkout-template.php:86 -#, fuzzy -msgid "Next" -msgstr "Prossimo" - -#: ../includes/metabox.php:565 -#, fuzzy -msgid "Download Log" -msgstr "Scarica Log" - -#: ../includes/metabox.php:567 -#, fuzzy -msgid "Each time a file is downloaded, it is recorded below." -msgstr "Ogni volta che un file viene scaricato, questo viene registrato qui di seguito." - -#: ../includes/metabox.php:588 -#, fuzzy -msgid "Downloaded by:" -msgstr "Scaricato da:" - -#: ../includes/metabox.php:592 -#, fuzzy -msgid "IP Address:" -msgstr "Indirizzo IP:" - -#: ../includes/metabox.php:596 -#, fuzzy -msgid "File: " -msgstr "File:" - -#: ../includes/metabox.php:605 -#, fuzzy -msgid "No file downloads yet yet" -msgstr "Nessun file scaricato ancora" - -#: ../includes/ajax-functions.php:102 -#, fuzzy -msgid "This discount code has been used already" -msgstr "Non ci sono codici sconto che sono stati creati." - -#: ../includes/ajax-functions.php:118 -#, fuzzy -msgid "The discount you entered is invalid" -msgstr "Lo sconto che hai inserito non è valido" - -#: ../includes/admin-pages.php:26 -#, fuzzy -msgid "Payment History" -msgstr "Cronologia pagamenti" - -#: ../includes/admin-pages.php:27 -#, fuzzy -msgid "Discount Codes" -msgstr "Codici Sconto" - -#: ../includes/admin-pages.php:28 -#, fuzzy -msgid "Earnings and Sales Reports" -msgstr "Utili e rapporti di vendita" - -#: ../includes/admin-pages.php:28 -#, fuzzy -msgid "Reports" -msgstr "Rapporti" - -#: ../includes/admin-pages.php:29 -#, fuzzy -msgid "Easy Digital Download Settings" -msgstr "Easy Digital Download Impostazioni" - -#: ../includes/admin-pages.php:29 -#, fuzzy -msgid "Settings" -msgstr "Impostazioni" - -#: ../includes/admin-pages.php:30 -#, fuzzy -msgid "Easy Digital Download Add Ons" -msgstr "Easy Digital Download Impostazioni" - -#: ../includes/admin-pages.php:30 -msgid "Add Ons" -msgstr "" - -#: ../includes/login-register.php:45 -#, fuzzy -msgid "Log into Your Account" -msgstr "Accedi al tuo account" - -#: ../includes/login-register.php:47 -#: ../includes/login-register.php:48 -#: ../includes/checkout-template.php:385 -#: ../includes/checkout-template.php:386 -#: ../includes/checkout-template.php:433 -#, fuzzy -msgid "Username" -msgstr "Nome utente" - -#: ../includes/login-register.php:51 -#: ../includes/checkout-template.php:389 -#: ../includes/checkout-template.php:390 -#: ../includes/checkout-template.php:437 -#, fuzzy -msgid "Password" -msgstr "Password" - -#: ../includes/login-register.php:58 -#: ../includes/checkout-template.php:380 -#, fuzzy -msgid "Login" -msgstr "Accesso" - -#: ../includes/login-register.php:61 -#, fuzzy -msgid "Lost Password" -msgstr "Password" - -#: ../includes/login-register.php:62 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Lost Password?" -msgstr "Password" - -<<<<<<< HEAD -#: ../includes/metabox.php:588 -======= -#: ../includes/login-register.php:69 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "You are already logged in" -msgstr "Hai già acquistato questo articolo." - -<<<<<<< HEAD -#: ../includes/metabox.php:592 -======= -#: ../includes/login-register.php:92 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The password you entered is incorrect" -msgstr "La password inserita non è corretta" - -<<<<<<< HEAD -#: ../includes/metabox.php:596 -======= -#: ../includes/login-register.php:95 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The username you entered does not exist" -msgstr "Il nome utente che hai inserito non esiste" - -<<<<<<< HEAD -#: ../includes/metabox.php:605 -======= -#: ../includes/error-tracking.php:30 -#: ../includes/process-download.php:266 -#: ../includes/process-download.php:283 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Error" -msgstr "Errore" - -#: ../includes/misc-functions.php:185 -#, fuzzy -msgid "US Dollars ($)" -msgstr "US Dollars ($)" - -#: ../includes/misc-functions.php:186 -#, fuzzy -msgid "Euros (€)" -msgstr "Euros (€)" - -#: ../includes/misc-functions.php:187 -#, fuzzy -msgid "Pounds Sterling (£)" -msgstr "Pounds Sterling (£)" - -#: ../includes/misc-functions.php:188 -#, fuzzy -msgid "Australian Dollars ($)" -msgstr "Australian Dollars ($)" - -#: ../includes/misc-functions.php:189 -#, fuzzy -msgid "Brazilian Real ($)" -msgstr "Brazilian Real ($)" - -#: ../includes/misc-functions.php:190 -#, fuzzy -msgid "Canadian Dollars ($)" -msgstr "Canadian Dollars ($)" - -#: ../includes/misc-functions.php:191 -#, fuzzy -msgid "Czech Koruna" -msgstr "Corona ceca" - -#: ../includes/misc-functions.php:192 -#, fuzzy -msgid "Danish Krone" -msgstr "Corona danese" - -#: ../includes/misc-functions.php:193 -#, fuzzy -msgid "Hong Kong Dollar ($)" -msgstr "Hong Kong Dollar ($)" - -#: ../includes/misc-functions.php:194 -#, fuzzy -msgid "Hungarian Forint" -msgstr "Fiorino ungherese" - -#: ../includes/misc-functions.php:195 -#, fuzzy -msgid "Israeli Shekel" -msgstr "Shekel israeliano" - -#: ../includes/misc-functions.php:196 -#, fuzzy -msgid "Japanese Yen (¥)" -msgstr "Japanese Yen (¥)" - -#: ../includes/misc-functions.php:197 -#, fuzzy -msgid "Malaysian Ringgits" -msgstr "Malaysian Ringgits" - -#: ../includes/misc-functions.php:198 -#, fuzzy -msgid "Mexican Peso ($)" -msgstr "Mexican Peso ($)" - -#: ../includes/misc-functions.php:199 -#, fuzzy -msgid "New Zealand Dollar ($)" -msgstr "New Zealand Dollar ($)" - -#: ../includes/misc-functions.php:200 -#, fuzzy -msgid "Norwegian Krone" -msgstr "Corona norvegese" - -#: ../includes/misc-functions.php:201 -#, fuzzy -msgid "Philippine Pesos" -msgstr "Philippine Pesos" - -#: ../includes/misc-functions.php:202 -#, fuzzy -msgid "Polish Zloty" -msgstr "Zloty polacco" - -#: ../includes/misc-functions.php:203 -#, fuzzy -msgid "Singapore Dollar ($)" -msgstr "Singapore Dollar ($)" - -#: ../includes/misc-functions.php:204 -#, fuzzy -msgid "Swedish Krona" -msgstr "Corona svedese" - -#: ../includes/misc-functions.php:205 -#, fuzzy -msgid "Swiss Franc" -msgstr "Franco Svizzero" - -#: ../includes/misc-functions.php:206 -#, fuzzy -msgid "Taiwan New Dollars" -msgstr "Nuovi Dollari di Taiwan" - -#: ../includes/misc-functions.php:207 -#, fuzzy -msgid "Thai Baht" -msgstr "Baht thailandese" - -#: ../includes/misc-functions.php:208 -msgid "Indian Rupee" -msgstr "" - -#: ../includes/misc-functions.php:209 -msgid "Turkish Lira" -msgstr "" -<<<<<<< HEAD - -#: ../includes/misc-functions.php:210 -msgid "Iranian Rial" -msgstr "" - -#: ../includes/payment-functions.php:278 -msgid "Pending" -msgstr "In attesa" - -#: ../includes/payment-functions.php:279 -msgid "Complete" -msgstr "Completato" - -#: ../includes/payment-functions.php:280 -msgid "Refunded" -msgstr "" - -#: ../includes/pdf-reports.php:36 -msgid "to" -msgstr "" - -#: ../includes/pdf-reports.php:41 -#: ../includes/pdf-reports.php:52 -msgid "Sales and earnings reports for the current year for all products" -msgstr "" - -#: ../includes/pdf-reports.php:42 -#: ../includes/pdf-reports.php:43 -#, fuzzy -msgid "Easy Digital Downloads" -msgstr "Easy Digital Download Impostazioni" - -#: ../includes/pdf-reports.php:57 -msgid "Date Range: " -msgstr "" - -#: ../includes/pdf-reports.php:61 -msgid "Table View" -msgstr "" - -#: ../includes/pdf-reports.php:65 -#, fuzzy -msgid "Product Name" -msgstr "Prodotti" - -#: ../includes/pdf-reports.php:69 -msgid "Number of Sales" -msgstr "" - -#: ../includes/pdf-reports.php:70 -#, fuzzy -msgid "Earnings to Date" -msgstr "Guadagni" - -#: ../includes/pdf-reports.php:112 -#, fuzzy -msgid "No Downloads found." -msgstr "Nessun Downloads trovato" - -#: ../includes/pdf-reports.php:119 -msgid "Graph View" -msgstr "" - -#: ../includes/pdf-reports.php:212 -msgid "Sales and Earnings by Month for all Products" -msgstr "" - -#: ../includes/pdf-reports.php:226 -msgid "Jan" -msgstr "" - -#: ../includes/pdf-reports.php:227 -msgid "Feb" -msgstr "" - -#: ../includes/pdf-reports.php:228 -msgid "Mar" -msgstr "" - -#: ../includes/pdf-reports.php:229 -msgid "Apr" -msgstr "" - -#: ../includes/pdf-reports.php:230 -msgid "May" -msgstr "" - -#: ../includes/pdf-reports.php:231 -msgid "June" -msgstr "" - -#: ../includes/pdf-reports.php:232 -msgid "July" -msgstr "" - -#: ../includes/pdf-reports.php:233 -msgid "Aug" -msgstr "" - -#: ../includes/pdf-reports.php:234 -msgid "Sept" -msgstr "" - -#: ../includes/pdf-reports.php:235 -msgid "Oct" -msgstr "" - -#: ../includes/pdf-reports.php:236 -msgid "Nov" -msgstr "" - -#: ../includes/pdf-reports.php:237 -msgid "Dec" -msgstr "" - -#: ../includes/post-types.php:44 -#, fuzzy, php-format -msgid "Add New %1$s" -msgstr "Aggiungi nuovo" - -#: ../includes/post-types.php:45 -#, fuzzy, php-format -msgid "Edit %1$s" -msgstr "Modifica" - -#: ../includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "" - -#: ../includes/post-types.php:47 -#, fuzzy, php-format -msgid "All %2$s" -msgstr "Tutti i Tag" - -#: ../includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "" - -#: ../includes/post-types.php:49 -#, fuzzy, php-format -msgid "Search %2$s" -msgstr "Ricerca Tags" - -#: ../includes/post-types.php:50 -#, fuzzy, php-format -msgid "No %2$s found" -msgstr "Nessun pagamento trovato" - -#: ../includes/post-types.php:51 -#, fuzzy, php-format -msgid "No %2$s found in Trash" -msgstr "Nessun pagamento trovato nel Cestino" - -#: ../includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "" - -#: ../includes/post-types.php:79 -#, fuzzy -msgid "Payments" -msgstr "Pagamenti" - -#: ../includes/post-types.php:80 -#, fuzzy -msgid "Payment" -msgstr "Pagamento" - -#: ../includes/post-types.php:82 -#, fuzzy -msgid "Add New Payment" -msgstr "Aggiungi nuovo pagamento" - -#: ../includes/post-types.php:83 -======= - -#: ../includes/misc-functions.php:210 -msgid "Iranian Rial" -msgstr "" - -#: ../includes/process-download.php:266 -#: ../includes/process-download.php:283 -msgid "Sorry but this file does not exist." -msgstr "" - -#: ../includes/process-download.php:294 -#, fuzzy -msgid "You do not have permission to download this file" -msgstr "Non hai il permesso di scaricare questo file" - -#: ../includes/process-download.php:294 -msgid "Purchase Verification Failed" -msgstr "Verifica di Acquisto non riuscita" - -#: ../includes/checkout-template.php:75 -#, fuzzy -msgid "Choose Your Payment Method" -msgstr "Scegli il tuo metodo di pagamento" - -#: ../includes/checkout-template.php:141 -#, fuzzy -msgid "Personal Info" -msgstr "Pagamenti Info" - -#: ../includes/checkout-template.php:144 -#, fuzzy -msgid "Email address" -msgstr "Indirizzo e-mail" - -#: ../includes/checkout-template.php:145 -#, fuzzy -msgid "Email Address" -msgstr "Indirizzo e-mail" - -#: ../includes/checkout-template.php:153 -#: ../includes/checkout-template.php:407 -#, fuzzy -msgid "Last name" -msgstr "Cognome" - -#: ../includes/checkout-template.php:166 -#, fuzzy -msgid "Enter discount" -msgstr "Inserisci sconto" - -#: ../includes/checkout-template.php:168 -#, fuzzy -msgid "Discount" -msgstr "Sconto" - -#: ../includes/checkout-template.php:170 -#, fuzzy -msgid "Apply Discount" -msgstr "Applicare Sconto" - -#: ../includes/checkout-template.php:197 -msgid "Show Terms" -msgstr "" - -#: ../includes/checkout-template.php:198 -msgid "Hide Terms" -msgstr "" - -#: ../includes/checkout-template.php:201 -msgid "Agree to Terms?" -msgstr "" - -#: ../includes/checkout-template.php:226 -msgid "Go back" -msgstr "Torna dietro" - -#: ../includes/checkout-template.php:230 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "You must be logged in to complete your purchase" -msgstr "Devi essere loggato per completare l'acquisto" - -<<<<<<< HEAD -#: ../includes/post-types.php:84 -======= -#: ../includes/checkout-template.php:259 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Credit Card Info" -msgstr "Carta di Credito Info" - -<<<<<<< HEAD -#: ../includes/post-types.php:85 -======= -#: ../includes/checkout-template.php:261 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Card name" -msgstr "Biglietto da visita" - -<<<<<<< HEAD -#: ../includes/post-types.php:86 -======= -#: ../includes/checkout-template.php:262 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Name on the Card" -msgstr "Nome sulla Carta" - -<<<<<<< HEAD -#: ../includes/post-types.php:87 -#, fuzzy -msgid "Search Payments" -msgstr "Cerca Pagamenti" - -#: ../includes/post-types.php:88 -msgid "No Payments found" -msgstr "Nessun pagamento trovato" - -#: ../includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "Nessun pagamento trovato nel Cestino" - -#: ../includes/post-types.php:126 -#, fuzzy -msgid "Downloads" -msgstr "Downloads" - -#: ../includes/post-types.php:174 -======= -#: ../includes/checkout-template.php:265 -#, fuzzy -msgid "Card number" -msgstr "Numero di carta" - -#: ../includes/checkout-template.php:266 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Card Number" -msgstr "Numero di Carta" - -<<<<<<< HEAD -#: ../includes/post-types.php:175 -======= -#: ../includes/checkout-template.php:269 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Security code" -msgstr "Codice di sicurezza" - -<<<<<<< HEAD -#: ../includes/post-types.php:176 -======= -#: ../includes/checkout-template.php:270 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "CVC" -msgstr "CVC" - -<<<<<<< HEAD -#: ../includes/post-types.php:177 -======= -#: ../includes/checkout-template.php:278 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Year" -msgstr "Anno" - -#: ../includes/checkout-template.php:279 -msgid "Expiration (MM/YYYY)" -msgstr "Scadenza (MM/AAAA)" - -<<<<<<< HEAD -#: ../includes/post-types.php:178 -======= -#: ../includes/checkout-template.php:309 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Address line 1" -msgstr "Indirizzo riga 1" - -<<<<<<< HEAD -#: ../includes/post-types.php:179 -======= -#: ../includes/checkout-template.php:310 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Billing Address" -msgstr "Indirizzo di fatturazione" - -<<<<<<< HEAD -#: ../includes/post-types.php:180 -======= -#: ../includes/checkout-template.php:313 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Address line 2" -msgstr "Indirizzo riga 2" - -<<<<<<< HEAD -#: ../includes/post-types.php:181 -======= -#: ../includes/checkout-template.php:314 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Billing Address Line 2" -msgstr "Fatturazione Indirizzo Riga 2" - -<<<<<<< HEAD -#: ../includes/post-types.php:182 -======= -#: ../includes/checkout-template.php:317 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "City" -msgstr "Città" - -<<<<<<< HEAD -#: ../includes/post-types.php:196 -======= -#: ../includes/checkout-template.php:318 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Billing City" -msgstr "Città per la Fatturazione" - -<<<<<<< HEAD -#: ../includes/post-types.php:197 -======= -#: ../includes/checkout-template.php:329 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Billing Country" -msgstr "Città per la Fatturazione" - -<<<<<<< HEAD -#: ../includes/post-types.php:198 -======= -#: ../includes/checkout-template.php:332 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "State / Province" -msgstr "Stato / Provincia" - -<<<<<<< HEAD -#: ../includes/post-types.php:199 -======= -#: ../includes/checkout-template.php:349 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Billing State / Province" -msgstr "Fatturazione Stato / Provincia" - -<<<<<<< HEAD -#: ../includes/post-types.php:200 -======= -#: ../includes/checkout-template.php:352 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Zip / Postal code" -msgstr "Zip / CAP" - -<<<<<<< HEAD -#: ../includes/post-types.php:201 -======= -#: ../includes/checkout-template.php:353 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Billing Zip / Postal Code" -msgstr "Fatturazione Zip / CAP" - -<<<<<<< HEAD -#: ../includes/post-types.php:202 -======= -#: ../includes/checkout-template.php:380 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Already have an account?" -msgstr "Hai già un account." - -<<<<<<< HEAD -#: ../includes/post-types.php:203 -======= -#: ../includes/checkout-template.php:382 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Create an account" -msgstr "Crea un account" - -<<<<<<< HEAD -#: ../includes/post-types.php:204 -msgid "New Tag Name" -msgstr "Nuovo Nome Tag" - -#: ../includes/post-types.php:233 -#: ../includes/post-types.php:234 -======= -#: ../includes/checkout-template.php:382 -msgid "(optional)" -msgstr "" - -#: ../includes/checkout-template.php:393 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Confirm password" -msgstr "Conferma password" - -<<<<<<< HEAD -#: ../includes/post-types.php:235 -======= -#: ../includes/checkout-template.php:394 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Password Again" -msgstr "Renserire Password" - -<<<<<<< HEAD -#: ../includes/post-types.php:236 -======= -#: ../includes/checkout-template.php:429 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Login to your account" -msgstr "Accedi al tuo account" - -<<<<<<< HEAD -#: ../includes/post-types.php:237 -======= -#: ../includes/checkout-template.php:432 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Your username" -msgstr "Il tuo nome utente" - -<<<<<<< HEAD -#: ../includes/process-download.php:266 -#: ../includes/process-download.php:283 -msgid "Sorry but this file does not exist." -msgstr "" - -#: ../includes/process-download.php:294 -#, fuzzy -msgid "You do not have permission to download this file" -msgstr "Non hai il permesso di scaricare questo file" - -#: ../includes/process-download.php:294 -msgid "Purchase Verification Failed" -msgstr "Verifica di Acquisto non riuscita" - -#: ../includes/process-purchase.php:206 -msgid "The selected gateway is not active" -msgstr "" - -#: ../includes/process-purchase.php:210 -msgid "No gateway has been selected" -msgstr "" - -#: ../includes/process-purchase.php:259 -msgid "You must agree to the terms of use" -msgstr "" - -#: ../includes/process-purchase.php:289 -#, fuzzy -msgid "Please enter a valid email address." -msgstr "Devi inserire un indirizzo email valido." - -#: ../includes/process-purchase.php:303 -msgid "The user information is invalid." -msgstr "" - -#: ../includes/process-purchase.php:349 -======= -#: ../includes/checkout-template.php:436 -#, fuzzy -msgid "Your password" -msgstr "La tua password" - -#: ../includes/checkout-template.php:444 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Need to create an account?" -msgstr "Bisogna creare un account." - -<<<<<<< HEAD -#: ../includes/process-purchase.php:355 -======= -#: ../includes/checkout-template.php:446 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Register" -msgstr "Registrati" - -<<<<<<< HEAD -#: ../includes/process-purchase.php:366 -#, fuzzy -msgid "You must register or login to complete your purchase" -msgstr "Devi essere loggato per completare l'acquisto" - -#: ../includes/process-purchase.php:378 -#: ../includes/process-purchase.php:522 -======= -#: ../includes/checkout-template.php:446 -msgid "or checkout as a guest." -msgstr "" - -#: ../includes/email-functions.php:59 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Hello" -msgstr "Ciao" - -<<<<<<< HEAD -#: ../includes/process-purchase.php:384 -======= -#: ../includes/email-functions.php:59 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "A download purchase has been made" -msgstr "Un download è stato acquistato" - -<<<<<<< HEAD -#: ../includes/process-purchase.php:396 -#: ../includes/process-purchase.php:529 -#, fuzzy -msgid "Enter an email" -msgstr "Inserisci un nome utente" - -#: ../includes/process-purchase.php:407 -msgid "Passwords don't match" -msgstr "Password non corrisponde" - -#: ../includes/process-purchase.php:422 -#: ../includes/process-purchase.php:487 -#, fuzzy -msgid "Enter a password" -msgstr "Inserire una password" - -#: ../includes/process-purchase.php:427 -#, fuzzy -msgid "Enter the password confirmation" -msgstr "Inserire una password" - -#: ../includes/process-purchase.php:454 -#, fuzzy -msgid "You must login or register to complete your purchase" -msgstr "Devi essere loggato per completare l'acquisto" - -#: ../includes/register-settings.php:41 -#, fuzzy -msgid "Test Mode" -msgstr "Test" - -#: ../includes/register-settings.php:42 -======= -#: ../includes/email-functions.php:60 -#, fuzzy -msgid "Downloads sold:" -msgstr "Downloads venduti:" - -#: ../includes/email-functions.php:71 -#, fuzzy -msgid "Purchased by: " -msgstr "Acquisto ID:" - -#: ../includes/email-functions.php:72 -#, fuzzy -msgid "Amount: " -msgstr "Importo:" - -#: ../includes/email-functions.php:73 -#, fuzzy -msgid "Payment Method: " -msgstr "Modalità di pagamento" - -#: ../includes/email-functions.php:74 -#, fuzzy -msgid "Thank you" -msgstr "Grazie" - -#: ../includes/email-functions.php:76 -#, fuzzy -msgid "New download purchase" -msgstr "Nuovo download acquistabile " - -#: ../includes/install.php:42 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Purchase Confirmation" -msgstr "Verifica di Acquisto non riuscita" - -<<<<<<< HEAD -#: ../includes/register-settings.php:47 -#, fuzzy -msgid "Checkout Page" -msgstr "Paga" - -#: ../includes/register-settings.php:48 -#, fuzzy -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "Questa è la pagina di pagamento in cui gli acquirenti porteranno a termine gli acquisti" - -#: ../includes/register-settings.php:54 -msgid "Success Page" -msgstr "Pagina Successo" - -#: ../includes/register-settings.php:55 -#, fuzzy -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "Questa è la pagina acquirenti in cui saranno reindirizzati dopo aver completato i loro acquisti" - -#: ../includes/register-settings.php:61 -msgid "Download Links on Success Page" -msgstr "" - -#: ../includes/register-settings.php:62 -msgid "Show a list of all download links on the success page after completing a purchase?" -msgstr "" - -#: ../includes/register-settings.php:67 -======= -#: ../includes/install.php:43 -#, fuzzy -msgid "Thank you for your purchase!" -msgstr "La data di acquisto" - -#: ../includes/process-purchase.php:206 -msgid "The selected gateway is not active" -msgstr "" - -#: ../includes/process-purchase.php:210 -msgid "No gateway has been selected" -msgstr "" - -#: ../includes/process-purchase.php:259 -msgid "You must agree to the terms of use" -msgstr "" - -#: ../includes/process-purchase.php:289 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Please enter a valid email address." -msgstr "Devi inserire un indirizzo email valido." - -<<<<<<< HEAD -#: ../includes/register-settings.php:68 -#, fuzzy -msgid "Configure the currency options" -msgstr "Configurare le opzioni su valute" - -#: ../includes/register-settings.php:73 -======= -#: ../includes/process-purchase.php:303 -msgid "The user information is invalid." -msgstr "" - -#: ../includes/process-purchase.php:349 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Username already taken" -msgstr "Nome utente già preso" - -<<<<<<< HEAD -#: ../includes/register-settings.php:74 -======= -#: ../includes/process-purchase.php:355 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Invalid username" -msgstr "Nome utente non valido" - -<<<<<<< HEAD -#: ../includes/register-settings.php:80 -======= -#: ../includes/process-purchase.php:366 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "You must register or login to complete your purchase" -msgstr "Devi essere loggato per completare l'acquisto" - -<<<<<<< HEAD -#: ../includes/register-settings.php:81 -======= -#: ../includes/process-purchase.php:378 -#: ../includes/process-purchase.php:522 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Invalid email" -msgstr "E-mail valido" - -<<<<<<< HEAD -#: ../includes/register-settings.php:84 -======= -#: ../includes/process-purchase.php:384 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Email already used" -msgstr "Email già utilizzata" - -<<<<<<< HEAD -#: ../includes/register-settings.php:85 -======= -#: ../includes/process-purchase.php:396 -#: ../includes/process-purchase.php:529 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Enter an email" -msgstr "Inserisci un nome utente" - -<<<<<<< HEAD -#: ../includes/register-settings.php:90 -#, fuzzy -msgid "Thousands Separator" -msgstr "Separatore delle migliaia" - -#: ../includes/register-settings.php:91 -======= -#: ../includes/process-purchase.php:407 -msgid "Passwords don't match" -msgstr "Password non corrisponde" - -#: ../includes/process-purchase.php:422 -#: ../includes/process-purchase.php:487 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Enter a password" -msgstr "Inserire una password" - -<<<<<<< HEAD -#: ../includes/register-settings.php:98 -======= -#: ../includes/process-purchase.php:427 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Enter the password confirmation" -msgstr "Inserire una password" - -<<<<<<< HEAD -#: ../includes/register-settings.php:99 -======= -#: ../includes/process-purchase.php:454 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "You must login or register to complete your purchase" -msgstr "Devi essere loggato per completare l'acquisto" - -<<<<<<< HEAD -#: ../includes/register-settings.php:110 -#: ../includes/admin-pages/settings.php:34 -======= -#: ../includes/gateway-functions.php:28 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Test Payment" -msgstr "Nuovo Pagamento" - -<<<<<<< HEAD -#: ../includes/register-settings.php:111 -======= -#: ../includes/admin-notices.php:24 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Discount code updated." -msgstr "Codice di sconto aggiornato." - -<<<<<<< HEAD -#: ../includes/register-settings.php:117 -#, fuzzy -msgid "Accepted Payment Method Icons" -msgstr "Metodi di pagamento accettati" - -#: ../includes/register-settings.php:118 -#, fuzzy -msgid "Display icons for the selected payment methods" -msgstr "Visualizzare le icone per i metodi di pagamento selezionati" - -#: ../includes/register-settings.php:118 -#, fuzzy -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "Sarà inoltre necessario configurare le impostazioni del gateway se si accetta carte di credito" - -#: ../includes/register-settings.php:131 -#, fuzzy -msgid "PayPal Settings" -msgstr "Impostazioni PayPal" - -#: ../includes/register-settings.php:132 -======= -#: ../includes/admin-notices.php:27 -#, fuzzy -msgid "There was a problem updating your discount code, please try again." -msgstr "C'è stato un problema aggiornare il codice di sconto, si prega di riprovare." - -#: ../includes/admin-notices.php:30 -msgid "The payment has been deleted." -msgstr "" - -#: ../includes/admin-notices.php:33 -msgid "The purchase receipt has been resent." -msgstr "" - -#: ../includes/admin-notices.php:38 -#, php-format -msgid "The payment history needs updated. %s" -msgstr "" - -#: ../includes/admin-notices.php:38 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Click to Upgrade" -msgstr "Clicca %s per l'acquisto di nuovo." - -<<<<<<< HEAD -#: ../includes/register-settings.php:137 -#, fuzzy -msgid "PayPal Email" -msgstr "Email PayPal" - -#: ../includes/register-settings.php:138 -#, fuzzy -msgid "Enter your PayPal account's email" -msgstr "Inserisci la tua email account PayPal" - -#: ../includes/register-settings.php:144 -#, fuzzy -msgid "Alternate PayPal Purchase Verification" -msgstr "Alternare verifica Acquisto PayPal" - -#: ../includes/register-settings.php:145 -#, fuzzy -msgid "If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal." -msgstr "Se i pagamenti non sono sempre contrassegnati come completati, selezionare questa opzione" - -#: ../includes/register-settings.php:150 -#, fuzzy -msgid "Disable PayPal IPN Verification" -msgstr "Alternare verifica Acquisto PayPal" - -#: ../includes/register-settings.php:151 -#, fuzzy -msgid "If payments are not getting marked as complete, then check this box. This forces the site to use a slightly less secure method of verifying purchases." -msgstr "Se i pagamenti non sono sempre contrassegnati come completati, e la disattivazione cURL non risolve il problema, quindi selezionare questa casella" - -#: ../includes/register-settings.php:160 -msgid "Email Template" -msgstr "" - -#: ../includes/register-settings.php:161 -msgid "Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" to see the new template." -msgstr "" - -#: ../includes/register-settings.php:173 -#, fuzzy -msgid "From Name" -msgstr "Da Nome" - -#: ../includes/register-settings.php:174 -#, fuzzy -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "Nome da cui proviene la vendita. Questo probabilmente dovrebbe essere il vostro sito o nome del negozio." - -#: ../includes/register-settings.php:179 -#, fuzzy -msgid "From Email" -msgstr "Da E-mail" - -#: ../includes/register-settings.php:180 -#, fuzzy -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "Email da usare nel modulo della ricevuta. Questo funzionerà come \"da\" e \"rispondere-a\" indirizzo." - -#: ../includes/register-settings.php:185 -======= -#: ../includes/admin-notices.php:55 -msgid "There seems to be an issue with the server. Please try again in a few minutes." -msgstr "" - -#: ../includes/post-types.php:44 -#, fuzzy, php-format -msgid "Add New %1$s" -msgstr "Aggiungi nuovo" - -#: ../includes/post-types.php:45 -#, fuzzy, php-format -msgid "Edit %1$s" -msgstr "Modifica" - -#: ../includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "" - -#: ../includes/post-types.php:47 -#, fuzzy, php-format -msgid "All %2$s" -msgstr "Tutti i Tag" - -#: ../includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "" - -#: ../includes/post-types.php:49 -#, fuzzy, php-format -msgid "Search %2$s" -msgstr "Ricerca Tags" - -#: ../includes/post-types.php:50 -#, fuzzy, php-format -msgid "No %2$s found" -msgstr "Nessun pagamento trovato" - -#: ../includes/post-types.php:51 -#, fuzzy, php-format -msgid "No %2$s found in Trash" -msgstr "Nessun pagamento trovato nel Cestino" - -#: ../includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "" - -#: ../includes/post-types.php:79 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Payments" -msgstr "Pagamenti" - -<<<<<<< HEAD -#: ../includes/register-settings.php:186 -======= -#: ../includes/post-types.php:80 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Payment" -msgstr "Pagamento" - -<<<<<<< HEAD -#: ../includes/register-settings.php:192 -======= -#: ../includes/post-types.php:82 -#, fuzzy -msgid "Add New Payment" -msgstr "Aggiungi nuovo pagamento" - -#: ../includes/post-types.php:83 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Edit Payment" -msgstr "Modifica pagamento" - -<<<<<<< HEAD -#: ../includes/register-settings.php:193 -======= -#: ../includes/post-types.php:84 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "New Payment" -msgstr "Nuovo Pagamento" - -<<<<<<< HEAD -#: ../includes/register-settings.php:194 -======= -#: ../includes/post-types.php:85 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "All Payments" -msgstr "Tutti i pagamenti" - -<<<<<<< HEAD -#: ../includes/register-settings.php:195 -======= -#: ../includes/post-types.php:86 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "View Payment" -msgstr "Mostra di pagamento" - -<<<<<<< HEAD -#: ../includes/register-settings.php:196 -#, fuzzy -msgid "The total price of the purchase" -msgstr "La data di acquisto" - -#: ../includes/register-settings.php:197 -#, fuzzy -msgid "The unique ID number for this purchase receipt" -msgstr "Inserire la liena del soggetto della e-mail per la ricevuta d'acquisto" - -#: ../includes/register-settings.php:198 -#, fuzzy -msgid "The method of payment used for this purchase" -msgstr "La data di acquisto" - -#: ../includes/register-settings.php:199 -======= -#: ../includes/post-types.php:87 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Search Payments" -msgstr "Cerca Pagamenti" - -<<<<<<< HEAD -#: ../includes/register-settings.php:208 -msgid "Disable Styles" -msgstr "" - -#: ../includes/register-settings.php:209 -msgid "Check this to disable all included styling" -msgstr "" - -#: ../includes/register-settings.php:214 -#, fuzzy -msgid "Checkout Button Color" -msgstr "Colore Pulsante " - -#: ../includes/register-settings.php:215 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "" - -#: ../includes/register-settings.php:225 -#, fuzzy -msgid "Disable Ajax" -msgstr "Abilitare Ajax" - -#: ../includes/register-settings.php:226 -#, fuzzy -msgid "Check this to disable AJAX for the shopping cart." -msgstr "Selezionare questa opzione per abilitare AJAX per il carrello della spesa." - -#: ../includes/register-settings.php:231 -======= -#: ../includes/post-types.php:88 -msgid "No Payments found" -msgstr "Nessun pagamento trovato" - -#: ../includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "Nessun pagamento trovato nel Cestino" - -#: ../includes/post-types.php:126 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Downloads" -msgstr "Downloads" - -<<<<<<< HEAD -#: ../includes/register-settings.php:232 -======= -#: ../includes/post-types.php:174 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Category" -msgstr "Categoria" - -<<<<<<< HEAD -#: ../includes/register-settings.php:237 -#, fuzzy -msgid "Disable Guest Checkout" -msgstr "Disabilita Ospiti Pagamento" - -#: ../includes/register-settings.php:238 -msgid "Require that users be logged-in to purchase files." -msgstr "Si richiede che gli utenti siano registrati per l'acquisto di file." - -#: ../includes/register-settings.php:243 -#, fuzzy -msgid "Show Register / Login Form?" -msgstr "Mostra Registrati / Registrazione Modulo?" - -#: ../includes/register-settings.php:244 -#, fuzzy -msgid "Display the registration and login forms on the checkout page for non-logged-in users." -msgstr "Visualizzare i moduli registrazione e di login nella pagina di pagamento per gli utenti non loggati." - -#: ../includes/register-settings.php:249 -#, fuzzy -msgid "Download Link Expiration" -msgstr "Configurazione Download" - -#: ../includes/register-settings.php:250 -msgid "How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours." -msgstr "" - -#: ../includes/register-settings.php:256 -======= -#: ../includes/post-types.php:175 -#, fuzzy -msgid "Search Categories" -msgstr "Categorie di ricerca" - -#: ../includes/post-types.php:176 -#, fuzzy -msgid "All Categories" -msgstr "Tutte le categorie" - -#: ../includes/post-types.php:177 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Parent Category" -msgstr "Parent Categoria" - -<<<<<<< HEAD -#: ../includes/register-settings.php:257 -#, fuzzy -msgid "Check this if you do not want to allow users to redownload items from their purchase history." -msgstr "Selezionare questa opzione se non si desidera consentire agli utenti di scaricare di nuovo gli elementi dalla loro cronologia degli acquisti" - -#: ../includes/register-settings.php:262 -msgid "Terms of Agreement" -msgstr "" - -#: ../includes/register-settings.php:268 -msgid "Agree to Terms" -msgstr "" - -#: ../includes/register-settings.php:269 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing." -msgstr "" - -#: ../includes/register-settings.php:274 -msgid "Agree to Terms Label" -msgstr "" - -#: ../includes/register-settings.php:275 -msgid "Label shown next to the agree to terms check box." -msgstr "" - -#: ../includes/register-settings.php:281 -msgid "Agreement Text" -msgstr "" - -#: ../includes/register-settings.php:282 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "" - -#: ../includes/register-settings.php:287 -#, fuzzy -msgid "Complete Purchase Text" -msgstr "Testo per tasto Acquisto" - -#: ../includes/register-settings.php:288 -#, fuzzy -msgid "The button label for completing a purchase." -msgstr "Questa è la pagina acquirenti in cui saranno reindirizzati dopo aver completato i loro acquisti" - -#: ../includes/register-settings.php:314 -======= -#: ../includes/post-types.php:178 -#, fuzzy -msgid "Parent Category:" -msgstr "Parent Categoria:" - -#: ../includes/post-types.php:179 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Edit Category" -msgstr "Modifica Categoria" - -<<<<<<< HEAD -#: ../includes/register-settings.php:340 -======= -#: ../includes/post-types.php:180 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Update Category" -msgstr "Aggiornare Categoria" - -<<<<<<< HEAD -#: ../includes/register-settings.php:366 -======= -#: ../includes/post-types.php:181 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Add New Category" -msgstr "Aggiungi Nuova Categoria" - -<<<<<<< HEAD -#: ../includes/register-settings.php:392 -#, fuzzy -msgid "Style Settings" -msgstr "Impostazioni PayPal" - -#: ../includes/register-settings.php:419 -======= -#: ../includes/post-types.php:182 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "New Category Name" -msgstr "Nuovo Nome Categoria " - -<<<<<<< HEAD -#: ../includes/register-settings.php:746 -#, fuzzy -msgid "Settings Updated" -msgstr "Impostazioni" - -#: ../includes/scripts.php:40 -======= -#: ../includes/post-types.php:196 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Tag" -msgstr "Etichetta" - -<<<<<<< HEAD -#: ../includes/scripts.php:41 -======= -#: ../includes/post-types.php:197 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Search Tags" -msgstr "Ricerca Tags" - -<<<<<<< HEAD -#: ../includes/scripts.php:42 -#, fuzzy -msgid "Please enter an email address before applying a discount code" -msgstr "Si prega di inserire un codice di sconto" - -#: ../includes/scripts.php:44 -msgid "You have already added this item to your cart" -msgstr "Hai già aggiunto questo prodotto al tuo carrello" - -#: ../includes/scripts.php:45 -#, fuzzy -msgid "Your cart is empty" -msgstr "Il carrello è vuoto" - -#: ../includes/scripts.php:46 -======= -#: ../includes/post-types.php:198 -#, fuzzy -msgid "All Tags" -msgstr "Tutti i Tag" - -#: ../includes/post-types.php:199 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Parent Tag" -msgstr "Parent Tag" - -<<<<<<< HEAD -#: ../includes/scripts.php:123 -#, fuzzy -msgid "Add New Download" -msgstr "Aggiungi Nuovo Download" - -#: ../includes/scripts.php:124 -msgid "Use This File" -msgstr "" - -#: ../includes/scripts.php:125 -msgid "Sorry, not available for variable priced products." -msgstr "" - -#: ../includes/scripts.php:126 -msgid "Are you sure you wish to delete this payment?" -msgstr "" - -#: ../includes/shortcodes.php:333 -#, fuzzy, php-format -msgid "No %s found" -msgstr "Nessun pagamento trovato" - -#: ../includes/template-functions.php:48 -#, fuzzy -msgid "No checkout page has been configured." -msgstr "Non ci sono codici sconto che sono stati creati." - -#: ../includes/template-functions.php:167 -#, fuzzy -msgid "added to your cart" -msgstr "aggiunto al tuo carrello" - -#: ../includes/template-functions.php:263 -msgid "Gray" -msgstr "" - -#: ../includes/template-functions.php:264 -msgid "Pink" -msgstr "" - -#: ../includes/template-functions.php:265 -#, fuzzy -msgid "Blue" -msgstr "Acquirente:" - -#: ../includes/template-functions.php:266 -msgid "Green" -msgstr "" - -#: ../includes/template-functions.php:267 -#, fuzzy -msgid "Teal" -msgstr "Totale" - -#: ../includes/template-functions.php:268 -#, fuzzy -msgid "Black" -msgstr "Torna indietro" - -#: ../includes/template-functions.php:269 -msgid "Dark Gray" -msgstr "" - -#: ../includes/template-functions.php:270 -msgid "Orange" -msgstr "" - -#: ../includes/template-functions.php:271 -msgid "Purple" -msgstr "" - -#: ../includes/template-functions.php:272 -#, fuzzy -msgid "Slate" -msgstr "Data" - -#: ../includes/template-functions.php:295 -#, fuzzy -msgid "You have already purchased this item, but you may purchase it again." -msgstr "Hai già aggiunto questo prodotto al tuo carrello" - -#: ../includes/thickbox.php:29 -#: ../includes/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "" - -#: ../includes/thickbox.php:30 -======= -#: ../includes/post-types.php:200 -#, fuzzy -msgid "Parent Tag:" -msgstr "Parent Tag:" - -#: ../includes/post-types.php:201 -#, fuzzy -msgid "Edit Tag" -msgstr "Modifica Tag" - -#: ../includes/post-types.php:202 -#, fuzzy -msgid "Update Tag" -msgstr "Aggiornamento Tag" - -#: ../includes/post-types.php:203 -#, fuzzy -msgid "Add New Tag" -msgstr "Aggiungi nuovo tag" - -#: ../includes/post-types.php:204 -msgid "New Tag Name" -msgstr "Nuovo Nome Tag" - -#: ../includes/post-types.php:233 -#: ../includes/post-types.php:234 -#, fuzzy -msgid "Download updated." -msgstr "Download aggiornato." - -#: ../includes/post-types.php:235 -#, fuzzy -msgid "Download published." -msgstr "Download pubblicato." - -#: ../includes/post-types.php:236 -#, fuzzy -msgid "Download saved." -msgstr "Download salvato." - -#: ../includes/post-types.php:237 -#, fuzzy -msgid "Download submitted." -msgstr "Download consegnato." - -#: ../includes/shortcodes.php:333 -#, fuzzy, php-format -msgid "No %s found" -msgstr "Nessun pagamento trovato" - -#: ../includes/gateways/paypal.php:271 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Invalid IPN" -msgstr "IPN non valido" - -<<<<<<< HEAD -#: ../includes/thickbox.php:65 -======= -#: ../includes/templates/history-purchases.php:11 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Purchase ID" -msgstr "Acquisto ID:" - -<<<<<<< HEAD -#: ../includes/thickbox.php:88 -#, fuzzy, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "Usa il modulo qui sotto per inserire lo short code per l'acquisto di un download." - -#: ../includes/thickbox.php:91 -#, fuzzy, php-format -msgid "Choose a %s" -msgstr "Scegli uno stile" - -#: ../includes/thickbox.php:100 -#, fuzzy -msgid "Choose a style" -msgstr "Scegli uno stile" - -#: ../includes/thickbox.php:111 -msgid "Choose a button color" -msgstr "Scegli un colore per il pulsante" - -#: ../includes/thickbox.php:120 -======= -#: ../includes/templates/history-purchases.php:13 -#, fuzzy -msgid "Amount" -msgstr "Quantità" - -#: ../includes/templates/history-purchases.php:14 -#, fuzzy -msgid "Files" -msgstr "Files" - -#: ../includes/templates/history-purchases.php:54 -#, fuzzy -msgid "You have not made any purchases" -msgstr "Hai già acquistato questo articolo." - -#: ../includes/templates/checkout_cart.php:6 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Item Name" -msgstr "Nome Articolo" - -<<<<<<< HEAD -#: ../includes/thickbox.php:124 -#, fuzzy -msgid "Cancel" -msgstr "Annullare" - -#: ../includes/thickbox.php:126 -msgid "Button Styles" -msgstr "Stili Pulsante" - -#: ../includes/widgets.php:39 -======= -#: ../includes/templates/checkout_cart.php:7 -#, fuzzy -msgid "Item Price" -msgstr "Prezzo Articolo " - -#: ../includes/templates/checkout_cart.php:8 -#: ../includes/admin-pages/discount-codes.php:47 -#: ../includes/admin-pages/discount-codes.php:60 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Actions" -msgstr "Azioni" - -<<<<<<< HEAD -#: ../includes/widgets.php:39 -======= -#: ../includes/templates/checkout_cart.php:49 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Total" -msgstr "Totale" - -<<<<<<< HEAD -#: ../includes/widgets.php:82 -#: ../includes/widgets.php:162 -======= -#: ../includes/templates/history-downloads.php:10 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Download Name" -msgstr "Download Nome" - -<<<<<<< HEAD -#: ../includes/widgets.php:87 -======= -#: ../includes/templates/history-downloads.php:72 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "You have not purchased any downloads" -msgstr "Non hai ancora acquistato alcun download" - -<<<<<<< HEAD -#: ../includes/widgets.php:112 -======= -#: ../includes/admin-pages/discount-codes.php:40 -#: ../includes/admin-pages/discount-codes.php:53 -#: ../includes/admin-pages/forms/edit-discount.php:32 -#: ../includes/admin-pages/forms/add-discount.php:27 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Downloads Categories / Tags" -msgstr "Downloads Carello" - -<<<<<<< HEAD -#: ../includes/widgets.php:112 -#, fuzzy -msgid "Display the downloads categories or tags" -msgstr "Visualizzare il carrello della spesa download" - -#: ../includes/widgets.php:167 -msgid "Taxonomy:" -msgstr "" - -#: ../includes/widgets.php:193 -msgid "Display a user's purchase history" -msgstr "" - -#: ../includes/widgets.php:232 -#, fuzzy -msgid "No downloadable files found." -msgstr "Nessun file scaricabile trovato." - -#: ../includes/widgets.php:259 -#, fuzzy -msgid "Title" -msgstr "Titolo:" - -#: ../includes/admin-pages/add-ons.php:69 -#, fuzzy -msgid "Add Ons for Easy Digital Downloads" -msgstr "Easy Digital Download Impostazioni" - -#: ../includes/admin-pages/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "" - -#: ../includes/admin-pages/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:40 -#: ../includes/admin-pages/discount-codes.php:53 -#, fuzzy -msgid "Code" -msgstr "Codice" - -#: ../includes/admin-pages/discount-codes.php:41 -#: ../includes/admin-pages/discount-codes.php:54 -#, fuzzy -msgid "Amount" -msgstr "Quantità" - -======= ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#: ../includes/admin-pages/discount-codes.php:42 -#: ../includes/admin-pages/discount-codes.php:55 -#, fuzzy -msgid "Uses" -msgstr "Utilizza" - -#: ../includes/admin-pages/discount-codes.php:43 -#: ../includes/admin-pages/discount-codes.php:56 -<<<<<<< HEAD -======= -#: ../includes/admin-pages/forms/edit-discount.php:80 -#: ../includes/admin-pages/forms/add-discount.php:84 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Max Uses" -msgstr "Max Usa" - -#: ../includes/admin-pages/discount-codes.php:44 -#: ../includes/admin-pages/discount-codes.php:57 -#, fuzzy -msgid "Start Date" -msgstr "Data di inizio" - -#: ../includes/admin-pages/discount-codes.php:45 -#: ../includes/admin-pages/discount-codes.php:58 -#, fuzzy -msgid "Expiration" -msgstr "Scadenza" - -<<<<<<< HEAD -#: ../includes/admin-pages/discount-codes.php:47 -#: ../includes/admin-pages/discount-codes.php:60 -#, fuzzy -msgid "Actions" -msgstr "Azioni" - -======= ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#: ../includes/admin-pages/discount-codes.php:82 -#: ../includes/admin-pages/discount-codes.php:84 -#, fuzzy -msgid "unlimited" -msgstr "illimitato" - -#: ../includes/admin-pages/discount-codes.php:93 -#, fuzzy -msgid "No start date" -msgstr "Nessuna data di inizio" - -#: ../includes/admin-pages/discount-codes.php:100 -#, fuzzy -msgid "Expired" -msgstr "Scaduto" - -#: ../includes/admin-pages/discount-codes.php:102 -#, fuzzy -msgid "no expiration" -msgstr "senza scadenza" - -#: ../includes/admin-pages/discount-codes.php:108 -#: ../includes/admin-pages/payments-history.php:176 -#, fuzzy -msgid "Edit" -msgstr "Modifica" - -#: ../includes/admin-pages/discount-codes.php:110 -#, fuzzy -msgid "Deactivate" -msgstr "Disattivare" - -#: ../includes/admin-pages/discount-codes.php:112 -#, fuzzy -msgid "Activate" -msgstr "Attivare" - -#: ../includes/admin-pages/discount-codes.php:114 -#: ../includes/admin-pages/payments-history.php:178 -#, fuzzy -msgid "Delete" -msgstr "Cancellare" - -#: ../includes/admin-pages/discount-codes.php:119 -#, fuzzy -msgid "No discount codes have been created." -msgstr "Non ci sono codici sconto che sono stati creati." - -<<<<<<< HEAD -======= -#: ../includes/admin-pages/reports.php:41 -msgid "Download Sales and Earnings PDF Report for all Products" -msgstr "" - -#: ../includes/admin-pages/reports.php:42 -msgid "Download a CSV Customers List" -msgstr "" - -#: ../includes/admin-pages/reports.php:44 -#, fuzzy -msgid "Please Note: Transactions created while in test mode are not included on this page or in the PDF reports." -msgstr "Transazioni create nella modalità di prova non sono incluse in questa pagina." - ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#: ../includes/admin-pages/payments-history.php:81 -msgid "All" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:86 -#, fuzzy -msgid "Completed" -msgstr "Completato" - -#: ../includes/admin-pages/payments-history.php:97 -#, fuzzy -msgid "Export" -<<<<<<< HEAD -msgstr "Scadenza" -======= -msgstr "Rapporti" ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d - -#: ../includes/admin-pages/payments-history.php:101 -#, fuzzy -msgid "Payment mode" -msgstr "Modalità di pagamento" - -#: ../includes/admin-pages/payments-history.php:103 -#, fuzzy -msgid "Live" -msgstr "Vivere" - -#: ../includes/admin-pages/payments-history.php:104 -msgid "Test" -msgstr "Test" - -#: ../includes/admin-pages/payments-history.php:114 -#, fuzzy -msgid "Payments per page" -msgstr "Pagamenti per pagina" - -#: ../includes/admin-pages/payments-history.php:116 -#, fuzzy -msgid "Show" -msgstr "Mostra" - -#: ../includes/admin-pages/payments-history.php:122 -#, fuzzy -msgid "Showing payments for: " -msgstr "Nessun pagamento trovato" - -#: ../includes/admin-pages/payments-history.php:122 -<<<<<<< HEAD -#, fuzzy -msgid "clear" -msgstr "Anno" -======= -msgid "clear" -msgstr "" ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d - -#: ../includes/admin-pages/payments-history.php:177 -#, fuzzy -msgid "Resend Purchase Receipt" -msgstr "Rispedisci ricevuta d'acquisto" - -#: ../includes/admin-pages/payments-history.php:190 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "Acquisto Dettagli per il pagamento #%s" - -#: ../includes/admin-pages/payments-history.php:190 -#, fuzzy -msgid "View Order Details" -msgstr "Visualizza dettagli dell'ordine" - -#: ../includes/admin-pages/payments-history.php:198 -#, fuzzy -msgid "Purchased File" -msgstr "File Acquistato" - -#: ../includes/admin-pages/payments-history.php:198 -#, fuzzy -msgid "Purchased Files" -msgstr "I file acquistati" - -#: ../includes/admin-pages/payments-history.php:241 -#, fuzzy -msgid "Discount used:" -msgstr "Sconto utilizzato:" - -#: ../includes/admin-pages/payments-history.php:242 -#, fuzzy -msgid "Total:" -msgstr "Totale:" - -#: ../includes/admin-pages/payments-history.php:245 -#, fuzzy -msgid "Buyer's Personal Details:" -msgstr "Dettagli personali Clienti:" - -#: ../includes/admin-pages/payments-history.php:247 -#, fuzzy -msgid "Name:" -msgstr "Nome:" - -#: ../includes/admin-pages/payments-history.php:248 -#, fuzzy -msgid "Email:" -msgstr "Email:" - -#: ../includes/admin-pages/payments-history.php:257 -<<<<<<< HEAD -#, fuzzy -msgid "Payment Method:" -msgstr "Modalità di pagamento" - -#: ../includes/admin-pages/payments-history.php:262 -#, fuzzy -msgid "Purchase Key" -msgstr "Acquista" - -#: ../includes/admin-pages/payments-history.php:265 -#, fuzzy -msgid "Close" -msgstr "Chiudere" - -#: ../includes/admin-pages/payments-history.php:295 -#, fuzzy -msgid "Total Earnings:" -msgstr "Risultato:" - -#: ../includes/admin-pages/reports.php:41 -msgid "Download Sales and Earnings PDF Report for all Products" -msgstr "" - -#: ../includes/admin-pages/reports.php:42 -msgid "Download a CSV Customers List" -msgstr "" - -#: ../includes/admin-pages/reports.php:44 -#, fuzzy -msgid "Please Note: Transactions created while in test mode are not included on this page or in the PDF reports." -msgstr "Transazioni create nella modalità di prova non sono incluse in questa pagina." -======= -#, fuzzy -msgid "Payment Method:" -msgstr "Modalità di pagamento" - -#: ../includes/admin-pages/payments-history.php:262 -#, fuzzy -msgid "Purchase Key" -msgstr "Testo per tasto Acquisto" - -#: ../includes/admin-pages/payments-history.php:265 -#: ../includes/admin-pages/forms/edit-payment.php:92 -#, fuzzy -msgid "Close" -msgstr "Chiudere" - -#: ../includes/admin-pages/payments-history.php:295 -#, fuzzy -msgid "Total Earnings:" -msgstr "Risultato:" ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d - -#: ../includes/admin-pages/settings.php:33 -#, fuzzy -msgid "General" -msgstr "Generale" - -#: ../includes/admin-pages/settings.php:35 -#, fuzzy -msgid "Emails" -msgstr "E-mail" - -#: ../includes/admin-pages/settings.php:36 -#, fuzzy -msgid "Styles" -<<<<<<< HEAD -msgstr "Vendite" -======= -msgstr "Stili Pulsante" ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d - -#: ../includes/admin-pages/settings.php:37 -#, fuzzy -msgid "Misc" -msgstr "Misc" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:12 -======= -#: ../includes/admin-pages/add-ons.php:69 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Add Ons for Easy Digital Downloads" -msgstr "Easy Digital Download Impostazioni" - -#: ../includes/admin-pages/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "" - -#: ../includes/admin-pages/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:13 -#, fuzzy -msgid "Something went wrong." -msgstr "Qualcosa è andato storto." - -#: ../includes/admin-pages/forms/edit-discount.php:17 -#, fuzzy -msgid "Edit Discount" -msgstr "Modifica Sconto" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:22 -#: ../includes/admin-pages/forms/edit-discount.php:27 -======= -#: ../includes/admin-pages/forms/edit-discount.php:17 -#: ../includes/admin-pages/forms/edit-payment.php:16 -#, fuzzy -msgid "Go Back" -msgstr "Torna indietro" - -#: ../includes/admin-pages/forms/edit-discount.php:27 -#: ../includes/admin-pages/forms/add-discount.php:22 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The name of this discount" -msgstr "Il nome di questo sconto" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:31 -#: ../includes/admin-pages/forms/edit-discount.php:36 -======= -#: ../includes/admin-pages/forms/edit-discount.php:36 -#: ../includes/admin-pages/forms/add-discount.php:31 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "Inserire un codice per lo sconto, come 10PERCENT" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:36 -#: ../includes/admin-pages/forms/edit-discount.php:41 -======= -#: ../includes/admin-pages/forms/edit-discount.php:41 -#: ../includes/admin-pages/forms/add-discount.php:36 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Type" -msgstr "Tipo" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:40 -#: ../includes/admin-pages/forms/edit-discount.php:45 -======= -#: ../includes/admin-pages/forms/edit-discount.php:45 -#: ../includes/admin-pages/forms/add-discount.php:40 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Percentage" -msgstr "Percentuale" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:41 -#: ../includes/admin-pages/forms/edit-discount.php:46 -======= -#: ../includes/admin-pages/forms/edit-discount.php:46 -#: ../includes/admin-pages/forms/add-discount.php:41 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Flat amount" -msgstr "Importo forfettario" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:43 -#: ../includes/admin-pages/forms/edit-discount.php:48 -======= -#: ../includes/admin-pages/forms/edit-discount.php:48 -#: ../includes/admin-pages/forms/add-discount.php:43 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The kind of discount to apply for this discount." -msgstr "Il tipo di sconto da applicare a questo sconto." - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:52 -#: ../includes/admin-pages/forms/edit-discount.php:57 -======= -#: ../includes/admin-pages/forms/edit-discount.php:57 -#: ../includes/admin-pages/forms/add-discount.php:52 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The amount of this discount code." -msgstr "L'importo di questo codice sconto." - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:57 -#: ../includes/admin-pages/forms/edit-discount.php:62 -======= -#: ../includes/admin-pages/forms/edit-discount.php:62 -#: ../includes/admin-pages/forms/add-discount.php:57 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Start date" -msgstr "Data di inizio" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:61 -#: ../includes/admin-pages/forms/edit-discount.php:66 -======= -#: ../includes/admin-pages/forms/edit-discount.php:66 -#: ../includes/admin-pages/forms/add-discount.php:61 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Enter the start date for this discount code in the format of yyyy-mm-dd. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "Inserisci la data di inizio per questo codice di sconto nel formato aaaa-mm-dd. Per nessuna data di inizio, lasciare in bianco. Se è inserito, lo sconto può essere utilizzato solo dopo o in questa data." - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:66 -#: ../includes/admin-pages/forms/edit-discount.php:71 -======= -#: ../includes/admin-pages/forms/edit-discount.php:71 -#: ../includes/admin-pages/forms/add-discount.php:66 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Expiration date" -msgstr "Data di scadenza" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:70 -#: ../includes/admin-pages/forms/edit-discount.php:75 -======= -#: ../includes/admin-pages/forms/edit-discount.php:75 -#: ../includes/admin-pages/forms/add-discount.php:70 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Enter the expiration date for this discount code in the format of yyyy-mm-dd. For no expiration, leave blank" -msgstr "Inserisci la data di scadenza per questo codice di sconto nel formato aaaa-mm-dd. Per nessuna scadenza, lasciare in bianco" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:75 -#: ../includes/admin-pages/forms/edit-discount.php:89 -#, fuzzy -msgid "Minimum Amount" -msgstr "Quantità" - -#: ../includes/admin-pages/forms/add-discount.php:79 -#: ../includes/admin-pages/forms/edit-discount.php:93 -#, fuzzy -msgid "The minimum amount that must be purchased before this discount can be used. Leave blank for no minimum." -msgstr "Il numero massimo di volte che questo sconto può essere utilizzato. Lasciare in bianco per un numero illimitato." - -#: ../includes/admin-pages/forms/add-discount.php:88 -#: ../includes/admin-pages/forms/edit-discount.php:84 -======= -#: ../includes/admin-pages/forms/edit-discount.php:84 -#: ../includes/admin-pages/forms/add-discount.php:88 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "Il numero massimo di volte che questo sconto può essere utilizzato. Lasciare in bianco per un numero illimitato." - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/add-discount.php:96 -#, fuzzy -msgid "Add Discount Code" -msgstr "Aggiungi Codice Sconto" - -#: ../includes/admin-pages/forms/edit-discount.php:13 -#, fuzzy -msgid "Something went wrong." -msgstr "Qualcosa è andato storto." - -#: ../includes/admin-pages/forms/edit-discount.php:17 -======= -#: ../includes/admin-pages/forms/edit-discount.php:89 -#: ../includes/admin-pages/forms/add-discount.php:75 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "Minimum Amount" -msgstr "Quantità" - -<<<<<<< HEAD -#: ../includes/admin-pages/forms/edit-discount.php:17 -#: ../includes/admin-pages/forms/edit-payment.php:16 -======= -#: ../includes/admin-pages/forms/edit-discount.php:93 -#: ../includes/admin-pages/forms/add-discount.php:79 ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -msgid "The minimum amount that must be purchased before this discount can be used. Leave blank for no minimum." -msgstr "Il numero massimo di volte che questo sconto può essere utilizzato. Lasciare in bianco per un numero illimitato." - -#: ../includes/admin-pages/forms/edit-discount.php:102 -#, fuzzy -msgid "Active" -msgstr "Attivo" - -#: ../includes/admin-pages/forms/edit-discount.php:103 -#, fuzzy -msgid "Inactive" -msgstr "Inattivo" - -#: ../includes/admin-pages/forms/edit-discount.php:105 -#, fuzzy -msgid "The status of this discount code." -msgstr "Lo stato di questo codice sconto." - -#: ../includes/admin-pages/forms/edit-discount.php:115 -#, fuzzy -msgid "Update Discount Code" -msgstr "Aggiorna il Codice Sconto" - -<<<<<<< HEAD -======= -#: ../includes/admin-pages/forms/add-discount.php:12 -#, fuzzy -msgid "Add New Discount" -msgstr "Aggiungi Nuovo Sconto" - -#: ../includes/admin-pages/forms/add-discount.php:96 -#, fuzzy -msgid "Add Discount Code" -msgstr "Aggiungi Codice Sconto" - ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#: ../includes/admin-pages/forms/edit-payment.php:22 -#, fuzzy -msgid "Buyer's Email" -msgstr "Da E-mail" - -#: ../includes/admin-pages/forms/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:31 -#, fuzzy -msgid "Downloads Purchased" -msgstr "Downloads Acquistato" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "Aggiungi il download per l'acquisto di #%s" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -#, fuzzy -msgid "Add download to purchase" -msgstr "Aggiungi il download per l'acquisto" - -#: ../includes/admin-pages/forms/edit-payment.php:48 -#, fuzzy -msgid "Payment Status" -msgstr "Controlla lo stato dell'ordine" - -#: ../includes/admin-pages/forms/edit-payment.php:64 -#, fuzzy -msgid "Send Purchase Receipt" -msgstr "Rispedisci ricevuta d'acquisto" - -#: ../includes/admin-pages/forms/edit-payment.php:68 -msgid "Check this box to send the purchase receipt, including all download links." -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:78 -#, fuzzy -msgid "Update Payment" -msgstr "Aggiorna Pagamento" - -#: ../includes/admin-pages/forms/edit-payment.php:91 -#, fuzzy -msgid "Add Selected Downloads" -msgstr "Aggiungi Downloads selezionati" - -<<<<<<< HEAD -#: ../includes/gateways/paypal.php:271 -======= ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -#~ msgid "Your shopping cart is empty" -#~ msgstr "Il carrello è vuoto" - -#, fuzzy -#~ msgid "email@domain.com" -#~ msgstr "email@dominio.com" - -#, fuzzy -#~ msgid "Manual Payment" -#~ msgstr "Pagamento Manuale" - -#, fuzzy -#~ msgid "Enter the download price" -#~ msgstr "Inserisci il prezzo per il download" - -#, fuzzy -#~ msgid "Download Stats" -#~ msgstr "Statistiche di download" - -#, fuzzy -#~ msgid "Short Code for this Download" -#~ msgstr "Short Code per questo download" - -<<<<<<< HEAD -#: ../includes/templates/checkout_cart.php:6 -#, fuzzy -msgid "Item Name" -msgstr "Nome Articolo" - -#: ../includes/templates/checkout_cart.php:7 -#, fuzzy -msgid "Item Price" -msgstr "Prezzo Articolo " - -#: ../includes/templates/checkout_cart.php:49 -#, fuzzy -msgid "Total" -msgstr "Totale" - -#: ../includes/templates/history-downloads.php:10 -#, fuzzy -msgid "Download Name" -msgstr "Download Nome" - -#: ../includes/templates/history-downloads.php:12 -#: ../includes/templates/history-purchases.php:14 -#, fuzzy -msgid "Files" -msgstr "Files" - -#: ../includes/templates/history-downloads.php:72 -#, fuzzy -msgid "You have not purchased any downloads" -msgstr "Non hai ancora acquistato alcun download" - -#: ../includes/templates/history-purchases.php:11 -#, fuzzy -msgid "Purchase ID" -msgstr "Acquisto ID:" - -#: ../includes/templates/history-purchases.php:54 -#, fuzzy -msgid "You have not made any purchases" -msgstr "Hai già acquistato questo articolo." - -#, fuzzy -#~ msgid "Your shopping cart is empty" -#~ msgstr "Il carrello è vuoto" - -#, fuzzy -#~ msgid "email@domain.com" -#~ msgstr "email@dominio.com" - -#, fuzzy -#~ msgid "Manual Payment" -#~ msgstr "Pagamento Manuale" - -#, fuzzy -#~ msgid "Enter the download price" -#~ msgstr "Inserisci il prezzo per il download" - -#, fuzzy -#~ msgid "Download Stats" -#~ msgstr "Statistiche di download" - -#, fuzzy -#~ msgid "Short Code for this Download" -#~ msgstr "Short Code per questo download" - -======= ->>>>>>> 2835288eaff7fca70441ef9afae39b132d8d211d -#, fuzzy -#~ msgid "pending" -#~ msgstr "in attesa" - -#, fuzzy -#~ msgid "complete" -#~ msgstr "completato" - -#, fuzzy -#~ msgid "Edit Download" -#~ msgstr "Modifica Scarica" - -#, fuzzy -#~ msgid "New Download" -#~ msgstr "Nuovo Scarica" - -#, fuzzy -#~ msgid "All Downloads" -#~ msgstr "Tutti i download" - -#, fuzzy -#~ msgid "View Download" -#~ msgstr "Visualizza Scarica" - -#, fuzzy -#~ msgid "Search Downloads" -#~ msgstr "Cerca Downloads" - -#~ msgid "No Downloads found in Trash" -#~ msgstr "Nessun download trovato nel Cestino" - -#, fuzzy -#~ msgid "Something has gone wrong, please try again" -#~ msgstr "Qualcosa è andato storto, riprova" - -#, fuzzy -#~ msgid "Use this plugin in test mode" -#~ msgstr "Utilizzare questo plugin in modalità di prova" - -#, fuzzy -#~ msgid "Purchase Page" -#~ msgstr "Acquisto Pagina" - -#, fuzzy -#~ msgid "Disable cURL for PayPal IPN" -#~ msgstr "Disattivare cURL per PayPal IPN" - -#, fuzzy -#~ msgid "Add to Cart" -#~ msgstr "Aggiungi al carrello" - -#, fuzzy -#~ msgid "No downloads found" -#~ msgstr "Nessun download trovato" - -#, fuzzy -#~ msgid "here" -#~ msgstr "qui" - -#, fuzzy -#~ msgid "Choose a download" -#~ msgstr "Scegli un download" - -#, fuzzy -#~ msgid "Price: " -#~ msgstr "Prezzo:" diff --git a/languages/edd-ja.mo b/languages/edd-ja.mo deleted file mode 100755 index 8a87e9a1446..00000000000 Binary files a/languages/edd-ja.mo and /dev/null differ diff --git a/languages/edd-ja.po b/languages/edd-ja.po deleted file mode 100755 index dd0a8428c67..00000000000 --- a/languages/edd-ja.po +++ /dev/null @@ -1,1824 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-05-31 10:45-0600\n" -"PO-Revision-Date: 2012-07-07 19:04+0900\n" -"Last-Translator: Jun Shirasawa \n" -"Language-Team: Pippin's Plugins\n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_n;_x\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-Language: English\n" -"X-Poedit-Country: UNITED STATES\n" -"X-Poedit-SearchPath-0: ..\n" -"X-Poedit-SearchPath-1: ../includes\n" -"X-Poedit-SearchPath-2: ../includes/admin-pages\n" -"X-Poedit-SearchPath-3: ../includes/admin-pages/forms\n" -"X-Poedit-SearchPath-4: ../includes/gateways\n" -"X-Poedit-SearchPath-5: .\n" - -#: ../includes/template-functions.php:55 -#: ../includes/thickbox.php:61 -#: ../includes/dashboard-columns.php:69 -msgid "Purchase" -msgstr "購入" - -#: ../includes/template-functions.php:110 -#: ../includes/template-functions.php:121 -#: ../includes/cart-template.php:50 -#: ../includes/cart-template.php:53 -msgid "Checkout" -msgstr "お会計" - -#: ../includes/template-functions.php:128 -msgid "added to your cart" -msgstr "買い物カゴに入りました" - -#: ../includes/template-functions.php:219 -msgid "Gray" -msgstr "グレー" - -#: ../includes/template-functions.php:220 -msgid "Pink" -msgstr "ピンク" - -#: ../includes/template-functions.php:221 -msgid "Blue" -msgstr "青" - -#: ../includes/template-functions.php:222 -msgid "Green" -msgstr "緑" - -#: ../includes/template-functions.php:223 -msgid "Teal" -msgstr "青緑" - -#: ../includes/template-functions.php:224 -msgid "Black" -msgstr "黒" - -#: ../includes/template-functions.php:225 -msgid "Dark Gray" -msgstr "ダークグレイ" - -#: ../includes/template-functions.php:226 -msgid "Orange" -msgstr "オレンジ" - -#: ../includes/template-functions.php:227 -msgid "Purple" -msgstr "紫" - -#: ../includes/template-functions.php:228 -msgid "Slate" -msgstr "青灰色" - -#: ../includes/template-functions.php:245 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "既に購入済です(が、再購入は可能)。" - -#: ../includes/payment-functions.php:287 -msgid "Pending" -msgstr "保留" - -#: ../includes/payment-functions.php:288 -msgid "Complete" -msgstr "完了" - -#: ../includes/payment-functions.php:289 -msgid "Refunded" -msgstr "返金されました" - -#: ../includes/email-template.php:76 -msgid "Sample Product Title" -msgstr "[商品名]" - -#: ../includes/email-template.php:79 -msgid "Sample Download File Name" -msgstr "[ダウンロードファイル名]" - -#: ../includes/email-template.php:121 -msgid "Purchase Receipt Preview" -msgstr "購入通知プレビュー" - -#: ../includes/email-template.php:121 -msgid "Preview Purchase Receipt" -msgstr "購入通知をプレビューする" - -#: ../includes/email-template.php:126 -msgid "Close" -msgstr "閉じる" - -#: ../includes/email-template.php:255 -msgid "Default Template" -msgstr "デフォルトテンプレート" - -#: ../includes/email-template.php:256 -msgid "No template, plain text only" -msgstr "テンプレートなし、プレーンテキストのみ" - -#: ../includes/cart-template.php:83 -msgid "remove" -msgstr "削除" - -#: ../includes/cart-template.php:100 -msgid "Your cart is empty." -msgstr "買い物カゴは空です。" - -#: ../includes/thickbox.php:29 -#: ../includes/thickbox.php:30 -#: ../includes/thickbox.php:123 -msgid "Insert Download" -msgstr "ダウンロードを挿入" - -#: ../includes/thickbox.php:65 -msgid "You must choose a download" -msgstr "ダウンロードを選択して下さい" - -#: ../includes/thickbox.php:88 -msgid "Use the form below to insert the short code for purchasing a download." -msgstr "ダウンロード購入用ショートコードを挿入するのに以下のフォームを使って下さい。" - -#: ../includes/thickbox.php:91 -msgid "Choose a download" -msgstr "ダウンロードを選択" - -#: ../includes/thickbox.php:100 -msgid "Choose a style" -msgstr "スタイルを選択" - -#: ../includes/thickbox.php:111 -msgid "Choose a button color" -msgstr "ボタン色を選択" - -#: ../includes/thickbox.php:120 -msgid "Link text . . ." -msgstr "リンク文言..." - -#: ../includes/thickbox.php:124 -msgid "Cancel" -msgstr "キャンセル" - -#: ../includes/thickbox.php:126 -msgid "Button Styles" -msgstr "ボタン外観" - -#: ../includes/register-settings.php:41 -msgid "Test Mode" -msgstr "テストモード" - -#: ../includes/register-settings.php:42 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "テストモード中は、本番のトランザクションは一切処理されません。完全なテストモードを使うには、利用する決済代行機関のサンドボックス(テスト環境)アカウントを持っている必要があります。" - -#: ../includes/register-settings.php:47 -msgid "Purchase Page" -msgstr "決済ページ" - -#: ../includes/register-settings.php:48 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "購入者が決済を行って購入を完了させるためのページです" - -#: ../includes/register-settings.php:54 -msgid "Success Page" -msgstr "完了ページ" - -#: ../includes/register-settings.php:55 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "購入処理が完了した後に購入者が目にするページです" - -#: ../includes/register-settings.php:61 -msgid "Currency Settings" -msgstr "通貨設定" - -#: ../includes/register-settings.php:62 -msgid "Configure the currency options" -msgstr "通貨に関するオプション設定" - -#: ../includes/register-settings.php:67 -msgid "Currency" -msgstr "通貨" - -#: ../includes/register-settings.php:68 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "通貨を選択して下さい。いくつかの決済システムでは利用できる通貨が限られていることに留意下さい。" - -#: ../includes/register-settings.php:74 -msgid "Currency Position" -msgstr "通貨記号の位置" - -#: ../includes/register-settings.php:75 -msgid "Choose the location of the currency sign." -msgstr "通貨記号の位置を選択して下さい。" - -#: ../includes/register-settings.php:78 -msgid "Before - $10" -msgstr "前 - $10" - -#: ../includes/register-settings.php:79 -msgid "After - 10$" -msgstr "後 - 10$" - -#: ../includes/register-settings.php:84 -msgid "Thousands Separator" -msgstr "桁区切り" - -#: ../includes/register-settings.php:85 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "桁区切り記号(通常は「,」)" - -#: ../includes/register-settings.php:92 -msgid "Decimal Separator" -msgstr "小数点区切り" - -#: ../includes/register-settings.php:93 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "小数点区切り記号(通常は「.」)" - -#: ../includes/register-settings.php:104 -msgid "Payment Gateways" -msgstr "決済代行機関" - -#: ../includes/register-settings.php:105 -msgid "Choose the payment gateways you want to enable." -msgstr "利用したい決済代行機関を選んでください。" - -#: ../includes/register-settings.php:111 -msgid "Accepted Payment Method Icons" -msgstr "利用可能な決済代行機関のアイコン" - -#: ../includes/register-settings.php:112 -msgid "Display icons for the selected payment methods" -msgstr "選択された決済代行機関のアイコンを表示する" - -#: ../includes/register-settings.php:112 -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "クレジットカードを利用可能にする場合、決済代行機関用の設定が必要になる場合があります" - -#: ../includes/register-settings.php:124 -msgid "PayPal Settings" -msgstr "PayPal設定" - -#: ../includes/register-settings.php:125 -msgid "Configure the PayPal settings" -msgstr "PayPal設定の実行" - -#: ../includes/register-settings.php:130 -msgid "PayPal Email" -msgstr "PayPalメール" - -#: ../includes/register-settings.php:131 -msgid "Enter your PayPal account's email" -msgstr "PayPalアカウントのメールアドレス" - -#: ../includes/register-settings.php:137 -msgid "Alternate PayPal Purchase Verification" -msgstr "PayPal購入確認を代替する" - -#: ../includes/register-settings.php:138 -msgid "If payments are not getting marked as complete, then check this box. Note, this requires that buyers return to your site from PayPal." -msgstr "決済が完了にならない場合、このチェックボックスをオンにしてみて下さい。この場合、購入者はPayPalからあなたのサイトに戻されることに留意下さい。" - -#: ../includes/register-settings.php:147 -msgid "From Name" -msgstr "差出人名" - -#: ../includes/register-settings.php:148 -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "請求書発行元の名前です。あなたのサイトやショップの名前など。" - -#: ../includes/register-settings.php:153 -msgid "From Email" -msgstr "差出人メールアドレス" - -#: ../includes/register-settings.php:154 -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "購入通知メールの送信元アドレスです。これはFrom、またreply-toとしても機能します。" - -#: ../includes/register-settings.php:159 -msgid "Purchase Email Subject" -msgstr "購入メール件名" - -#: ../includes/register-settings.php:160 -msgid "Enter the subject line for the purchase receipt email" -msgstr "購入通知メールの件名を入力して下さい" - -#: ../includes/register-settings.php:165 -msgid "Purchase Receipt" -msgstr "購入通知" - -#: ../includes/register-settings.php:166 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "購入処理が完了した時にユーザーに送られるメールの本文を入力して下さい。HTMLも利用できます。利用可能なタグは以下の通り:" - -#: ../includes/register-settings.php:167 -msgid "A list of download URLs for each download purchased" -msgstr "購入いただいた商品のダウンロードリンクは以下の通りです" - -#: ../includes/register-settings.php:168 -msgid "The buyer's name" -msgstr "ご購入者氏名" - -#: ../includes/register-settings.php:169 -msgid "The date of the purchase" -msgstr "ご購入年月日" - -#: ../includes/register-settings.php:170 -msgid "The total price of the purchase" -msgstr "ご購入金額合計" - -#: ../includes/register-settings.php:171 -msgid "Your site name" -msgstr "サイト名" - -#: ../includes/register-settings.php:193 -msgid "Disable Styles" -msgstr "外観を無効化" - -#: ../includes/register-settings.php:194 -msgid "Check this to disable all included styling" -msgstr "すべての外観設定を無効化するにはここをチェックして下さい" - -#: ../includes/register-settings.php:199 -msgid "Checkout Button Color" -msgstr "会計ボタンの色" - -#: ../includes/register-settings.php:200 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "会計ボタンに使う色を選択して下さい" - -#: ../includes/register-settings.php:210 -msgid "Enable Ajax" -msgstr "Ajaxを有効にする" - -#: ../includes/register-settings.php:211 -msgid "Check this to enable AJAX for the shopping cart." -msgstr "買い物カゴ機能にAjaxを使いたい場合はここをチェックして下さい。" - -#: ../includes/register-settings.php:216 -msgid "Enable jQuery Validation" -msgstr "jQueryバリデーションを有効化する" - -#: ../includes/register-settings.php:217 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "会計フォームにjQueryバリデーションを利用したい場合ここをチェックして下さい。" - -#: ../includes/register-settings.php:222 -msgid "Disable Guest Checkout" -msgstr "ゲスト会計を無効化" - -#: ../includes/register-settings.php:223 -msgid "Require that users be logged-in to purchase files." -msgstr "購入するにはユーザーはログインしなければならない。" - -#: ../includes/register-settings.php:228 -msgid "Show Register / Login Form?" -msgstr "登録/ログインフォームを見せますか?" - -#: ../includes/register-settings.php:229 -msgid "Display the registration and login forms on the checkout page for non-logged-in users" -msgstr "ログインしていないユーザーに登録およびログインフォームを表示する" - -#: ../includes/register-settings.php:234 -msgid "Disable Redownload?" -msgstr "再ダウンロードを無効化しますか?" - -#: ../includes/register-settings.php:235 -msgid "Check this if you do not want to allow users to redownload items from their purchase history" -msgstr "購入記録から再ダウンロードを行えないようにするにはここをチェックして下さい" - -#: ../includes/register-settings.php:240 -msgid "Terms of Agreement" -msgstr "利用規約に同意" - -#: ../includes/register-settings.php:246 -msgid "Agree to Terms" -msgstr "利用規約に同意する" - -#: ../includes/register-settings.php:247 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing" -msgstr "購入前にユーザーが同意しなければならない規約を見せる場合ここをチェックして下さい" - -#: ../includes/register-settings.php:252 -msgid "Agree to Terms Label" -msgstr "規約同意の文言" - -#: ../includes/register-settings.php:253 -msgid "Label shown next to the agree to terms check box" -msgstr "規約同意チェックボックスにあてがう文言" - -#: ../includes/register-settings.php:259 -msgid "Agreement Text" -msgstr "同意文言" - -#: ../includes/register-settings.php:260 -msgid "If Agree to Terms is checked, enter the agreement terms here" -msgstr "規約に同意した場合、それを表す文言" - -#: ../includes/register-settings.php:286 -msgid "General Settings" -msgstr "一般設定" - -#: ../includes/register-settings.php:312 -msgid "Payment Gateway Settings" -msgstr "決済代行機関の設定" - -#: ../includes/register-settings.php:338 -msgid "Email Settings" -msgstr "メール設定" - -#: ../includes/register-settings.php:364 -msgid "Style Settings" -msgstr "スタイル設定" - -#: ../includes/register-settings.php:391 -msgid "Misc Settings" -msgstr "その他の設定" - -#: ../includes/cart-functions.php:390 -#, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "買い物カゴに%sが追加されました。" - -#: ../includes/cart-functions.php:391 -msgid "Checkout." -msgstr "会計する" - -#: ../includes/dashboard-columns.php:26 -msgid "Name" -msgstr "名前" - -#: ../includes/dashboard-columns.php:27 -#: ../includes/widgets.php:168 -msgid "Categories" -msgstr "カテゴリ" - -#: ../includes/dashboard-columns.php:28 -#: ../includes/widgets.php:169 -msgid "Tags" -msgstr "タグ" - -#: ../includes/dashboard-columns.php:29 -msgid "Sales" -msgstr "セールス" - -#: ../includes/dashboard-columns.php:30 -msgid "Earnings" -msgstr "収益" - -#: ../includes/dashboard-columns.php:31 -msgid "Short Code" -msgstr "ショートコード" - -#: ../includes/dashboard-columns.php:32 -msgid "Date" -msgstr "日付" - -#: ../includes/dashboard-columns.php:177 -msgid "Show all categories" -msgstr "すべてのカテゴリを見る" - -#: ../includes/dashboard-columns.php:188 -msgid "Show all tags" -msgstr "すべてのタグを見る" - -#: ../includes/widgets.php:38 -msgid "Downloads Cart" -msgstr "買い物カゴ" - -#: ../includes/widgets.php:38 -msgid "Display the downloads shopping cart" -msgstr "買い物カゴの中を見る" - -#: ../includes/widgets.php:81 -#: ../includes/widgets.php:161 -msgid "Title:" -msgstr "タイトル:" - -#: ../includes/widgets.php:86 -msgid "Show Quantity:" -msgstr "数量を見る:" - -#: ../includes/widgets.php:111 -msgid "Downloads Categories / Tags" -msgstr "カテゴリ/タグ" - -#: ../includes/widgets.php:111 -msgid "Display the downloads categories or tags" -msgstr "ダウンロード商品のカテゴリまたはタグを表示" - -#: ../includes/widgets.php:166 -msgid "Taxonomy:" -msgstr "分類:" - -#: ../includes/scripts.php:40 -msgid "Please enter a discount code" -msgstr "ディスカウントコードを入力してください" - -#: ../includes/scripts.php:41 -msgid "Discount Applied" -msgstr "ディスカウントが適用されました" - -#: ../includes/scripts.php:43 -msgid "You have already added this item to your cart" -msgstr "この商品はすでに買い物カゴに入っています" - -#: ../includes/scripts.php:44 -msgid "Your cart is empty" -msgstr "買い物カゴは空です" - -#: ../includes/scripts.php:45 -msgid "Loading" -msgstr "読み込み中" - -#: ../includes/scripts.php:112 -msgid "Add New Download" -msgstr "新規追加" - -#: ../includes/scripts.php:113 -msgid "Use This File" -msgstr "このファイルを使う" - -#: ../includes/scripts.php:114 -msgid "Are you sure you wish to delete this payment?" -msgstr "この支払いを削除してもよろしいですか?" - -#: ../includes/graphing.php:31 -#: ../includes/graphing.php:77 -msgid "Download" -msgstr "ダウンロード" - -#: ../includes/graphing.php:42 -msgid "Downloads Performance in Sales" -msgstr "ダウンロード売り上げ" - -#: ../includes/graphing.php:88 -msgid "Downloads Performance in Earnings" -msgstr "ダウンロード収益" - -#: ../includes/graphing.php:121 -#: ../includes/checkout-template.php:254 -msgid "Month" -msgstr "月" - -#: ../includes/graphing.php:136 -msgid "Earnings per month" -msgstr "月当たりの収益" - -#: ../includes/metabox.php:22 -msgid "Download Configuration" -msgstr "ダウンロード設定" - -#: ../includes/metabox.php:23 -msgid "Download Stats" -msgstr "ダウンロード統計" - -#: ../includes/metabox.php:24 -msgid "Purchase Log" -msgstr "購入ログ" - -#: ../includes/metabox.php:25 -msgid "File Download Log" -msgstr "ダウンロードログ" - -#: ../includes/metabox.php:26 -msgid "Payment Info" -msgstr "支払い情報" - -#: ../includes/metabox.php:69 -msgid "Pricing" -msgstr "価格設定" - -#: ../includes/metabox.php:73 -msgid "Check this to enable variable pricing." -msgstr "可変価格を有効にするにはここをチェックして下さい。" - -#: ../includes/metabox.php:96 -msgid "price option name" -msgstr "価格オプション名" - -#: ../includes/metabox.php:97 -#: ../includes/metabox.php:107 -msgid "9.99" -msgstr "9.99" - -#: ../includes/metabox.php:106 -msgid "price name" -msgstr "価格名称" - -#: ../includes/metabox.php:110 -msgid "Add New Price Option" -msgstr "価格オプションを追加" - -#: ../includes/metabox.php:126 -msgid "Enter the download price. Do not include a currency symbol" -msgstr "価格を入力してください。通貨記号は含みません" - -#: ../includes/metabox.php:147 -msgid "Download Files" -msgstr "ダウンロードファイル" - -#: ../includes/metabox.php:157 -#: ../includes/metabox.php:168 -msgid "file name" -msgstr "ファイル名" - -#: ../includes/metabox.php:158 -#: ../includes/metabox.php:169 -msgid "file url" -msgstr "ファイルURL" - -#: ../includes/metabox.php:159 -#: ../includes/metabox.php:170 -msgid "Upload File" -msgstr "アップロード" - -#: ../includes/metabox.php:173 -msgid "Add New" -msgstr "新規追加" - -#: ../includes/metabox.php:173 -msgid "Upload the downloadable files." -msgstr "ダウンロード可能なファイルをアップロード" - -#: ../includes/metabox.php:194 -msgid "Purchase Text" -msgstr "購入に対する文言" - -#: ../includes/metabox.php:196 -msgid "Add the text you would like displayed for the purchase text" -msgstr "「購入」の代わりに表示するテキストを追加して下さい" - -#: ../includes/metabox.php:215 -msgid "Link Style" -msgstr "リンクの外観" - -#: ../includes/metabox.php:217 -msgid "Button" -msgstr "ボタン" - -#: ../includes/metabox.php:218 -msgid "Text" -msgstr "テキスト" - -#: ../includes/metabox.php:219 -msgid "Choose the style of the purchase link" -msgstr "購入リンクの外観を選択して下さい" - -#: ../includes/metabox.php:240 -msgid "Button Color" -msgstr "ボタン色" - -#: ../includes/metabox.php:248 -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "上でボタンが選択されている場合、購入リンクの色を選択して下さい。" - -#: ../includes/metabox.php:266 -msgid "Disable the purchase button?" -msgstr "購入ボタンを無効にしますか?" - -#: ../includes/metabox.php:269 -msgid "Check this if you do not want the purchase button displayed." -msgstr "購入ボタンを表示したくない時はここをチェックして下さい。" - -#: ../includes/metabox.php:287 -msgid "Notes" -msgstr "注意" - -#: ../includes/metabox.php:290 -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "外観のオプションはショートコードでのスタイルには反映されません。ショートコードを使うとサイトのどこにでも購入ボタンを配置することができます。" - -#: ../includes/metabox.php:296 -msgid "This short code can be placed anywhere on your site" -msgstr "このショートコードはサイト上のどこにでも使うことができます" - -#: ../includes/metabox.php:383 -msgid "Sales:" -msgstr "セールス:" - -#: ../includes/metabox.php:389 -msgid "Earnings:" -msgstr "収益:" - -#: ../includes/metabox.php:414 -msgid "Sales Log" -msgstr "セールス記録" - -#: ../includes/metabox.php:416 -msgid "Each sale for this download is listed below." -msgstr "このダウンロードに対するそれぞれの売り上げは以下の通りです。" - -#: ../includes/metabox.php:430 -#: ../includes/metabox.php:488 -msgid "Date:" -msgstr "日付:" - -#: ../includes/metabox.php:434 -msgid "Buyer:" -msgstr "購入者:" - -#: ../includes/metabox.php:438 -msgid "Purchase ID:" -msgstr "購入ID:" - -#: ../includes/metabox.php:446 -msgid "No sales yet" -msgstr "まだ売れていません" - -#: ../includes/metabox.php:470 -msgid "Download Log" -msgstr "ダウンロード記録" - -#: ../includes/metabox.php:472 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "ダウンロードされるたびに以下に記録されます。" - -#: ../includes/metabox.php:492 -msgid "Downloaded by:" -msgstr "ダウンロードした人:" - -#: ../includes/metabox.php:496 -msgid "IP Address:" -msgstr "IPアドレス:" - -#: ../includes/metabox.php:500 -msgid "File: " -msgstr "ファイル:" - -#: ../includes/metabox.php:509 -msgid "No file downloads yet yet" -msgstr "まだダウンロードされていません" - -#: ../includes/ajax-functions.php:107 -#: ../includes/process-purchase.php:222 -msgid "The discount you entered is invalid" -msgstr "入力されたディスカウントは正しくありません" - -#: ../includes/admin-pages.php:26 -msgid "Payment History" -msgstr "支払い履歴" - -#: ../includes/admin-pages.php:27 -msgid "Discount Codes" -msgstr "ディスカウントコード" - -#: ../includes/admin-pages.php:28 -msgid "Earnings and Sales Reports" -msgstr "収益および売り上げ報告" - -#: ../includes/admin-pages.php:28 -msgid "Reports" -msgstr "レポート" - -#: ../includes/admin-pages.php:29 -msgid "Easy Digital Download Settings" -msgstr "Easy Digital Download 設定" - -#: ../includes/admin-pages.php:29 -msgid "Settings" -msgstr "設定" - -#: ../includes/admin-pages.php:30 -msgid "Easy Digital Download Add Ons" -msgstr "アドオン" - -#: ../includes/admin-pages.php:30 -msgid "Add Ons" -msgstr "アドオン" - -#: ../includes/login-register.php:45 -msgid "Log into Your Account" -msgstr "ログインする" - -#: ../includes/login-register.php:47 -#: ../includes/login-register.php:48 -#: ../includes/checkout-template.php:333 -#: ../includes/checkout-template.php:334 -#: ../includes/checkout-template.php:381 -msgid "Username" -msgstr "ユーザー名" - -#: ../includes/login-register.php:51 -#: ../includes/checkout-template.php:337 -#: ../includes/checkout-template.php:338 -#: ../includes/checkout-template.php:385 -msgid "Password" -msgstr "パスワード" - -#: ../includes/login-register.php:58 -#: ../includes/checkout-template.php:345 -msgid "Login" -msgstr "ログイン" - -#: ../includes/login-register.php:61 -msgid "Lost Password" -msgstr "パスワード忘れ" - -#: ../includes/login-register.php:62 -msgid "Lost Password?" -msgstr "パスワードを忘れてしまいましたか?" - -#: ../includes/login-register.php:69 -msgid "You are already logged in" -msgstr "すでにログインしています" - -#: ../includes/login-register.php:92 -#: ../includes/process-purchase.php:450 -msgid "The password you entered is incorrect" -msgstr "入力されたパスワードは正しくありません" - -#: ../includes/login-register.php:95 -#: ../includes/process-purchase.php:469 -msgid "The username you entered does not exist" -msgstr "入力されたユーザー名は存在しません" - -#: ../includes/error-tracking.php:30 -msgid "Error" -msgstr "エラー" - -#: ../includes/misc-functions.php:164 -msgid "US Dollars ($)" -msgstr "米ドル ($)" - -#: ../includes/misc-functions.php:165 -msgid "Euros (€)" -msgstr "ユーロ (€)" - -#: ../includes/misc-functions.php:166 -msgid "Pounds Sterling (£)" -msgstr "ポンド (£)" - -#: ../includes/misc-functions.php:167 -msgid "Australian Dollars ($)" -msgstr "豪ドル ($)" - -#: ../includes/misc-functions.php:168 -msgid "Brazilian Real ($)" -msgstr "ブラジル レアル($)" - -#: ../includes/misc-functions.php:169 -msgid "Canadian Dollars ($)" -msgstr "カナダドル ($)" - -#: ../includes/misc-functions.php:170 -msgid "Czech Koruna" -msgstr "チェコ コルナ" - -#: ../includes/misc-functions.php:171 -msgid "Danish Krone" -msgstr "デンマーク クローネ" - -#: ../includes/misc-functions.php:172 -msgid "Hong Kong Dollar ($)" -msgstr "香港ドル ($)" - -#: ../includes/misc-functions.php:173 -msgid "Hungarian Forint" -msgstr "ハンガリー フォリント" - -#: ../includes/misc-functions.php:174 -msgid "Israeli Shekel" -msgstr "イスラエル シェケル" - -#: ../includes/misc-functions.php:175 -msgid "Japanese Yen (¥)" -msgstr "日本円" - -#: ../includes/misc-functions.php:176 -msgid "Malaysian Ringgits" -msgstr "マレーシア リンギ" - -#: ../includes/misc-functions.php:177 -msgid "Mexican Peso ($)" -msgstr "メキシコ ペソ($)" - -#: ../includes/misc-functions.php:178 -msgid "New Zealand Dollar ($)" -msgstr "ニュージーランドドル ($)" - -#: ../includes/misc-functions.php:179 -msgid "Norwegian Krone" -msgstr "ノルウェー クローネ" - -#: ../includes/misc-functions.php:180 -msgid "Philippine Pesos" -msgstr "フィリピン ペソ" - -#: ../includes/misc-functions.php:181 -msgid "Polish Zloty" -msgstr "ポーランド ズローティ" - -#: ../includes/misc-functions.php:182 -msgid "Singapore Dollar ($)" -msgstr "シンガポールドル ($)" - -#: ../includes/misc-functions.php:183 -msgid "Swedish Krona" -msgstr "スゥエーデン クローナ" - -#: ../includes/misc-functions.php:184 -msgid "Swiss Franc" -msgstr "スイス フラン" - -#: ../includes/misc-functions.php:185 -msgid "Taiwan New Dollars" -msgstr "台湾ドル ($)" - -#: ../includes/misc-functions.php:186 -msgid "Thai Baht" -msgstr "タイ バーツ" - -#: ../includes/misc-functions.php:187 -msgid "Indian Rupee" -msgstr "インド ルピー" - -#: ../includes/misc-functions.php:188 -msgid "Turkish Lira" -msgstr "トルコ リラ" - -#: ../includes/process-download.php:94 -msgid "You do not have permission to download this file" -msgstr "ファイルをダウンロードするための権限がありません" - -#: ../includes/process-download.php:94 -msgid "Purchase Verification Failed" -msgstr "購入確認に失敗しました" - -#: ../includes/checkout-template.php:75 -msgid "Choose Your Payment Method" -msgstr "支払い方法を選択して下さい" - -#: ../includes/checkout-template.php:86 -msgid "Next" -msgstr "次へ" - -#: ../includes/checkout-template.php:133 -msgid "Email address" -msgstr "メールアドレス" - -#: ../includes/checkout-template.php:134 -msgid "Email Address" -msgstr "メールアドレス" - -#: ../includes/checkout-template.php:137 -#: ../includes/checkout-template.php:138 -#: ../includes/checkout-template.php:351 -#: ../includes/checkout-template.php:352 -msgid "First Name" -msgstr "名" - -#: ../includes/checkout-template.php:141 -#: ../includes/checkout-template.php:355 -msgid "Last name" -msgstr "姓" - -#: ../includes/checkout-template.php:142 -#: ../includes/checkout-template.php:356 -msgid "Last Name" -msgstr "姓" - -#: ../includes/checkout-template.php:150 -msgid "Enter discount" -msgstr "ディスカウントを入力" - -#: ../includes/checkout-template.php:152 -msgid "Discount" -msgstr "ディスカウント" - -#: ../includes/checkout-template.php:154 -msgid "Apply Discount" -msgstr "ディスカウントを適用" - -#: ../includes/checkout-template.php:180 -msgid "Show Terms" -msgstr "規約を表示" - -#: ../includes/checkout-template.php:181 -msgid "Hide Terms" -msgstr "規約を非表示" - -#: ../includes/checkout-template.php:184 -msgid "Agree to Terms?" -msgstr "利用規約に同意しますか?" - -#: ../includes/checkout-template.php:208 -msgid "Go back" -msgstr "戻る" - -#: ../includes/checkout-template.php:212 -msgid "You must be logged in to complete your purchase" -msgstr "購入するにはログインする必要があります" - -#: ../includes/checkout-template.php:241 -msgid "Card name" -msgstr "カード名義" - -#: ../includes/checkout-template.php:242 -msgid "Name on the Card" -msgstr "カード名義" - -#: ../includes/checkout-template.php:245 -msgid "Card number" -msgstr "カード番号" - -#: ../includes/checkout-template.php:246 -msgid "Card Number" -msgstr "カード番号" - -#: ../includes/checkout-template.php:249 -msgid "Security code" -msgstr "セキュリティコード" - -#: ../includes/checkout-template.php:250 -msgid "CVC" -msgstr "CVC" - -#: ../includes/checkout-template.php:256 -msgid "Year" -msgstr "年" - -#: ../includes/checkout-template.php:257 -msgid "Expiration (MM/YYYY)" -msgstr "有効期限(MM/YYYY)" - -#: ../includes/checkout-template.php:285 -msgid "Credit Card Info" -msgstr "クレジットカード情報" - -#: ../includes/checkout-template.php:287 -msgid "Address line 1" -msgstr "住所1" - -#: ../includes/checkout-template.php:288 -msgid "Billing Address" -msgstr "住所1" - -#: ../includes/checkout-template.php:291 -msgid "Address line 2" -msgstr "住所2" - -#: ../includes/checkout-template.php:292 -msgid "Billing Address Line 2" -msgstr "住所2" - -#: ../includes/checkout-template.php:295 -msgid "City" -msgstr "市区町村" - -#: ../includes/checkout-template.php:296 -msgid "Billing City" -msgstr "市区町村" - -#: ../includes/checkout-template.php:299 -msgid "State / Province" -msgstr "都道府県" - -#: ../includes/checkout-template.php:300 -msgid "Billing State / Province" -msgstr "都道府県" - -#: ../includes/checkout-template.php:303 -msgid "Zip / Postal code" -msgstr "郵便番号" - -#: ../includes/checkout-template.php:304 -msgid "Billing Zip / Postal Code" -msgstr "郵便番号" - -#: ../includes/checkout-template.php:331 -msgid "Create an account" -msgstr "アカウントを作成" - -#: ../includes/checkout-template.php:331 -msgid "(optional)" -msgstr "(任意)" - -#: ../includes/checkout-template.php:341 -msgid "Confirm password" -msgstr "パスワード確認" - -#: ../includes/checkout-template.php:342 -msgid "Password Again" -msgstr "パスワード再入力" - -#: ../includes/checkout-template.php:345 -msgid "Already have an account?" -msgstr "すでにアカウントをお持ちですか?" - -#: ../includes/checkout-template.php:347 -#: ../includes/checkout-template.php:348 -msgid "Email" -msgstr "メールアドレス" - -#: ../includes/checkout-template.php:378 -msgid "Login to your account" -msgstr "ログイン" - -#: ../includes/checkout-template.php:380 -msgid "Your username" -msgstr "ユーザーID" - -#: ../includes/checkout-template.php:384 -msgid "Your password" -msgstr "パスワード" - -#: ../includes/checkout-template.php:391 -msgid "Need to create an account?" -msgstr "アカウントを作成しますか?" - -#: ../includes/checkout-template.php:393 -msgid "Register" -msgstr "登録" - -#: ../includes/checkout-template.php:393 -msgid "or checkout as a guest." -msgstr "ゲストとして会計" - -#: ../includes/email-functions.php:47 -msgid "Hello" -msgstr "こんにちは" - -#: ../includes/email-functions.php:47 -msgid "A download purchase has been made" -msgstr "購入処理が行われました" - -#: ../includes/email-functions.php:48 -msgid "Downloads sold:" -msgstr "商品:" - -#: ../includes/email-functions.php:57 -msgid "Amount: " -msgstr "合計:" - -#: ../includes/email-functions.php:58 -msgid "Thank you" -msgstr "ありがとうございました" - -#: ../includes/email-functions.php:60 -msgid "New download purchase" -msgstr "新しいお買いもの" - -#: ../includes/install.php:41 -msgid "Purchase Confirmation" -msgstr "購入確認" - -#: ../includes/install.php:42 -msgid "Thank you for your purchase!" -msgstr "ご購入ありがとうございました!" - -#: ../includes/install.php:51 -msgid "Purchase History" -msgstr "購入履歴" - -#: ../includes/process-purchase.php:190 -msgid "The selected gateway is not active" -msgstr "選択された決済代行機関は無効です" - -#: ../includes/process-purchase.php:194 -msgid "No gateway has been selected" -msgstr "決済代行機関が選択されていません" - -#: ../includes/process-purchase.php:242 -msgid "You must agree to the terms of use" -msgstr "利用規約に同意していただく必要があります" - -#: ../includes/process-purchase.php:281 -msgid "The user information is invalid." -msgstr "ユーザー情報が正しくありません。" - -#: ../includes/process-purchase.php:327 -msgid "Username already taken" -msgstr "そのユーザーIDは既に使われています" - -#: ../includes/process-purchase.php:333 -msgid "Invalid username" -msgstr "ユーザー名が正しくありません" - -#: ../includes/process-purchase.php:344 -msgid "You must register or login to complete your purchase" -msgstr "購入を完了するには登録またはログインして下さい" - -#: ../includes/process-purchase.php:356 -#: ../includes/process-purchase.php:500 -msgid "Invalid email" -msgstr "メールアドレスが正しくありません" - -#: ../includes/process-purchase.php:362 -msgid "Email already used" -msgstr "そのメールアドレスは既に使われています" - -#: ../includes/process-purchase.php:374 -#: ../includes/process-purchase.php:507 -msgid "Enter an email" -msgstr "メールアドレスを入力して下さい" - -#: ../includes/process-purchase.php:385 -msgid "Passwords don't match" -msgstr "パスワードが合致しません" - -#: ../includes/process-purchase.php:400 -#: ../includes/process-purchase.php:465 -msgid "Enter a password" -msgstr "パスワードを入力して下さい" - -#: ../includes/process-purchase.php:405 -msgid "Enter the password confirmation" -msgstr "パスワード確認を入力して下さい" - -#: ../includes/process-purchase.php:432 -msgid "You must login or register to complete your purchase" -msgstr "購入を完了するにはログインまたは登録する必要があります" - -#: ../includes/gateway-functions.php:28 -msgid "Manual Payment" -msgstr "手動決済" - -#: ../includes/admin-notices.php:24 -msgid "Discount code updated." -msgstr "ディスカウントコードが更新されました。" - -#: ../includes/admin-notices.php:27 -msgid "There was a problem updating your discount code, please try again." -msgstr "ディスカウントコード更新中に問題が発生しました。もう一度試してみて下さい。" - -#: ../includes/admin-notices.php:30 -msgid "The payment has been deleted." -msgstr "支払いは削除されました。" - -#: ../includes/admin-notices.php:33 -msgid "The purchase receipt has been resent." -msgstr "領収書が再送されました。" - -#: ../includes/admin-notices.php:49 -msgid "There seems to be an issue with the server. Please try again in a few minutes." -msgstr "サーバー側で何か発生した模様です。しばらく立ってから再試行して下さい。" - -#: ../includes/post-types.php:46 -#: ../includes/post-types.php:58 -msgid "Downloads" -msgstr "ダウンロード商品" - -#: ../includes/post-types.php:50 -msgid "Edit Download" -msgstr "ダウンロード商品を編集" - -#: ../includes/post-types.php:51 -msgid "New Download" -msgstr "新しいダウンロード商品" - -#: ../includes/post-types.php:52 -msgid "All Downloads" -msgstr "すべてのダウンロード商品" - -#: ../includes/post-types.php:53 -msgid "View Download" -msgstr "ダウンロード商品を見る" - -#: ../includes/post-types.php:54 -msgid "Search Downloads" -msgstr "ダウンロード商品を検索する" - -#: ../includes/post-types.php:55 -msgid "No Downloads found" -msgstr "ダウンロード商品がありません" - -#: ../includes/post-types.php:56 -msgid "No Downloads found in Trash" -msgstr "ゴミ箱にダウンロード商品はありません" - -#: ../includes/post-types.php:78 -msgid "Payments" -msgstr "支払い" - -#: ../includes/post-types.php:79 -msgid "Payment" -msgstr "支払い" - -#: ../includes/post-types.php:81 -msgid "Add New Payment" -msgstr "支払いを追加" - -#: ../includes/post-types.php:82 -msgid "Edit Payment" -msgstr "支払いを編集" - -#: ../includes/post-types.php:83 -msgid "New Payment" -msgstr "新しい支払い" - -#: ../includes/post-types.php:84 -msgid "All Payments" -msgstr "すべての支払い" - -#: ../includes/post-types.php:85 -msgid "View Payment" -msgstr "支払いを見る" - -#: ../includes/post-types.php:86 -msgid "Search Payments" -msgstr "支払いを検索" - -#: ../includes/post-types.php:87 -msgid "No Payments found" -msgstr "支払いはありません" - -#: ../includes/post-types.php:88 -msgid "No Payments found in Trash" -msgstr "ゴミ箱に支払いはありません" - -#: ../includes/post-types.php:127 -msgid "Category" -msgstr "カテゴリ" - -#: ../includes/post-types.php:128 -msgid "Search Categories" -msgstr "カテゴリを検索" - -#: ../includes/post-types.php:129 -msgid "All Categories" -msgstr "すべてのカテゴリ" - -#: ../includes/post-types.php:130 -msgid "Parent Category" -msgstr "親カテゴリ" - -#: ../includes/post-types.php:131 -msgid "Parent Category:" -msgstr "親カテゴリ:" - -#: ../includes/post-types.php:132 -msgid "Edit Category" -msgstr "カテゴリを編集" - -#: ../includes/post-types.php:133 -msgid "Update Category" -msgstr "カテゴリを更新" - -#: ../includes/post-types.php:134 -msgid "Add New Category" -msgstr "カテゴリを追加" - -#: ../includes/post-types.php:135 -msgid "New Category Name" -msgstr "新しいカテゴリ名" - -#: ../includes/post-types.php:149 -msgid "Tag" -msgstr "タグ" - -#: ../includes/post-types.php:150 -msgid "Search Tags" -msgstr "タグを検索" - -#: ../includes/post-types.php:151 -msgid "All Tags" -msgstr "すべてのタグ" - -#: ../includes/post-types.php:152 -msgid "Parent Tag" -msgstr "親タグ" - -#: ../includes/post-types.php:153 -msgid "Parent Tag:" -msgstr "親タグ:" - -#: ../includes/post-types.php:154 -msgid "Edit Tag" -msgstr "タグを編集" - -#: ../includes/post-types.php:155 -msgid "Update Tag" -msgstr "タグを更新" - -#: ../includes/post-types.php:156 -msgid "Add New Tag" -msgstr "タグを新規追加" - -#: ../includes/post-types.php:157 -msgid "New Tag Name" -msgstr "新しいタグ名" - -#: ../includes/post-types.php:186 -#: ../includes/post-types.php:187 -msgid "Download updated." -msgstr "ダウンロード商品が更新されました。" - -#: ../includes/post-types.php:188 -msgid "Download published." -msgstr "ダウンロード商品が公開されました。" - -#: ../includes/post-types.php:189 -msgid "Download saved." -msgstr "ダウンロード商品が保存されました。" - -#: ../includes/post-types.php:190 -msgid "Download submitted." -msgstr "ダウンロード商品が送信されました。" - -#: ../includes/shortcodes.php:63 -msgid "Download Name" -msgstr "ダウンロード商品名" - -#: ../includes/shortcodes.php:65 -#: ../includes/shortcodes.php:133 -msgid "Files" -msgstr "ファイル" - -#: ../includes/shortcodes.php:89 -#: ../includes/shortcodes.php:160 -msgid "No downloadable files found." -msgstr "ダウンロード可能なファイルが見つかりません。" - -#: ../includes/shortcodes.php:100 -msgid "You have not purchased any downloads" -msgstr "何も購入していません" - -#: ../includes/shortcodes.php:130 -msgid "Purchase ID" -msgstr "購入ID" - -#: ../includes/shortcodes.php:132 -#: ../includes/admin-pages/discount-codes.php:42 -#: ../includes/admin-pages/discount-codes.php:56 -msgid "Amount" -msgstr "合計金額" - -#: ../includes/shortcodes.php:173 -msgid "You have not made any purchases" -msgstr "何も購入なさっていません" - -#: ../includes/shortcodes.php:275 -msgid "Add to Cart" -msgstr "カートに入れる" - -#: ../includes/shortcodes.php:302 -msgid "No downloads found" -msgstr "ダウンロード商品はありません" - -#: ../includes/gateways/paypal.php:264 -msgid "Invalid IPN" -msgstr "IPNが正しくありません" - -#: ../includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "商品名" - -#: ../includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "単価" - -#: ../includes/templates/checkout_cart.php:8 -#: ../includes/admin-pages/discount-codes.php:48 -#: ../includes/admin-pages/discount-codes.php:62 -msgid "Actions" -msgstr "操作" - -#: ../includes/templates/checkout_cart.php:47 -msgid "Total" -msgstr "合計" - -#: ../includes/admin-pages/discount-codes.php:40 -#: ../includes/admin-pages/discount-codes.php:54 -msgid "Code" -msgstr "コード" - -#: ../includes/admin-pages/discount-codes.php:41 -#: ../includes/admin-pages/discount-codes.php:55 -msgid "Type" -msgstr "タイプ" - -#: ../includes/admin-pages/discount-codes.php:43 -#: ../includes/admin-pages/discount-codes.php:57 -msgid "Uses" -msgstr "利用" - -#: ../includes/admin-pages/discount-codes.php:44 -#: ../includes/admin-pages/discount-codes.php:58 -msgid "Max Uses" -msgstr "最大利用" - -#: ../includes/admin-pages/discount-codes.php:45 -#: ../includes/admin-pages/discount-codes.php:59 -msgid "Start Date" -msgstr "開始日" - -#: ../includes/admin-pages/discount-codes.php:46 -#: ../includes/admin-pages/discount-codes.php:60 -msgid "Expiration" -msgstr "有効期限" - -#: ../includes/admin-pages/discount-codes.php:47 -#: ../includes/admin-pages/discount-codes.php:61 -#: ../includes/admin-pages/payments-history.php:83 -#: ../includes/admin-pages/payments-history.php:96 -msgid "Status" -msgstr "状態" - -#: ../includes/admin-pages/discount-codes.php:85 -#: ../includes/admin-pages/discount-codes.php:87 -msgid "unlimited" -msgstr "無制限" - -#: ../includes/admin-pages/discount-codes.php:96 -msgid "No start date" -msgstr "開始日がありません" - -#: ../includes/admin-pages/discount-codes.php:103 -msgid "Expired" -msgstr "期限切れ" - -#: ../includes/admin-pages/discount-codes.php:105 -msgid "no expiration" -msgstr "無期限" - -#: ../includes/admin-pages/discount-codes.php:111 -#: ../includes/admin-pages/payments-history.php:120 -msgid "Edit" -msgstr "編集" - -#: ../includes/admin-pages/discount-codes.php:113 -msgid "Deactivate" -msgstr "無効化" - -#: ../includes/admin-pages/discount-codes.php:115 -msgid "Activate" -msgstr "有効化" - -#: ../includes/admin-pages/discount-codes.php:117 -#: ../includes/admin-pages/payments-history.php:122 -msgid "Delete" -msgstr "削除" - -#: ../includes/admin-pages/discount-codes.php:122 -msgid "No discount codes have been created." -msgstr "ディスカウントコードが作成されていません。" - -#: ../includes/admin-pages/reports.php:34 -msgid "Transactions created while in test mode are not included on this page." -msgstr "テストモードでのトランザクションはここには記録されません。" - -#: ../includes/admin-pages/payments-history.php:57 -msgid "Payment mode" -msgstr "支払いモード" - -#: ../includes/admin-pages/payments-history.php:59 -msgid "Live" -msgstr "生" - -#: ../includes/admin-pages/payments-history.php:60 -msgid "Test" -msgstr "テスト" - -#: ../includes/admin-pages/payments-history.php:64 -msgid "Payments per page" -msgstr "ページ当たりの支払い" - -#: ../includes/admin-pages/payments-history.php:66 -msgid "Show" -msgstr "見せる" - -#: ../includes/admin-pages/payments-history.php:72 -#: ../includes/admin-pages/payments-history.php:89 -msgid "ID" -msgstr "ID" - -#: ../includes/admin-pages/payments-history.php:75 -#: ../includes/admin-pages/payments-history.php:91 -msgid "Key" -msgstr "キー" - -#: ../includes/admin-pages/payments-history.php:76 -#: ../includes/admin-pages/payments-history.php:92 -msgid "Products" -msgstr "製品" - -#: ../includes/admin-pages/payments-history.php:77 -#: ../includes/admin-pages/payments-history.php:93 -msgid "Price" -msgstr "価格" - -#: ../includes/admin-pages/payments-history.php:81 -#: ../includes/admin-pages/payments-history.php:95 -msgid "User" -msgstr "ユーザー" - -#: ../includes/admin-pages/payments-history.php:121 -msgid "Resend Purchase Receipt" -msgstr "領収書を再送" - -#: ../includes/admin-pages/payments-history.php:135 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "支払い番号%sに対する購入の詳細" - -#: ../includes/admin-pages/payments-history.php:135 -msgid "View Order Details" -msgstr "注文詳細" - -#: ../includes/admin-pages/payments-history.php:140 -msgid "Purchased File" -msgstr "購入されたファイル" - -#: ../includes/admin-pages/payments-history.php:140 -msgid "Purchased Files" -msgstr "購入されたファイル" - -#: ../includes/admin-pages/payments-history.php:155 -msgid "Price: " -msgstr "価格:" - -#: ../includes/admin-pages/payments-history.php:161 -msgid "Discount used:" -msgstr "ディスカウント利用:" - -#: ../includes/admin-pages/payments-history.php:161 -msgid "none" -msgstr "なし" - -#: ../includes/admin-pages/payments-history.php:162 -msgid "Total:" -msgstr "合計:" - -#: ../includes/admin-pages/payments-history.php:165 -msgid "Buyer's Personal Details:" -msgstr "購入者の個人的詳細:" - -#: ../includes/admin-pages/payments-history.php:167 -msgid "Name:" -msgstr "氏名:" - -#: ../includes/admin-pages/payments-history.php:168 -msgid "Email:" -msgstr "メールアドレス:" - -#: ../includes/admin-pages/payments-history.php:177 -msgid "guest" -msgstr "ゲスト" - -#: ../includes/admin-pages/payments-history.php:184 -msgid "No payments recorded yet" -msgstr "支払い記録はありません" - -#: ../includes/admin-pages/payments-history.php:199 -msgid "Previous" -msgstr "プレビュー" - -#: ../includes/admin-pages/settings.php:33 -msgid "General" -msgstr "一般" - -#: ../includes/admin-pages/settings.php:35 -msgid "Emails" -msgstr "メール" - -#: ../includes/admin-pages/settings.php:36 -msgid "Styles" -msgstr "外観" - -#: ../includes/admin-pages/settings.php:37 -msgid "Misc" -msgstr "その他" - -#: ../includes/admin-pages/add-ons.php:69 -msgid "Add Ons for Easy Digital Downloads" -msgstr "Easy Digital Downloads のためのアドオン" - -#: ../includes/admin-pages/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "これらのアドオンは、Easy Digital Downloadsの機能を拡張します。" - -#: ../includes/admin-pages/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "すべての機能拡張を見る" - -#: ../includes/admin-pages/forms/edit-discount.php:13 -msgid "Something went wrong." -msgstr "何か問題があったようです。" - -#: ../includes/admin-pages/forms/edit-discount.php:17 -msgid "Edit Discount" -msgstr "ディスカウントを編集" - -#: ../includes/admin-pages/forms/edit-discount.php:17 -#: ../includes/admin-pages/forms/edit-payment.php:16 -msgid "Go Back" -msgstr "戻る" - -#: ../includes/admin-pages/forms/edit-discount.php:27 -#: ../includes/admin-pages/forms/add-discount.php:22 -msgid "The name of this discount" -msgstr "ディスカウント名" - -#: ../includes/admin-pages/forms/edit-discount.php:36 -#: ../includes/admin-pages/forms/add-discount.php:31 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "ディスカウントコードを入力してください(例:10-PERCENT)" - -#: ../includes/admin-pages/forms/edit-discount.php:45 -#: ../includes/admin-pages/forms/add-discount.php:40 -msgid "Percentage" -msgstr "パーセンテージ" - -#: ../includes/admin-pages/forms/edit-discount.php:46 -#: ../includes/admin-pages/forms/add-discount.php:41 -msgid "Flat amount" -msgstr "単純合計" - -#: ../includes/admin-pages/forms/edit-discount.php:48 -#: ../includes/admin-pages/forms/add-discount.php:43 -msgid "The kind of discount to apply for this discount." -msgstr "適用されるディスカウントの種類" - -#: ../includes/admin-pages/forms/edit-discount.php:57 -#: ../includes/admin-pages/forms/add-discount.php:52 -msgid "The amount of this discount code." -msgstr "ディスカウント合計" - -#: ../includes/admin-pages/forms/edit-discount.php:62 -#: ../includes/admin-pages/forms/add-discount.php:57 -msgid "Start date" -msgstr "開始日" - -#: ../includes/admin-pages/forms/edit-discount.php:66 -#: ../includes/admin-pages/forms/add-discount.php:61 -msgid "Enter the start date for this discount code in the format of yyyy-mm-dd. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "ディスカウントコードの開始日を yyyy-mm-dd 形式で入力して下さい。開始日がなければ、空のままにして下さい。入力されていた場合、ディスカウントはこの日付以降にならないと利用できません。" - -#: ../includes/admin-pages/forms/edit-discount.php:71 -#: ../includes/admin-pages/forms/add-discount.php:66 -msgid "Expiration date" -msgstr "有効期限" - -#: ../includes/admin-pages/forms/edit-discount.php:75 -#: ../includes/admin-pages/forms/add-discount.php:70 -msgid "Enter the expiration date for this discount code in the format of yyyy-mm-dd. For no expiration, leave blank" -msgstr "ディスカウントコードの有効期限を yyyy-mm-dd 形式で入力して下さい。期限がないなら、空のままにして下さい。" - -#: ../includes/admin-pages/forms/edit-discount.php:84 -#: ../includes/admin-pages/forms/add-discount.php:79 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "ディスカウントの最大適用回数を入力して下さい。空にすると無制限の意味となります。" - -#: ../includes/admin-pages/forms/edit-discount.php:93 -msgid "Active" -msgstr "有効" - -#: ../includes/admin-pages/forms/edit-discount.php:94 -msgid "Inactive" -msgstr "無効" - -#: ../includes/admin-pages/forms/edit-discount.php:96 -msgid "The status of this discount code." -msgstr "ディスカウントコードの状態" - -#: ../includes/admin-pages/forms/edit-discount.php:106 -msgid "Update Discount Code" -msgstr "ディスカウントコードを更新" - -#: ../includes/admin-pages/forms/add-discount.php:12 -msgid "Add New Discount" -msgstr "ディスカウントを追加" - -#: ../includes/admin-pages/forms/add-discount.php:87 -msgid "Add Discount Code" -msgstr "ディスカウントコードを追加" - -#: ../includes/admin-pages/forms/edit-payment.php:22 -msgid "Buyer's Email" -msgstr "購入者メールアドレス" - -#: ../includes/admin-pages/forms/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "必要に応じて、購入者のメールアドレスをここで更新できます。" - -#: ../includes/admin-pages/forms/edit-payment.php:31 -msgid "Downloads Purchased" -msgstr "購入されたダウンロード商品" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "購入番号 #%s にダウンロード商品を追加" - -#: ../includes/admin-pages/forms/edit-payment.php:43 -msgid "Add download to purchase" -msgstr "ダウンロード商品を追加" - -#: ../includes/admin-pages/forms/edit-payment.php:48 -msgid "Payment Status" -msgstr "支払い状況" - -#: ../includes/admin-pages/forms/edit-payment.php:70 -msgid "Update Payment" -msgstr "支払いを更新" - -#: ../includes/admin-pages/forms/edit-payment.php:83 -msgid "Add Selected Downloads" -msgstr "選択されたダウンロード商品を追加" - diff --git a/languages/edd-nl_NL.mo b/languages/edd-nl_NL.mo deleted file mode 100644 index 823a2060b6a..00000000000 Binary files a/languages/edd-nl_NL.mo and /dev/null differ diff --git a/languages/edd-nl_NL.po b/languages/edd-nl_NL.po deleted file mode 100644 index 95ec7a1f89a..00000000000 --- a/languages/edd-nl_NL.po +++ /dev/null @@ -1,1703 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-04-30 21:43-0600\n" -"PO-Revision-Date: 2012-08-24 08:55+0830\n" -"Last-Translator: Piet Bos \n" -"Language-Team: Pippin's Plugins\n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_n;_x\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-Language: English\n" -"X-Poedit-Country: UNITED STATES\n" -"X-Poedit-SearchPath-0: ..\n" -"X-Poedit-SearchPath-1: ../includes\n" -"X-Poedit-SearchPath-2: ../includes/admin-pages\n" -"X-Poedit-SearchPath-3: ../includes/admin-pages/forms\n" -"X-Poedit-SearchPath-4: ../includes/gateways\n" -"X-Poedit-SearchPath-5: .\n" - -#: ../includes/template-functions.php:8 -#: ../includes/dashboard-columns.php:41 -msgid "Purchase" -msgstr "Bestellen" - -#: ../includes/template-functions.php:60 -#: ../includes/template-functions.php:71 -#: ../includes/cart-template.php:23 -#: ../includes/cart-template.php:26 -msgid "Checkout" -msgstr "Afrekenen" - -#: ../includes/template-functions.php:78 -msgid "added to your cart" -msgstr "aan uw winkelmandje toegevoegd" - -#: ../includes/template-functions.php:112 -msgid "Gray" -msgstr "Grijs" - -#: ../includes/template-functions.php:113 -msgid "Pink" -msgstr "Roze" - -#: ../includes/template-functions.php:114 -msgid "blue" -msgstr "Blauw" - -#: ../includes/template-functions.php:115 -msgid "Green" -msgstr "Groen" - -#: ../includes/template-functions.php:116 -msgid "Teal" -msgstr "Zeegroen" - -#: ../includes/template-functions.php:117 -msgid "Black" -msgstr "Zwart" - -#: ../includes/template-functions.php:118 -msgid "Dark Gray" -msgstr "Donkergrijs" - -#: ../includes/template-functions.php:119 -msgid "Orange" -msgstr "Oranje" - -#: ../includes/template-functions.php:120 -msgid "Purple" -msgstr "Paars" - -#: ../includes/template-functions.php:121 -msgid "Slate" -msgstr "Leisteen" - -#: ../includes/template-functions.php:127 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "Je hebt dit item al eens besteld, maar doe dat gerust nog eens.." - -#: ../includes/payment-functions.php:141 -msgid "pending" -msgstr "in afwachting van" - -#: ../includes/payment-functions.php:144 -msgid "complete" -msgstr "voltooid" - -#: ../includes/cart-template.php:44 -msgid "remove" -msgstr "verwijder" - -#: ../includes/cart-template.php:52 -msgid "Your cart is empty." -msgstr "Je mandje is leeg." - -#: ../includes/thickbox.php:9 -#: ../includes/thickbox.php:10 -#: ../includes/thickbox.php:97 -msgid "Insert Download" -msgstr "Voeg een download toe" - -#: ../includes/thickbox.php:39 -msgid "You must choose a download" -msgstr "Je moet een download kiezen" - -#: ../includes/thickbox.php:62 -msgid "Use the form below to insert the short code for purchasing a download." -msgstr "Gebruik het formulier hieronder om de shortcode toe te voegen" - -#: ../includes/thickbox.php:65 -msgid "Choose a download" -msgstr "Kies een downloadbestand" - -#: ../includes/thickbox.php:74 -msgid "Choose a style" -msgstr "Kies een stijl" - -#: ../includes/thickbox.php:85 -msgid "Choose a button color" -msgstr "Kies een button-kleur" - -#: ../includes/thickbox.php:94 -msgid "Link text . . ." -msgstr "Link tekst..." - -#: ../includes/thickbox.php:98 -msgid "Cancel" -msgstr "Annuleer" - -#: ../includes/thickbox.php:100 -msgid "Button Styles" -msgstr "Button Stijlen" - -#: ../includes/register-settings.php:23 -msgid "Test Mode" -msgstr "Test modus" - -#: ../includes/register-settings.php:24 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "In testmodus worden geen live transacties doorgevoerd. Om de testmodus te gebruiken heb je wel een sandbox (test) account nodig voor de betaalwijze (payment gateway) die je kiest." - -#: ../includes/register-settings.php:29 -msgid "Purchase Page" -msgstr "Bestelpagina" - -#: ../includes/register-settings.php:30 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "Dit is de checkout pagina, waar bezoekers hun bestelling voltooien." - -#: ../includes/register-settings.php:36 -msgid "Success Page" -msgstr "Transactie geslaagd pagina" - -#: ../includes/register-settings.php:37 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "Hier worden bezoekers naar doorverwezen na het voltooien van de bestelling" - -#: ../includes/register-settings.php:43 -msgid "Currency Settings" -msgstr "Munteenheid" - -#: ../includes/register-settings.php:44 -msgid "Configure the currency options" -msgstr "Stel de munteenheid in" - -#: ../includes/register-settings.php:49 -msgid "Currency" -msgstr "Munteenheid" - -#: ../includes/register-settings.php:50 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "Kies welke munteenheid gebruikt moet worden; sommige gateways hebben hier wel beperkingen op staan." - -#: ../includes/register-settings.php:56 -msgid "Currency Position" -msgstr "Positie munteenheid" - -#: ../includes/register-settings.php:57 -msgid "Choose the location of the currency sign." -msgstr "Kies de plek waar het betreffende munt-teken moet staan." - -#: ../includes/register-settings.php:60 -msgid "Before - $10" -msgstr "Voor - € 10" - -#: ../includes/register-settings.php:61 -msgid "After - 10$" -msgstr "Achter - 10 €" - -#: ../includes/register-settings.php:66 -msgid "Thousands Separator" -msgstr "Scheidingsteken duizendtallen" - -#: ../includes/register-settings.php:67 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "Het scheidingsteken voor duizendtallen (b.v. 10.000)" - -#: ../includes/register-settings.php:74 -msgid "Decimal Separator" -msgstr "Decimale scheidsteken" - -#: ../includes/register-settings.php:75 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "Scheidingsteken voor decimalen (meestal . of ,)" - -#: ../includes/register-settings.php:86 -msgid "Payment Gateways" -msgstr "Payment Gateways" - -#: ../includes/register-settings.php:87 -msgid "Choose the payment gateways you want to enable." -msgstr "Kies de payment gateway(s) die je wilt gebruiken." - -#: ../includes/register-settings.php:93 -msgid "Accepted Payment Method Icons" -msgstr "Betaalmethode-icons" - -#: ../includes/register-settings.php:94 -msgid "Display icons for the selected payment methods" -msgstr "Toon de icons voor de betaalmethode(s)" - -#: ../includes/register-settings.php:94 -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "Je moet je betaalmethode ook configureren wanneer je creditcards accepteerd" - -#: ../includes/register-settings.php:106 -msgid "PayPal Settings" -msgstr "PayPal instellingen" - -#: ../includes/register-settings.php:107 -msgid "Configure the PayPal settings" -msgstr "Stel de PayPal instellingen in" - -#: ../includes/register-settings.php:112 -msgid "PayPal Email" -msgstr "PayPal email" - -#: ../includes/register-settings.php:113 -msgid "Enter your PayPal account's email" -msgstr "Voer hier het emailadres bij je PayPal account in" - -#: ../includes/register-settings.php:119 -msgid "Disable cURL for PayPal IPN" -msgstr "Schakel cURL uit voor PayPal IPN" - -#: ../includes/register-settings.php:120 -msgid "If payments are not getting marked as complete, check this option" -msgstr "Vink deze optie aan wanneer betalingen niet als voltooid worden aangemerkt" - -#: ../includes/register-settings.php:125 -msgid "Alternate PayPal Purchase Verification" -msgstr "Alternatieve PayPal bestelbevestiging" - -#: ../includes/register-settings.php:126 -msgid "If payments are not getting marked as complete, and disabling cURL does not fix the problem, then check this box" -msgstr "Vink dit aan wanneer betaling niet al voldaan worden aangemerkt en het uitschakelen van cURL niet helpt." - -#: ../includes/register-settings.php:135 -msgid "From Name" -msgstr "Afzender" - -#: ../includes/register-settings.php:136 -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "De naam van de afzender; meestal vul je hier je website naam in" - -#: ../includes/register-settings.php:141 -msgid "From Email" -msgstr "Mailadres afzender" - -#: ../includes/register-settings.php:142 -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "Dit adres fungeert ook als \"reply to\" adres" - -#: ../includes/register-settings.php:147 -msgid "Purchase Email Subject" -msgstr "Onderwerp van de bevestigingsmail" - -#: ../includes/register-settings.php:148 -msgid "Enter the subject line for the purchase receipt email" -msgstr "Voer hier het onderwerp van de bevestingsmail in." - -#: ../includes/register-settings.php:153 -msgid "Purchase Receipt" -msgstr "Ontvangstbewijs" - -#: ../includes/register-settings.php:154 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "Voer de tekst in die naar bestelllers wordt verzonden na een geslaagde bestelling; je kunt HTML en onderstaande template (merge) tags gebruiken:" - -#: ../includes/register-settings.php:155 -msgid "A list of download URLs for each download purchased" -msgstr "Een lijst met alle URLs van de gekozen downloads" - -#: ../includes/register-settings.php:156 -msgid "The buyer's name" -msgstr "Naam koper" - -#: ../includes/register-settings.php:157 -msgid "The date of the purchase" -msgstr "Besteldatum" - -#: ../includes/register-settings.php:158 -msgid "The total price of the purchase" -msgstr "Totaalprijs" - -#: ../includes/register-settings.php:159 -msgid "Your site name" -msgstr "De naam van je website" - -#: ../includes/register-settings.php:168 -msgid "Disable Styles" -msgstr "Stijlen uitschakelen" - -#: ../includes/register-settings.php:169 -msgid "Check this to disable all included styling" -msgstr "Vink dit aan om alle stijlen te verwijderen" - -#: ../includes/register-settings.php:174 -msgid "Checkout Button Color" -msgstr "Kleur van de checkout button" - -#: ../includes/register-settings.php:175 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "Kies de kleur van de button die je wilt gebruiken voor de checkout buttons" - -#: ../includes/register-settings.php:185 -msgid "Enable Ajax" -msgstr "Schakel Ajax in" - -#: ../includes/register-settings.php:186 -msgid "Check this to enable AJAX for the shopping cart." -msgstr "Vink dit aan om Ajax te activeren voor het winkelmandje" - -#: ../includes/register-settings.php:191 -msgid "Enable jQuery Validation" -msgstr "Schakel jQuery validatie in" - -#: ../includes/register-settings.php:192 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "Vink aan om jQuery validatie in te schakelen op het checkout formulier." - -#: ../includes/register-settings.php:197 -msgid "Disable Guest Checkout" -msgstr "Deactiveer Gust checkout" - -#: ../includes/register-settings.php:198 -msgid "Require that users be logged-in to purchase files." -msgstr "Alleen ingelogde gebruikers kunnen bestanden bestellen." - -#: ../includes/register-settings.php:203 -msgid "Show Register / Login Form?" -msgstr "Registratie/Login formulier tonen?" - -#: ../includes/register-settings.php:204 -msgid "Display the registration and login forms on the checkout page for non-logged-in users" -msgstr "Toon registratie- en login formulier voor niet ingelogde gebruikers op de checkout pagina." - -#: ../includes/register-settings.php:209 -msgid "Disable Redownload?" -msgstr "Opnieuw downloaden niet toestaan?" - -#: ../includes/register-settings.php:210 -msgid "Check this if you do not want to allow users to redownload items from their purchase history" -msgstr "Aanvinken als je niet wilt dat gebruikers de door hen bestelde items opnieuw kunnen downloaden." - -#: ../includes/register-settings.php:215 -msgid "Terms of Agreement" -msgstr "Algemene voorwaarden" - -#: ../includes/register-settings.php:221 -msgid "Agree to Terms" -msgstr "Akkoord gaan met de voorwaarden" - -#: ../includes/register-settings.php:222 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing" -msgstr "Aanvinken als je een \"Ga akkoord met voorwaarden\" wilt tonen, waarmee akkoord moet worden gegaan voor de aankoop." - -#: ../includes/register-settings.php:227 -msgid "Agree to Terms Label" -msgstr "Label voor \"Ga akkoord met voorwaarden\"" - -#: ../includes/register-settings.php:228 -msgid "Label shown next to the agree to terms check box" -msgstr "Het label dat naast de checkbox voor de algemene voorwaarden komt te staan." - -#: ../includes/register-settings.php:234 -msgid "Agreement Text" -msgstr "De tekst van de voorwaarden" - -#: ../includes/register-settings.php:235 -msgid "If Agree to Terms is checked, enter the agreement terms here" -msgstr "Als \"Ga akkoord met voorwaarden\" is aangevinkt, dien je hier de tekst in te voeren." - -#: ../includes/register-settings.php:261 -msgid "General Settings" -msgstr "Algemene instellingen" - -#: ../includes/register-settings.php:287 -msgid "Payment Gateway Settings" -msgstr "Instellingen betaalmethode" - -#: ../includes/register-settings.php:313 -msgid "Email Settings" -msgstr "Email instellingen" - -#: ../includes/register-settings.php:339 -msgid "Style Settings" -msgstr "Stijl instellingen" - -#: ../includes/register-settings.php:366 -msgid "Misc Settings" -msgstr "Div. instellingen" - -#: ../includes/dashboard-columns.php:7 -msgid "Name" -msgstr "Naam" - -#: ../includes/dashboard-columns.php:8 -msgid "Categories" -msgstr "Categorieën" - -#: ../includes/dashboard-columns.php:9 -msgid "Tags" -msgstr "Trefwoorden" - -#: ../includes/dashboard-columns.php:10 -#: ../includes/graphing.php:14 -msgid "Sales" -msgstr "Verkopen" - -#: ../includes/dashboard-columns.php:11 -#: ../includes/graphing.php:52 -#: ../includes/graphing.php:87 -msgid "Earnings" -msgstr "Inkomsten" - -#: ../includes/dashboard-columns.php:12 -msgid "Short Code" -msgstr "Shortcode" - -#: ../includes/dashboard-columns.php:13 -msgid "Date" -msgstr "Datum" - -#: ../includes/dashboard-columns.php:106 -msgid "Show all categories" -msgstr "Toon alle categorieën" - -#: ../includes/dashboard-columns.php:117 -msgid "Show all tags" -msgstr "Toon alle trefwoorden" - -#: ../includes/widgets.php:21 -msgid "Downloads Cart" -msgstr "Downloads-overzicht" - -#: ../includes/widgets.php:21 -msgid "Display the downloads shopping cart" -msgstr "Toon downloads winkelmandje" - -#: ../includes/widgets.php:72 -msgid "Title:" -msgstr "Titel:" - -#: ../includes/widgets.php:77 -msgid "Show Quantity:" -msgstr "Toon hoeveelheid:" - -#: ../includes/scripts.php:19 -msgid "Please enter a discount code" -msgstr "Voer aub een kortingscode in" - -#: ../includes/scripts.php:20 -msgid "Discount Applied" -msgstr "Korting toegepast" - -#: ../includes/scripts.php:21 -msgid "You have already added this item to your cart" -msgstr "Dit item zit al in je wagentje" - -#: ../includes/scripts.php:22 -msgid "Your cart is empty" -msgstr "Je winkelmandje is leeg" - -#: ../includes/scripts.php:23 -msgid "Loading" -msgstr "Laden..." - -#: ../includes/graphing.php:13 -#: ../includes/graphing.php:51 -msgid "Download" -msgstr "Download" - -#: ../includes/graphing.php:24 -msgid "Downloads Performance in Sales" -msgstr "Downloads-prestaties is verkopen" - -#: ../includes/graphing.php:62 -msgid "Downloads Performance in Earnings" -msgstr "Downloads-prestaties in inkomsten" - -#: ../includes/graphing.php:86 -msgid "Month" -msgstr "Maand" - -#: ../includes/graphing.php:101 -msgid "Earnings per month" -msgstr "Inkomsten per maand" - -#: ../includes/metabox.php:4 -msgid "Download Configuration" -msgstr "Download-instellingen" - -#: ../includes/metabox.php:5 -msgid "Download Stats" -msgstr "Dwonload-statistieken" - -#: ../includes/metabox.php:6 -msgid "Purchase Log" -msgstr "Verkoop logboek" - -#: ../includes/metabox.php:7 -msgid "File Download Log" -msgstr "Bestandsdownload-logboek" - -#: ../includes/metabox.php:8 -msgid "Payment Info" -msgstr "Betaalinformatie" - -#: ../includes/metabox.php:32 -msgid "Pricing" -msgstr "Prijs" - -#: ../includes/metabox.php:36 -msgid "Check this to enable variable pricing." -msgstr "Vink dit aan om variabele prijzen mogelijk te maken" - -#: ../includes/metabox.php:57 -msgid "price option name" -msgstr "Prijs-optie naam" - -#: ../includes/metabox.php:58 -#: ../includes/metabox.php:68 -msgid "9.99" -msgstr "9,99" - -#: ../includes/metabox.php:67 -#: ../includes/metabox.php:106 -#: ../includes/metabox.php:117 -msgid "file name" -msgstr "Bestandsnaam" - -#: ../includes/metabox.php:71 -msgid "Add New Price Option" -msgstr "Een nieuwe prijsoptie toevoegen" - -#: ../includes/metabox.php:84 -msgid "Enter the download price. Do not include a currency symbol" -msgstr "Voer de dwonloadprijs in (zonder mu8unteenheid)." - -#: ../includes/metabox.php:96 -msgid "Download Files" -msgstr "Bestanden downloaden" - -#: ../includes/metabox.php:107 -#: ../includes/metabox.php:118 -msgid "file url" -msgstr "Bestands-url" - -#: ../includes/metabox.php:108 -#: ../includes/metabox.php:119 -msgid "Upload File" -msgstr "Bestand uploaden" - -#: ../includes/metabox.php:122 -#: ../includes/post-types.php:28 -#: ../includes/post-types.php:60 -msgid "Add New" -msgstr "Nieuw bestand" - -#: ../includes/metabox.php:122 -msgid "Upload the downloadable files." -msgstr "De downloadbare bestanden uploaden." - -#: ../includes/metabox.php:134 -msgid "Purchase Text" -msgstr "Aanschaf-tekst" - -#: ../includes/metabox.php:136 -msgid "Add the text you would like displayed for the purchase text" -msgstr "Voer de tekst in die als aanschaf-tekst moet worden getoond" - -#: ../includes/metabox.php:146 -msgid "Link Style" -msgstr "Link-stijl" - -#: ../includes/metabox.php:148 -msgid "Button" -msgstr "Knop" - -#: ../includes/metabox.php:149 -msgid "Text" -msgstr "Tekst" - -#: ../includes/metabox.php:150 -msgid "Choose the style of the purchase link" -msgstr "Kies een style voor de aanschaf-link" - -#: ../includes/metabox.php:162 -msgid "Button Color" -msgstr "Kleur van de knop" - -#: ../includes/metabox.php:170 -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "Kies de kleur voor de link, wanneer hierboeven voor Knop is gekozen" - -#: ../includes/metabox.php:179 -msgid "Disable the purchase button?" -msgstr "De aanschaf-knop deactiveren?" - -#: ../includes/metabox.php:182 -msgid "Check this if you do not want the purchase button displayed." -msgstr "Vink aan wanneer je geen aanschaf-knop wilt tonen." - -#: ../includes/metabox.php:191 -msgid "Notes" -msgstr "Opmerkingen" - -#: ../includes/metabox.php:194 -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "De stijl-opties hierboven hebben GEEN invloed op de stijl van de shortcode. M.b.v. de shortcode kun je een aanschaf-knop voor dit item op elke plek op je website toevoegen." - -#: ../includes/metabox.php:200 -msgid "This short code can be placed anywhere on your site" -msgstr "Deze shortcode kun gebruiken waar je wilt" - -#: ../includes/metabox.php:263 -msgid "Sales:" -msgstr "Verkopen:" - -#: ../includes/metabox.php:269 -msgid "Earnings:" -msgstr "Verdiensten:" - -#: ../includes/metabox.php:285 -msgid "Sales Log" -msgstr "Verkoop logboek" - -#: ../includes/metabox.php:287 -msgid "Each sale for this download is listed below." -msgstr "Alle verkopen van dit item staan hier onder vermeld" - -#: ../includes/metabox.php:301 -#: ../includes/metabox.php:350 -msgid "Date:" -msgstr "Datum:" - -#: ../includes/metabox.php:305 -msgid "Buyer:" -msgstr "Koper:" - -#: ../includes/metabox.php:309 -msgid "Purchase ID:" -msgstr "Aanschaf-ID" - -#: ../includes/metabox.php:317 -msgid "No sales yet" -msgstr "Nog geen verkopen" - -#: ../includes/metabox.php:332 -msgid "Download Log" -msgstr "Download logboek" - -#: ../includes/metabox.php:334 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "Elke keer dat een bestand is gedownload wordt hieronder vermeld" - -#: ../includes/metabox.php:354 -msgid "Downloaded by:" -msgstr "Gedownload door:" - -#: ../includes/metabox.php:358 -msgid "IP Address:" -msgstr "IP-adres:" - -#: ../includes/metabox.php:362 -msgid "File: " -msgstr "Bestand:" - -#: ../includes/metabox.php:371 -msgid "No file downloads yet yet" -msgstr "Nog geen bestanden gedwonload" - -#: ../includes/ajax-functions.php:57 -#: ../includes/process-purchase.php:17 -msgid "The discount you entered is invalid" -msgstr "De korting die je hebt ingevoerd is ongeldig" - -#: ../includes/admin-pages.php:6 -#: ../includes/post-types.php:70 -msgid "Payment History" -msgstr "Betalingsgeschiedenis" - -#: ../includes/admin-pages.php:7 -msgid "Discount Codes" -msgstr "Kortingscodes" - -#: ../includes/admin-pages.php:8 -msgid "Earnings and Sales Reports" -msgstr "Inkomsten- en verkooprapportages" - -#: ../includes/admin-pages.php:8 -msgid "Reports" -msgstr "Rapporten" - -#: ../includes/admin-pages.php:9 -msgid "Easy Digital Download Settings" -msgstr "Easy Digital Download Instellingen" - -#: ../includes/admin-pages.php:9 -msgid "Settings" -msgstr "Instellingen" - -#: ../includes/admin-pages.php:10 -msgid "Easy Digital Download Add Ons" -msgstr "Easy Digital Download Add Ons" - -#: ../includes/admin-pages.php:10 -msgid "Add Ons" -msgstr "Add Ons" - -#: ../includes/error-tracking.php:13 -msgid "Error" -msgstr "Fout" - -#: ../includes/misc-functions.php:40 -msgid "US Dollars ($)" -msgstr "Amerikaanse dollars ($)" - -#: ../includes/misc-functions.php:41 -msgid "Euros (€)" -msgstr "Euros (€)" - -#: ../includes/misc-functions.php:42 -msgid "Pounds Sterling (£)" -msgstr "Britse ponden (£)" - -#: ../includes/misc-functions.php:43 -msgid "Australian Dollars ($)" -msgstr "Australische dollars ($)" - -#: ../includes/misc-functions.php:44 -msgid "Brazilian Real ($)" -msgstr "Braziliaanse reals ($)" - -#: ../includes/misc-functions.php:45 -msgid "Canadian Dollars ($)" -msgstr "Canadese dollars ($)" - -#: ../includes/misc-functions.php:46 -msgid "Czech Koruna" -msgstr "Tsjechische kronen" - -#: ../includes/misc-functions.php:47 -msgid "Danish Krone" -msgstr "Deense kronen" - -#: ../includes/misc-functions.php:48 -msgid "Hong Kong Dollar ($)" -msgstr "Hong Kong dollars ($)" - -#: ../includes/misc-functions.php:49 -msgid "Hungarian Forint" -msgstr "Hongaarse forinten" - -#: ../includes/misc-functions.php:50 -msgid "Israeli Shekel" -msgstr "Israëlische shekels" - -#: ../includes/misc-functions.php:51 -msgid "Japanese Yen (¥)" -msgstr "Japanse yen (¥)" - -#: ../includes/misc-functions.php:52 -msgid "Malaysian Ringgits" -msgstr "Maleisische ringgits" - -#: ../includes/misc-functions.php:53 -msgid "Mexican Peso ($)" -msgstr "Mexicaanse peso's ($)" - -#: ../includes/misc-functions.php:54 -msgid "New Zealand Dollar ($)" -msgstr "Nieuwzeelandse dollars ($)" - -#: ../includes/misc-functions.php:55 -msgid "Norwegian Krone" -msgstr "Noorse kronen" - -#: ../includes/misc-functions.php:56 -msgid "Philippine Pesos" -msgstr "Filippijnse Peso's" - -#: ../includes/misc-functions.php:57 -msgid "Polish Zloty" -msgstr "Poolse zloty" - -#: ../includes/misc-functions.php:58 -msgid "Singapore Dollar ($)" -msgstr "Singaporese dollars ($)" - -#: ../includes/misc-functions.php:59 -msgid "Swedish Krona" -msgstr "Zweedse kronen" - -#: ../includes/misc-functions.php:60 -msgid "Swiss Franc" -msgstr "Zwitserse franken" - -#: ../includes/misc-functions.php:61 -msgid "Taiwan New Dollars" -msgstr "Taiwanese nieuwe dollars" - -#: ../includes/misc-functions.php:62 -msgid "Thai Baht" -msgstr "Thaise baht" - -#: ../includes/misc-functions.php:63 -msgid "Indian Rupee" -msgstr "Indiase rupee" - -#: ../includes/process-download.php:64 -msgid "You do not have permission to download this file" -msgstr "Je mag dit bestand niet downloaden" - -#: ../includes/process-download.php:64 -msgid "Purchase Verification Failed" -msgstr "Bestel verifactie is mislukt" - -#: ../includes/checkout-template.php:57 -msgid "Choose Your Payment Method" -msgstr "Kies je gewenste betaalmethode" - -#: ../includes/checkout-template.php:68 -msgid "Next" -msgstr "Volgende" - -#: ../includes/checkout-template.php:115 -msgid "Email address" -msgstr "Email-adres" - -#: ../includes/checkout-template.php:116 -msgid "Email Address" -msgstr "Email adres" - -#: ../includes/checkout-template.php:119 -#: ../includes/checkout-template.php:120 -#: ../includes/checkout-template.php:289 -#: ../includes/checkout-template.php:290 -msgid "First Name" -msgstr "Voornaam" - -#: ../includes/checkout-template.php:123 -#: ../includes/checkout-template.php:293 -msgid "Last name" -msgstr "Achternaam" - -#: ../includes/checkout-template.php:124 -#: ../includes/checkout-template.php:294 -msgid "Last Name" -msgstr "Achternaam" - -#: ../includes/checkout-template.php:132 -msgid "Enter discount" -msgstr "Voeg korting toe" - -#: ../includes/checkout-template.php:134 -msgid "Discount" -msgstr "Korting" - -#: ../includes/checkout-template.php:136 -msgid "Apply Discount" -msgstr "Korting toepassen" - -#: ../includes/checkout-template.php:162 -msgid "Show Terms" -msgstr "Toon de voorwaarden" - -#: ../includes/checkout-template.php:163 -msgid "Hide Terms" -msgstr "Verberg voorwaarden" - -#: ../includes/checkout-template.php:166 -msgid "Agree to Terms?" -msgstr "Akkoord met de voorwaarden?" - -#: ../includes/checkout-template.php:190 -msgid "Go back" -msgstr "Ga terug" - -#: ../includes/checkout-template.php:194 -msgid "You must be logged in to complete your purchase" -msgstr "Je moet ingelogd zijn om een bestelling te kunnen plaatsen" - -#: ../includes/checkout-template.php:214 -msgid "Card name" -msgstr "Soort creditcard" - -#: ../includes/checkout-template.php:215 -msgid "Name on the Card" -msgstr "Naam op creditcard" - -#: ../includes/checkout-template.php:218 -msgid "Card number" -msgstr "Kaartnummer" - -#: ../includes/checkout-template.php:219 -msgid "Card Number" -msgstr "Kaartnummer" - -#: ../includes/checkout-template.php:222 -msgid "Security code" -msgstr "Security code" - -#: ../includes/checkout-template.php:223 -msgid "CVC" -msgstr "CVC" - -#: ../includes/checkout-template.php:229 -msgid "Year" -msgstr "Jaar" - -#: ../includes/checkout-template.php:230 -msgid "Expiration (MM/YYYY)" -msgstr "Geldig tot (MM/YYYY)" - -#: ../includes/checkout-template.php:248 -msgid "Credit Card Info" -msgstr "Creditcard informatie" - -#: ../includes/checkout-template.php:250 -msgid "Address line 1" -msgstr "Adresregel 1" - -#: ../includes/checkout-template.php:251 -msgid "Billing Address" -msgstr "Factuuradres" - -#: ../includes/checkout-template.php:254 -msgid "Address line 2" -msgstr "Adresregel 2" - -#: ../includes/checkout-template.php:255 -msgid "Billing Address Line 2" -msgstr "Factuuradres regel 2" - -#: ../includes/checkout-template.php:258 -msgid "City" -msgstr "Plaats" - -#: ../includes/checkout-template.php:259 -msgid "Billing City" -msgstr "Factuur - Plaats" - -#: ../includes/checkout-template.php:262 -msgid "State / Province" -msgstr "Provincie" - -#: ../includes/checkout-template.php:263 -msgid "Billing State / Province" -msgstr "Factuur - provincie" - -#: ../includes/checkout-template.php:266 -msgid "Zip / Postal code" -msgstr "Postcode" - -#: ../includes/checkout-template.php:267 -msgid "Billing Zip / Postal Code" -msgstr "Factuuradres postcode" - -#: ../includes/checkout-template.php:279 -msgid "Create an account" -msgstr "Maak een account aan" - -#: ../includes/checkout-template.php:279 -msgid "(optional)" -msgstr "(optioneel)" - -#: ../includes/checkout-template.php:281 -#: ../includes/checkout-template.php:282 -#: ../includes/checkout-template.php:318 -msgid "Username" -msgstr "Gebruikersnaam" - -#: ../includes/checkout-template.php:285 -#: ../includes/checkout-template.php:286 -msgid "Email" -msgstr "Email" - -#: ../includes/checkout-template.php:297 -#: ../includes/checkout-template.php:298 -#: ../includes/checkout-template.php:322 -msgid "Password" -msgstr "Wachtwoord" - -#: ../includes/checkout-template.php:301 -msgid "Confirm password" -msgstr "Bevestig wachtwoord" - -#: ../includes/checkout-template.php:302 -msgid "Password Again" -msgstr "Herhaal wachtwoord" - -#: ../includes/checkout-template.php:304 -msgid "Already have an account?" -msgstr "Heb je al een account?" - -#: ../includes/checkout-template.php:304 -msgid "Login" -msgstr "Inloggen" - -#: ../includes/checkout-template.php:315 -msgid "Login to your account" -msgstr "Log in op je account" - -#: ../includes/checkout-template.php:317 -msgid "Your username" -msgstr "Je gebruikersnaam" - -#: ../includes/checkout-template.php:321 -msgid "Your password" -msgstr "Je wachtwoord" - -#: ../includes/checkout-template.php:325 -msgid "Need to create an account?" -msgstr "Wil je een account aanmaken?" - -#: ../includes/checkout-template.php:325 -msgid "Register" -msgstr "Registreer" - -#: ../includes/email-functions.php:26 -msgid "Hello" -msgstr "Hallo" - -#: ../includes/email-functions.php:26 -msgid "A download purchase has been made" -msgstr "Er is een download besteld" - -#: ../includes/email-functions.php:27 -msgid "Downloads sold:" -msgstr "Verkochte downloads:" - -#: ../includes/email-functions.php:36 -msgid "Amount: " -msgstr "Hoeveelheid" - -#: ../includes/email-functions.php:37 -msgid "Thank you" -msgstr "Dank u!" - -#: ../includes/email-functions.php:39 -msgid "New download purchase" -msgstr "Nieuwe download" - -#: ../includes/process-purchase.php:20 -msgid "You must agree to the terms of use" -msgstr "Je dient akkoord te gaan met de voorwaarden" - -#: ../includes/process-purchase.php:37 -msgid "Username already taken" -msgstr "Deze gebruikersnaam is reeds in gebruik" - -#: ../includes/process-purchase.php:41 -msgid "Invalid username" -msgstr "Ongeldige gebruikersnaam" - -#: ../includes/process-purchase.php:45 -msgid "Enter a username" -msgstr "Voer een gebruikersnaam in" - -#: ../includes/process-purchase.php:49 -msgid "Invalid email" -msgstr "Ongeldig mailadres" - -#: ../includes/process-purchase.php:53 -msgid "Email already used" -msgstr "Dit emailadres is al in gebruik" - -#: ../includes/process-purchase.php:57 -msgid "Enter a password" -msgstr "Voer een wachtwoord in" - -#: ../includes/process-purchase.php:61 -msgid "Passwords don't match" -msgstr "De wachtwoorden komen niet overeen" - -#: ../includes/process-purchase.php:78 -msgid "The password you entered is incorrect" -msgstr "Dit wachtwoord is niet correct" - -#: ../includes/process-purchase.php:82 -msgid "The username you entered does not exist" -msgstr "De ingevoerde gebruiker bestaat niet" - -#: ../includes/process-purchase.php:86 -msgid "Something has gone wrong, please try again" -msgstr "Er is iets mis gegaan; probeer het a.u.b. opnieuw" - -#: ../includes/process-purchase.php:90 -msgid "You must enter a valid email address." -msgstr "Je moet een geldig mailadres invoeren" - -#: ../includes/gateway-functions.php:9 -msgid "Manual Payment" -msgstr "Handmatige betaling" - -#: ../includes/admin-notices.php:6 -msgid "Discount code updated." -msgstr "Kortingscode is bijgewerkt" - -#: ../includes/admin-notices.php:9 -msgid "There was a problem updating your discount code, please try again." -msgstr "Er was een probleem met het aanpassen van de kortingscode; probeer het nog eens." - -#: ../includes/admin-notices.php:12 -msgid "The payment has been deleted." -msgstr "De betaling is verwijderd" - -#: ../includes/admin-notices.php:15 -msgid "The purchase receipt has been resent." -msgstr "Bestelbewijs is opnieuw verstuurd" - -#: ../includes/post-types.php:26 -#: ../includes/post-types.php:38 -msgid "Downloads" -msgstr "Downloads" - -#: ../includes/post-types.php:29 -msgid "Add New Download" -msgstr "Voeg een download toe" - -#: ../includes/post-types.php:30 -msgid "Edit Download" -msgstr "Bewerk downloads" - -#: ../includes/post-types.php:31 -msgid "New Download" -msgstr "Nieuwe download" - -#: ../includes/post-types.php:32 -msgid "All Downloads" -msgstr "Alle downloads" - -#: ../includes/post-types.php:33 -msgid "View Download" -msgstr "Bekijk download" - -#: ../includes/post-types.php:34 -msgid "Search Downloads" -msgstr "Zoek downloads" - -#: ../includes/post-types.php:35 -msgid "No Downloads found" -msgstr "Geen downloads gevonden" - -#: ../includes/post-types.php:36 -msgid "No Downloads found in Trash" -msgstr "Geen downloads in de prullenbak gevonden" - -#: ../includes/post-types.php:58 -msgid "Payments" -msgstr "Transacties" - -#: ../includes/post-types.php:59 -msgid "Payment" -msgstr "Transactie" - -#: ../includes/post-types.php:61 -msgid "Add New Payment" -msgstr "Nieuwe transactie toevoegen" - -#: ../includes/post-types.php:62 -msgid "Edit Payment" -msgstr "Transactie bewerken" - -#: ../includes/post-types.php:63 -msgid "New Payment" -msgstr "Nieuwe transactie" - -#: ../includes/post-types.php:64 -msgid "All Payments" -msgstr "Alle transacties" - -#: ../includes/post-types.php:65 -msgid "View Payment" -msgstr "Bekijk transactie" - -#: ../includes/post-types.php:66 -msgid "Search Payments" -msgstr "Transactie zoeken" - -#: ../includes/post-types.php:67 -msgid "No Payments found" -msgstr "Geen transacties gevonden" - -#: ../includes/post-types.php:68 -msgid "No Payments found in Trash" -msgstr "Geen transacties in prullenbak gevonden" - -#: ../includes/post-types.php:96 -msgid "Category" -msgstr "Categorie" - -#: ../includes/post-types.php:97 -msgid "Search Categories" -msgstr "Zoek categorieën" - -#: ../includes/post-types.php:98 -msgid "All Categories" -msgstr "Alle categorieën" - -#: ../includes/post-types.php:99 -msgid "Parent Category" -msgstr "Bovenliggende categorie" - -#: ../includes/post-types.php:100 -msgid "Parent Category:" -msgstr "Bovenliggende categorie:" - -#: ../includes/post-types.php:101 -msgid "Edit Category" -msgstr "Categorie bewerken" - -#: ../includes/post-types.php:102 -msgid "Update Category" -msgstr "Categorie bijwerken" - -#: ../includes/post-types.php:103 -msgid "Add New Category" -msgstr "Categorie toevoegen" - -#: ../includes/post-types.php:104 -msgid "New Category Name" -msgstr "Nieuwe categorie-naam" - -#: ../includes/post-types.php:118 -msgid "Tag" -msgstr "Trefwoord" - -#: ../includes/post-types.php:119 -msgid "Search Tags" -msgstr "Trefwoorden zoeken" - -#: ../includes/post-types.php:120 -msgid "All Tags" -msgstr "Alle trefwoorden" - -#: ../includes/post-types.php:121 -msgid "Parent Tag" -msgstr "Bovenliggend trefwoord" - -#: ../includes/post-types.php:122 -msgid "Parent Tag:" -msgstr "Bovenliggend trefwoord:" - -#: ../includes/post-types.php:123 -msgid "Edit Tag" -msgstr "Trefwoord bewerken" - -#: ../includes/post-types.php:124 -msgid "Update Tag" -msgstr "Trefwoord bijwerken" - -#: ../includes/post-types.php:125 -msgid "Add New Tag" -msgstr "Trefwoord toevoegen" - -#: ../includes/post-types.php:126 -msgid "New Tag Name" -msgstr "Naam nieuw trefwoord" - -#: ../includes/post-types.php:144 -#: ../includes/post-types.php:145 -msgid "Download updated." -msgstr "Download bijgewerkt" - -#: ../includes/post-types.php:146 -msgid "Download published." -msgstr "Download gepupliceerd" - -#: ../includes/post-types.php:147 -msgid "Download saved." -msgstr "Download opgeslagen" - -#: ../includes/post-types.php:148 -msgid "Download submitted." -msgstr "Download toegevoegd" - -#: ../includes/shortcodes.php:35 -msgid "Download Name" -msgstr "Donwload naam" - -#: ../includes/shortcodes.php:36 -msgid "Files" -msgstr "Bestanden" - -#: ../includes/shortcodes.php:58 -msgid "No downloadable files found." -msgstr "Geen downloadable bestand gevonden" - -#: ../includes/shortcodes.php:68 -msgid "You have not purchased any downloads" -msgstr "Je hebt nog geen downloads aangeschaft" - -#: ../includes/shortcodes.php:95 -msgid "Add to Cart" -msgstr "Aan winkelmandje toevoegen" - -#: ../includes/shortcodes.php:122 -msgid "No downloads found" -msgstr "Geen downloads gevonden" - -#: ../includes/gateways/paypal.php:198 -msgid "Invalid IPN" -msgstr "Ongeldig IPN nummer" - -#: ../includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "Item naam:" - -#: ../includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "Item prijs" - -#: ../includes/templates/checkout_cart.php:8 -#: ../includes/admin-pages/discount-codes.php:28 -#: ../includes/admin-pages/discount-codes.php:42 -msgid "Actions" -msgstr "Acties" - -#: ../includes/templates/checkout_cart.php:43 -msgid "Total" -msgstr "Totaal" - -#: ../includes/admin-pages/discount-codes.php:20 -#: ../includes/admin-pages/discount-codes.php:34 -#: ../includes/admin-pages/forms/edit-discount.php:22 -#: ../includes/admin-pages/forms/add-discount.php:16 -msgid "Code" -msgstr "Code" - -#: ../includes/admin-pages/discount-codes.php:21 -#: ../includes/admin-pages/discount-codes.php:35 -#: ../includes/admin-pages/forms/edit-discount.php:31 -#: ../includes/admin-pages/forms/add-discount.php:25 -msgid "Type" -msgstr "Type" - -#: ../includes/admin-pages/discount-codes.php:22 -#: ../includes/admin-pages/discount-codes.php:36 -#: ../includes/admin-pages/forms/edit-discount.php:43 -#: ../includes/admin-pages/forms/add-discount.php:37 -msgid "Amount" -msgstr "Hoeveelheid" - -#: ../includes/admin-pages/discount-codes.php:23 -#: ../includes/admin-pages/discount-codes.php:37 -msgid "Uses" -msgstr "Gebruikt" - -#: ../includes/admin-pages/discount-codes.php:24 -#: ../includes/admin-pages/discount-codes.php:38 -#: ../includes/admin-pages/forms/edit-discount.php:70 -#: ../includes/admin-pages/forms/add-discount.php:64 -msgid "Max Uses" -msgstr "Max. te gebruiken" - -#: ../includes/admin-pages/discount-codes.php:25 -#: ../includes/admin-pages/discount-codes.php:39 -msgid "Start Date" -msgstr "Start datum" - -#: ../includes/admin-pages/discount-codes.php:26 -#: ../includes/admin-pages/discount-codes.php:40 -msgid "Expiration" -msgstr "Einddatum" - -#: ../includes/admin-pages/discount-codes.php:27 -#: ../includes/admin-pages/discount-codes.php:41 -#: ../includes/admin-pages/payments-history.php:51 -#: ../includes/admin-pages/payments-history.php:63 -#: ../includes/admin-pages/forms/edit-discount.php:79 -msgid "Status" -msgstr "Status" - -#: ../includes/admin-pages/discount-codes.php:65 -#: ../includes/admin-pages/discount-codes.php:67 -msgid "unlimited" -msgstr "onbegrensd" - -#: ../includes/admin-pages/discount-codes.php:76 -msgid "No start date" -msgstr "Geen startdatum" - -#: ../includes/admin-pages/discount-codes.php:83 -msgid "Expired" -msgstr "Verlopen" - -#: ../includes/admin-pages/discount-codes.php:85 -msgid "no expiration" -msgstr "Geen verloopdatum" - -#: ../includes/admin-pages/discount-codes.php:91 -#: ../includes/admin-pages/payments-history.php:83 -msgid "Edit" -msgstr "Bewerken" - -#: ../includes/admin-pages/discount-codes.php:93 -msgid "Deactivate" -msgstr "Deactiveren" - -#: ../includes/admin-pages/discount-codes.php:95 -msgid "Activate" -msgstr "Activeren" - -#: ../includes/admin-pages/discount-codes.php:97 -#: ../includes/admin-pages/payments-history.php:85 -msgid "Delete" -msgstr "Verwijderen" - -#: ../includes/admin-pages/discount-codes.php:102 -msgid "No discount codes have been created." -msgstr "Er zijn nog geen kortingscodes aangemaakt" - -#: ../includes/admin-pages/reports.php:14 -msgid "Transactions created while in test mode are not included on this page." -msgstr "Transacties in testmodus worden niet toegevoegd aan deze pagina" - -#: ../includes/admin-pages/payments-history.php:30 -msgid "Payment mode" -msgstr "Betaalmodus" - -#: ../includes/admin-pages/payments-history.php:32 -msgid "Live" -msgstr "Live" - -#: ../includes/admin-pages/payments-history.php:33 -msgid "Test" -msgstr "Test" - -#: ../includes/admin-pages/payments-history.php:37 -msgid "Payments per page" -msgstr "Betalingen per pagina" - -#: ../includes/admin-pages/payments-history.php:39 -msgid "Show" -msgstr "Tonen" - -#: ../includes/admin-pages/payments-history.php:44 -#: ../includes/admin-pages/payments-history.php:56 -msgid "ID" -msgstr "ID" - -#: ../includes/admin-pages/payments-history.php:46 -#: ../includes/admin-pages/payments-history.php:58 -msgid "Key" -msgstr "Sleutel" - -#: ../includes/admin-pages/payments-history.php:47 -#: ../includes/admin-pages/payments-history.php:59 -msgid "Products" -msgstr "Producten" - -#: ../includes/admin-pages/payments-history.php:48 -#: ../includes/admin-pages/payments-history.php:60 -msgid "Price" -msgstr "Prijs" - -#: ../includes/admin-pages/payments-history.php:50 -#: ../includes/admin-pages/payments-history.php:62 -msgid "User" -msgstr "Gebruiker" - -#: ../includes/admin-pages/payments-history.php:84 -msgid "Resend Purchase Receipt" -msgstr "Bestelbevesting opnieuw versturen" - -#: ../includes/admin-pages/payments-history.php:98 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "Aankoop-details voor betaling #%s" - -#: ../includes/admin-pages/payments-history.php:98 -msgid "View Order Details" -msgstr "Bekijk bestellingsdetails" - -#: ../includes/admin-pages/payments-history.php:103 -msgid "Purchased File" -msgstr "Besteld bestand" - -#: ../includes/admin-pages/payments-history.php:103 -msgid "Purchased Files" -msgstr "Bestelde bestanden" - -#: ../includes/admin-pages/payments-history.php:118 -msgid "Price: " -msgstr "Prijs:" - -#: ../includes/admin-pages/payments-history.php:124 -msgid "Discount used:" -msgstr "Toegepaste korting:" - -#: ../includes/admin-pages/payments-history.php:124 -msgid "none" -msgstr "geen" - -#: ../includes/admin-pages/payments-history.php:125 -msgid "Total:" -msgstr "Totaal:" - -#: ../includes/admin-pages/payments-history.php:128 -msgid "Buyer's Personal Details:" -msgstr "Persoonsgegevens besteller:" - -#: ../includes/admin-pages/payments-history.php:130 -msgid "Name:" -msgstr "Naam:" - -#: ../includes/admin-pages/payments-history.php:131 -msgid "Email:" -msgstr "Email:" - -#: ../includes/admin-pages/payments-history.php:134 -#: ../includes/admin-pages/forms/edit-payment.php:58 -msgid "Close" -msgstr "Sluiten" - -#: ../includes/admin-pages/payments-history.php:139 -msgid "guest" -msgstr "Gast" - -#: ../includes/admin-pages/payments-history.php:146 -msgid "No payments recorded yet" -msgstr "Nog geen betalingen geregistreerd" - -#: ../includes/admin-pages/payments-history.php:161 -msgid "Previous" -msgstr "Vorige" - -#: ../includes/admin-pages/settings.php:13 -msgid "General" -msgstr "Algemeen" - -#: ../includes/admin-pages/settings.php:15 -msgid "Emails" -msgstr "Emails" - -#: ../includes/admin-pages/settings.php:16 -msgid "Styles" -msgstr "Stijlen" - -#: ../includes/admin-pages/settings.php:17 -msgid "Misc" -msgstr "Div." - -#: ../includes/admin-pages/add-ons.php:9 -msgid "Add Ons for Easy Digital Downloads" -msgstr "Add Ons voor Easy Digital Downloads" - -#: ../includes/admin-pages/add-ons.php:10 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "Deze add-ons voegen extra functionaliteit toe." - -#: ../includes/admin-pages/forms/edit-discount.php:3 -msgid "Something went wrong." -msgstr "Er is iets mis gegaan." - -#: ../includes/admin-pages/forms/edit-discount.php:7 -msgid "Edit Discount" -msgstr "Korting bewerken" - -#: ../includes/admin-pages/forms/edit-discount.php:7 -#: ../includes/admin-pages/forms/edit-payment.php:3 -msgid "Go Back" -msgstr "Ga terug" - -#: ../includes/admin-pages/forms/edit-discount.php:17 -#: ../includes/admin-pages/forms/add-discount.php:11 -msgid "The name of this discount" -msgstr "De naam voor deze korting" - -#: ../includes/admin-pages/forms/edit-discount.php:26 -#: ../includes/admin-pages/forms/add-discount.php:20 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "Voer een code in voor deze korting (bijv. 10 procent)" - -#: ../includes/admin-pages/forms/edit-discount.php:35 -#: ../includes/admin-pages/forms/add-discount.php:29 -msgid "Percentage" -msgstr "Percentage" - -#: ../includes/admin-pages/forms/edit-discount.php:36 -#: ../includes/admin-pages/forms/add-discount.php:30 -msgid "Flat amount" -msgstr "Flat amount" - -#: ../includes/admin-pages/forms/edit-discount.php:38 -#: ../includes/admin-pages/forms/add-discount.php:32 -msgid "The kind of discount to apply for this discount." -msgstr "Het soort korting dat toegepast moet worden." - -#: ../includes/admin-pages/forms/edit-discount.php:47 -#: ../includes/admin-pages/forms/add-discount.php:41 -msgid "The amount of this discount code." -msgstr "Het bedrag voor deze kortingscode." - -#: ../includes/admin-pages/forms/edit-discount.php:52 -#: ../includes/admin-pages/forms/add-discount.php:46 -msgid "Start date" -msgstr "Start datum" - -#: ../includes/admin-pages/forms/edit-discount.php:56 -#: ../includes/admin-pages/forms/add-discount.php:50 -msgid "Enter the start date for this discount code in the format of yyyy-mm-dd. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "Voer een startdatum in voor deze kortingscode in het formaat yyyy-mm-dd. Om geen startdatum te gebruiken laat je dit leeg. Wanneer hier een datum is ingevoerd kan pas op- of na deze datum gebruik worden gemaakt van de kortingscode." - -#: ../includes/admin-pages/forms/edit-discount.php:61 -#: ../includes/admin-pages/forms/add-discount.php:55 -msgid "Expiration date" -msgstr "Verloopdatum" - -#: ../includes/admin-pages/forms/edit-discount.php:65 -#: ../includes/admin-pages/forms/add-discount.php:59 -msgid "Enter the expiration date for this discount code in the format of yyyy-mm-dd. For no expiration, leave blank" -msgstr "Voer een einddatum in voor deze kortingscode (yyyy-mm-dd). Laat leeg om geen einddatum te gebruiken." - -#: ../includes/admin-pages/forms/edit-discount.php:74 -#: ../includes/admin-pages/forms/add-discount.php:68 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "Het maximaal aantal keren dat deze kortingscode gebruikt kan worden. Laat leeg voor geen maximum." - -#: ../includes/admin-pages/forms/edit-discount.php:83 -msgid "Active" -msgstr "Actief" - -#: ../includes/admin-pages/forms/edit-discount.php:84 -msgid "Inactive" -msgstr "Niet actief" - -#: ../includes/admin-pages/forms/edit-discount.php:86 -msgid "The status of this discount code." -msgstr "De status van deze kortingscode." - -#: ../includes/admin-pages/forms/edit-discount.php:96 -msgid "Update Discount Code" -msgstr "Kortingscode bijwerken" - -#: ../includes/admin-pages/forms/add-discount.php:1 -msgid "Add New Discount" -msgstr "Nieuwe kortingscode toevoegen" - -#: ../includes/admin-pages/forms/add-discount.php:76 -msgid "Add Discount Code" -msgstr "Kortingscode toevoegen" - -#: ../includes/admin-pages/forms/edit-payment.php:9 -msgid "Downloads Purchased" -msgstr "Aantal bestelde downloads" - -#: ../includes/admin-pages/forms/edit-payment.php:21 -#, php-format -msgid "Add download to purchase #%s" -msgstr "Voeg een download toe aan bestelling #%s" - -#: ../includes/admin-pages/forms/edit-payment.php:21 -msgid "Add download to purchase" -msgstr "Download toevoegen om te bestellen" - -#: ../includes/admin-pages/forms/edit-payment.php:26 -msgid "Payment Status" -msgstr "Betaalstatus" - -#: ../includes/admin-pages/forms/edit-payment.php:31 -msgid "Pending" -msgstr "In afwachting" - -#: ../includes/admin-pages/forms/edit-payment.php:32 -msgid "Complete" -msgstr "Voltooid" - -#: ../includes/admin-pages/forms/edit-payment.php:44 -msgid "Update Payment" -msgstr "Betaling bijwerken" - -#: ../includes/admin-pages/forms/edit-payment.php:57 -msgid "Add Selected Downloads" -msgstr "Voeg geselecteerde downloads toe" - diff --git a/languages/edd-pl_PL.mo b/languages/edd-pl_PL.mo deleted file mode 100644 index 52227341060..00000000000 Binary files a/languages/edd-pl_PL.mo and /dev/null differ diff --git a/languages/edd-pl_PL.po b/languages/edd-pl_PL.po deleted file mode 100644 index 219979f731a..00000000000 --- a/languages/edd-pl_PL.po +++ /dev/null @@ -1,2286 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-25 10:23-0600\n" -"PO-Revision-Date: 2012-09-26 11:49+0100\n" -"Last-Translator: Bartosz Sobaczewski \n" -"Language-Team: b'art \n" -"Language: pl_PL\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_x;_n;esc_attr__;esc_attr_e;esc_html__;" -"esc_html_e;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_x:1,2c\n" -"X-Poedit-Basepath: ../\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Generator: Poedit 1.5.3\n" -"X-Poedit-SearchPath-0: .\n" - -#: includes/template-functions.php:48 -#, php-format -msgid "" -"No checkout page has been configured. Visit Settings to " -"set one." -msgstr "" -"Nie została skonfigurowany strona \"Płacę\". Odwiedź Ustawienia, aby ją skonfigurować." - -#: includes/template-functions.php:60 -msgid "Purchase" -msgstr "Zamów" - -#: includes/template-functions.php:137 includes/template-functions.php:158 -#: includes/cart-template.php:46 includes/cart-template.php:49 -msgid "Checkout" -msgstr "Do kasy" - -#: includes/template-functions.php:167 -msgid "added to your cart" -msgstr "dodany do koszyka" - -#: includes/template-functions.php:263 -msgid "Gray" -msgstr "Szary" - -#: includes/template-functions.php:264 -msgid "Pink" -msgstr "Różowy" - -#: includes/template-functions.php:265 -msgid "Blue" -msgstr "Niebieski" - -#: includes/template-functions.php:266 -msgid "Green" -msgstr "Zielony" - -#: includes/template-functions.php:267 -msgid "Teal" -msgstr "Żólty" - -#: includes/template-functions.php:268 -msgid "Black" -msgstr "Czarny" - -#: includes/template-functions.php:269 -msgid "Dark Gray" -msgstr "Ciemny szary" - -#: includes/template-functions.php:270 -msgid "Orange" -msgstr "Pomarańczowy" - -#: includes/template-functions.php:271 -msgid "Purple" -msgstr "Purpurowy" - -#: includes/template-functions.php:272 -msgid "Slate" -msgstr "Szaroniebieski" - -#: includes/template-functions.php:293 -msgid "Button" -msgstr "Przycisk" - -#: includes/template-functions.php:294 -msgid "Plain Text" -msgstr "Czysty tekst" - -#: includes/template-functions.php:317 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "Już kupiłeś ten plik, ale możesz kupić go ponownie jeśli chcesz." - -#: includes/payment-functions.php:299 -msgid "Pending" -msgstr "W toku" - -#: includes/payment-functions.php:300 -msgid "Complete" -msgstr "Zakończone" - -#: includes/payment-functions.php:301 -msgid "Refunded" -msgstr "Zwrócone" - -#: includes/email-template.php:23 -msgid "Default Template" -msgstr "Szablon domyślny" - -#: includes/email-template.php:24 -msgid "No template, plain text only" -msgstr "Bez szablonu, tylko czysty tekst" - -#: includes/email-template.php:115 -msgid "Sample Product Title" -msgstr "Przykładowy tytuł" - -#: includes/email-template.php:118 -msgid "Sample Download File Name" -msgstr "Przykładowa nazwa pliku" - -#: includes/email-template.php:118 -msgid "Optional notes about this download." -msgstr "Opcjonalne uwagi na temat pobieranego pliku." - -#: includes/email-template.php:129 -msgid "These are some sample notes added to a product." -msgstr "To są przykładowe uwagi dodane do produktu." - -#: includes/email-template.php:168 -msgid "Purchase Receipt Preview" -msgstr "Podgląd rachunku" - -#: includes/email-template.php:168 -msgid "Preview Purchase Receipt" -msgstr "Zobacz podgląd rachunku" - -#: includes/email-template.php:209 -msgid "Dear" -msgstr "Szanowny/a" - -#: includes/email-template.php:210 -msgid "" -"Thank you for your purchase. Please click on the link(s) below to download " -"your files." -msgstr "" -"Dziękujemy za złożenie zamówienia. Poniżej znajduje się lista linków do " -"zakupionych plików." - -#: includes/cart-template.php:80 -msgid "remove" -msgstr "usuń" - -#: includes/cart-template.php:99 -msgid "Your cart is empty." -msgstr "Twój koszyk jest pusty." - -#: includes/register-settings.php:41 -msgid "Test Mode" -msgstr "Tryb testowy" - -#: includes/register-settings.php:42 -msgid "" -"While in test mode no live transactions are processed. To fully use test " -"mode, you must have a sandbox (test) account for the payment gateway you are " -"testing." -msgstr "" -"Podczas pracy w trybie testowym żadne transakcje są przetwarzane. Aby w " -"pełni korzystać z trybu testowego, musisz mieć konto w trybie piaskownicy na " -"bramce płatności, którą testujesz." - -#: includes/register-settings.php:47 -msgid "Checkout Page" -msgstr "Strona zamówienia" - -#: includes/register-settings.php:48 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "Jest to strona, gdzie kupujący zakończy swoje zakupy." - -#: includes/register-settings.php:54 -msgid "Success Page" -msgstr "Strona Sukces" - -#: includes/register-settings.php:55 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "To jeste strona, która jest przesyłana po zakończeniu zakupów" - -#: includes/register-settings.php:61 -msgid "Download Links on Success Page" -msgstr "Linki na stronie Sukcesu" - -#: includes/register-settings.php:62 -msgid "" -"Show a list of all download links on the success page after completing a " -"purchase?" -msgstr "" -"Pokaać listę wszystkich plików do pobrania na stronie Sukces po zakończeniu " -"zakupu?" - -#: includes/register-settings.php:67 -msgid "Currency Settings" -msgstr "Ustawienia waluty" - -#: includes/register-settings.php:68 -msgid "Configure the currency options" -msgstr "Konfiguracja ustawień waluty" - -#: includes/register-settings.php:73 -msgid "Currency" -msgstr "Waluta" - -#: includes/register-settings.php:74 -msgid "" -"Choose your currency. Note that some payment gateways have currency " -"restrictions." -msgstr "" -"Wybierz walutę. Pamiętaj, że niektóre bramki płatnicze mogą nie obsługiwać " -"wybranej przez Ciebie waluty." - -#: includes/register-settings.php:80 -msgid "Currency Position" -msgstr "Pozycja walutowa" - -#: includes/register-settings.php:81 -msgid "Choose the location of the currency sign." -msgstr "Wyświetlaj symbol waluty." - -#: includes/register-settings.php:84 -msgid "Before - $10" -msgstr "Przed - $10" - -#: includes/register-settings.php:85 -msgid "After - 10$" -msgstr "Po - 10$" - -#: includes/register-settings.php:90 -msgid "Thousands Separator" -msgstr "Separator tysiąca" - -#: includes/register-settings.php:91 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "Symbol (zazwyczaj, lub.) oddzielający tysiące" - -#: includes/register-settings.php:98 -msgid "Decimal Separator" -msgstr "Separator dziesiętny" - -#: includes/register-settings.php:99 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "Symbol (zwykle lub.) oddzielający punkty dziesiętne" - -#: includes/register-settings.php:110 -msgid "Payment Gateways" -msgstr "Bramki płatności" - -#: includes/register-settings.php:111 -msgid "Choose the payment gateways you want to enable." -msgstr "Wybierz bramki płatności, które chcesz włączyć." - -#: includes/register-settings.php:117 -msgid "Accepted Payment Method Icons" -msgstr "Ikony akceptowanych metod płatności" - -#: includes/register-settings.php:118 -msgid "Display icons for the selected payment methods" -msgstr "Wyświetlaj ikony dla wybranych metod płatności" - -#: includes/register-settings.php:118 -msgid "" -"You will also need to configure your gateway settings if you are accepting " -"credit cards" -msgstr "" -"Musisz również skonfigurować ustawienia bramy, jeśli chcesz akceptować karty " -"kredytowe" - -#: includes/register-settings.php:131 -msgid "PayPal Settings" -msgstr "Ustawienia PayPal" - -#: includes/register-settings.php:132 -msgid "Configure the PayPal settings" -msgstr "Skonfiguruj ustawienia systemu PayPal" - -#: includes/register-settings.php:137 -msgid "PayPal Email" -msgstr "E-mail konta PayPal" - -#: includes/register-settings.php:138 -msgid "Enter your PayPal account's email" -msgstr "Wprowadź adres e-mail konta PayPal" - -#: includes/register-settings.php:144 -msgid "Alternate PayPal Purchase Verification" -msgstr "Alternatywna weryfikacja zakupu PayPal" - -#: includes/register-settings.php:145 -msgid "" -"If payments are not getting marked as complete, then check this box. Note, " -"this requires that buyers return to your site from PayPal." -msgstr "" -"Zaznacz to pole, jeżeli płatności nie są oznaczone jako zakończone. Uwaga, " -"to wymaga, aby kupujący powrócił do witryny z PayPal." - -#: includes/register-settings.php:150 -msgid "Disable PayPal IPN Verification" -msgstr "Wyłącz weryfikację IPN PayPal" - -#: includes/register-settings.php:151 -msgid "" -"If payments are not getting marked as complete, then check this box. This " -"forces the site to use a slightly less secure method of verifying purchases." -msgstr "" -"Zaznacz to pole, jeżeli płatności nie są coraz oznaczone jako zakończone. To " -"wymusi nieco mniej bezpieczną metodę weryfikacji zakupów." - -#: includes/register-settings.php:160 -msgid "Email Template" -msgstr "Szablon powiadomienia e-mail" - -#: includes/register-settings.php:161 -msgid "" -"Choose a template. Click \"Save Changes\" then \"Preview Purchase Receipt\" " -"to see the new template." -msgstr "" -"Wybierz szablon. Naciśnij \"Zapisz zmiany\" a następnie \"Zobacz podgląd " -"rachunku\" żeby zobaczyć nowy szablon." - -#: includes/register-settings.php:173 -msgid "From Name" -msgstr "Nazwa nadawcy" - -#: includes/register-settings.php:174 -msgid "" -"The name purchase receipts are said to come from. This should probably be " -"your site or shop name." -msgstr "" -"Wpisz nazwę nadawcy. To powinna być nazwa Twojej strony lub nazwa sklepu." - -#: includes/register-settings.php:179 -msgid "From Email" -msgstr "E-mail nadawcy" - -#: includes/register-settings.php:180 -msgid "" -"Email to send purchase receipts from. This will act as the \"from\" and " -"\"reply-to\" address." -msgstr "" -"Adres e-mail, z którego wysłany będzie rachunek. Pojawi się w polach OD i " -"odpowiedz-do." - -#: includes/register-settings.php:185 -msgid "Purchase Email Subject" -msgstr "Tytuł e-maila" - -#: includes/register-settings.php:186 -msgid "Enter the subject line for the purchase receipt email" -msgstr "Temat e-maila z potwierdzeniem zakupu" - -#: includes/register-settings.php:191 -msgid "Purchase Receipt" -msgstr "Potwierdzenie zakupu" - -#: includes/register-settings.php:192 -msgid "" -"Enter the email that is sent to users after completing a successful " -"purchase. HTML is accepted. Available template tags:" -msgstr "" -"Wpisz treść e-maila, który jest wysyłane do użytkowników po udanej " -"transakcji zakupu. Możesz użyć HTML. Dostępne znaczniki szablonu:" - -#: includes/register-settings.php:193 -msgid "A list of download URLs for each download purchased" -msgstr "Lista adresów URL z plikami do pobrania" - -#: includes/register-settings.php:194 -msgid "The buyer's name" -msgstr "Kupujący" - -#: includes/register-settings.php:195 -msgid "The date of the purchase" -msgstr "Data zamówienia" - -#: includes/register-settings.php:196 -msgid "The total price of the purchase" -msgstr "Łączna wartość zamówienia" - -#: includes/register-settings.php:197 -msgid "The unique ID number for this purchase receipt" -msgstr "Unikalny numer ID rachunku" - -#: includes/register-settings.php:198 -msgid "The method of payment used for this purchase" -msgstr "Forma płatności" - -#: includes/register-settings.php:199 -msgid "Your site name" -msgstr "Nazwa Twojej strony" - -#: includes/register-settings.php:208 -msgid "Disable Styles" -msgstr "Wyłącz style" - -#: includes/register-settings.php:209 -msgid "Check this to disable all included styling" -msgstr "Zaznacz, aby wyłączyć wszystkie wbudowane style" - -#: includes/register-settings.php:214 -msgid "Buttons" -msgstr "Przyciski" - -#: includes/register-settings.php:215 -msgid "Options for add to cart and purchase buttons" -msgstr "Opcje przycisków Dodaj do koszyka i Kupuję " - -#: includes/register-settings.php:220 -msgid "Default Button Style" -msgstr "Domyślny styl przycisku" - -#: includes/register-settings.php:221 -msgid "Choose the style you want to use for the buttons." -msgstr "Wybierz domyślny styl przycisków" - -#: includes/register-settings.php:227 -msgid "Default Button Color" -msgstr "Domyślny kolor przycisku" - -#: includes/register-settings.php:228 -msgid "Choose the color you want to use for the buttons." -msgstr "Wybierz domyślny kolor przycisków" - -#: includes/register-settings.php:238 -msgid "Disable Ajax" -msgstr "Wyłącz Ajax" - -#: includes/register-settings.php:239 -msgid "Check this to disable AJAX for the shopping cart." -msgstr "Zaznacz, żeby wyłączyć AJAX przy pokazywaniu koszyka." - -#: includes/register-settings.php:244 -msgid "Enable jQuery Validation" -msgstr "Włącz walidację jQuery" - -#: includes/register-settings.php:245 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "Zaznacz, aby włączyć jQuery na stronie finalizacji zamówienia." - -#: includes/register-settings.php:250 -msgid "Disable Guest Checkout" -msgstr "Wyłącz zakupy anonimowe." - -#: includes/register-settings.php:251 -msgid "Require that users be logged-in to purchase files." -msgstr "Użytkownik będzie musiał się zalogować, aby kontynuować zakupy." - -#: includes/register-settings.php:256 -msgid "Show Register / Login Form?" -msgstr "Pokazać formularz Zaloguj / Zarejestruj się?" - -#: includes/register-settings.php:257 -msgid "" -"Display the registration and login forms on the checkout page for non-logged-" -"in users." -msgstr "" -"Wyświetl formularz rejestracji i logowania na stronie finalizacji zakupów " -"dla niezalogowanych użytkowników" - -#: includes/register-settings.php:262 -msgid "Download Link Expiration" -msgstr "Termin ważności linku do pobrania" - -#: includes/register-settings.php:263 -msgid "" -"How long should download links be valid for? Default is 24 hours from the " -"time they are generated. Enter a time in hours." -msgstr "" -"Jak długo mają być ważne linki do pobrania? Domyślnie są to 24 godziny od " -"momentu ich wytworzenia. Wprowadź czas w godzinach." - -#: includes/register-settings.php:269 -msgid "Disable Redownload?" -msgstr "Wyłączyć możliwość ponownego pobrania?" - -#: includes/register-settings.php:270 -msgid "" -"Check this if you do not want to allow users to redownload items from their " -"purchase history." -msgstr "" -"Zaznacz, jeśli nie chcesz pozwolić użytkownikom na ponowne pobieranie plików " -"z ich historii zakupów." - -#: includes/register-settings.php:275 -msgid "Terms of Agreement" -msgstr "Warunki umowy" - -#: includes/register-settings.php:281 -msgid "Agree to Terms" -msgstr "Zgadzam się na te warunki" - -#: includes/register-settings.php:282 -msgid "" -"Check this to show an agree to terms on the checkout that users must agree " -"to before purchasing." -msgstr "" -"Zaznacz, aby pokazać Warunki umowy na stronie finalizacji zamówienia. " -"Użytkownicy muszą zgodzić się na nie przed zakupem." - -#: includes/register-settings.php:287 -msgid "Agree to Terms Label" -msgstr "Nagłówek Warunków umowy" - -#: includes/register-settings.php:288 -msgid "Label shown next to the agree to terms check box." -msgstr "Etykieta pokazana obok pola wyboru \"Akceptuję warunki umowy\"." - -#: includes/register-settings.php:294 -msgid "Agreement Text" -msgstr "Treść umowy" - -#: includes/register-settings.php:295 -msgid "If Agree to Terms is checked, enter the agreement terms here." -msgstr "Jeślis Warunki Umowy są włączone, wpisz warunki umowy tutaj." - -#: includes/register-settings.php:300 -msgid "Complete Purchase Text" -msgstr "Tekst \"Zakończ zamówienie\"" - -#: includes/register-settings.php:301 -msgid "The button label for completing a purchase." -msgstr "Etykieta przycisku do zakończenia zamówienia." - -#: includes/register-settings.php:306 -msgid "Add to Cart Text" -msgstr "Tekst przycisku \"Dodaj do koszyka\"" - -#: includes/register-settings.php:307 -msgid "Text shown on the Add to Cart Buttons" -msgstr "Tekst wyświetlany na przycisku Dodaj do koszyka" - -#: includes/register-settings.php:333 -msgid "General Settings" -msgstr "Ustawienia ogólne" - -#: includes/register-settings.php:359 -msgid "Payment Gateway Settings" -msgstr "Bramka płatności" - -#: includes/register-settings.php:385 -msgid "Email Settings" -msgstr "Powiadomienia" - -#: includes/register-settings.php:411 -msgid "Style Settings" -msgstr "Ustawienia stylu" - -#: includes/register-settings.php:438 -msgid "Misc Settings" -msgstr "Inne ustawienia" - -#: includes/register-settings.php:727 -msgid "Upload File" -msgstr "Wyślij plik na serwer" - -#: includes/register-settings.php:765 -msgid "Settings Updated" -msgstr "Ustawienia zostały zaktualizowane" - -#: includes/query-filters.php:42 -msgid "You do not have permission to view this file." -msgstr "Nie masz uprawnień, aby pobrać ten plik" - -#: includes/query-filters.php:42 -msgid "Error" -msgstr "Błąd" - -#: includes/cart-functions.php:405 -#, php-format -msgid "You have successfully added %s to your shopping cart." -msgstr "Dodałeś %s do koszyka" - -#: includes/cart-functions.php:406 -msgid "Checkout." -msgstr "Do kasy." - -#: includes/widgets.php:39 -msgid "Downloads Cart" -msgstr "Koszyk" - -#: includes/widgets.php:39 -msgid "Display the downloads shopping cart" -msgstr "Wyświetl koszyk" - -#: includes/widgets.php:82 includes/widgets.php:162 -msgid "Title:" -msgstr "Tytuł:" - -#: includes/widgets.php:87 -msgid "Show Quantity:" -msgstr "Pokaż ilość:" - -#: includes/widgets.php:112 -msgid "Downloads Categories / Tags" -msgstr "Kategorie/Tagi plików" - -#: includes/widgets.php:112 -msgid "Display the downloads categories or tags" -msgstr "Wyświetl kategorie lub tagi plików do pobrania" - -#: includes/widgets.php:167 -msgid "Taxonomy:" -msgstr "Taksonomia:" - -#: includes/widgets.php:169 -msgid "Categories" -msgstr "Kategorie" - -#: includes/widgets.php:170 -msgid "Tags" -msgstr "Tagi" - -#: includes/widgets.php:193 -msgid "Purchase History" -msgstr "Historia zamówień" - -#: includes/widgets.php:193 -msgid "Display a user's purchase history" -msgstr "Wyświetl historię zamówień użytkownika" - -#: includes/widgets.php:232 -msgid "No downloadable files found." -msgstr "Nie znaleziono plików do pobrania." - -#: includes/widgets.php:259 -msgid "Title" -msgstr "Tytuł" - -#: includes/widgets.php:299 -msgid "Easy Digital Downloads Sales Summary" -msgstr "Dodatki do Easy Digital Downloads" - -#: includes/widgets.php:318 -msgid "Current Month" -msgstr "Zyski miesięczne" - -#: includes/widgets.php:323 -msgid "Earnings" -msgstr "Zyski" - -#: includes/widgets.php:327 -msgid "Sales" -msgstr "Sprzedaż" - -#: includes/widgets.php:333 -msgid "Totals" -msgstr "Sumy" - -#: includes/widgets.php:338 -msgid "Total Earnings" -msgstr "Łączne zyski:" - -#: includes/widgets.php:342 -msgid "Total Sales" -msgstr "Łączna sprzedaż" - -#: includes/scripts.php:40 -msgid "Please enter a discount code" -msgstr "wpisz kod rabatowy" - -#: includes/scripts.php:41 -msgid "Discount Applied" -msgstr "Rabat został zastosowany" - -#: includes/scripts.php:42 -msgid "Please enter an email address before applying a discount code" -msgstr "Wpisz adres e-mail przed zastosowaniem kodu rabatowego" - -#: includes/scripts.php:44 -msgid "You have already added this item to your cart" -msgstr "Dodałeś już tą pozycję do koszyka" - -#: includes/scripts.php:45 -msgid "Your cart is empty" -msgstr "Twój koszyk jest pusty" - -#: includes/scripts.php:46 -msgid "Loading" -msgstr "Ładowanie" - -#: includes/scripts.php:123 -msgid "Add New Download" -msgstr "Dodaj nowy plik do pobrania" - -#: includes/scripts.php:124 -msgid "Use This File" -msgstr "Użyj tego pliku" - -#: includes/scripts.php:125 -msgid "Sorry, not available for variable priced products." -msgstr "Niestety, to nie jest dostępne dla produktów ze zmienną ceną." - -#: includes/scripts.php:126 -msgid "Are you sure you wish to delete this payment?" -msgstr "Czy na pewno chcesz usunąć tę płatność?" - -#: includes/scripts.php:127 -msgid "You must have at least one price" -msgstr "Musisz mieć co najmniej jedną cenę" - -#: includes/scripts.php:128 -msgid "You must have at least one file" -msgstr "Musisz mieć co najmniej jeden plik" - -#: includes/scripts.php:129 -msgid "You must have at least one field" -msgstr "Musisz mieć co najmniej jedno pole" - -#: includes/ajax-functions.php:102 -msgid "This discount code has been used already" -msgstr "Kod promocyjny był już używany" - -#: includes/ajax-functions.php:118 includes/process-purchase.php:239 -msgid "The discount you entered is invalid" -msgstr "Kod promocyjny jest niepoprawny" - -#: includes/login-register.php:45 -msgid "Log into Your Account" -msgstr "Zaloguj się do swojego konta" - -#: includes/login-register.php:47 includes/login-register.php:48 -#: includes/checkout-template.php:325 includes/checkout-template.php:326 -#: includes/checkout-template.php:373 -msgid "Username" -msgstr "Nazwa użytkownika" - -#: includes/login-register.php:51 includes/checkout-template.php:329 -#: includes/checkout-template.php:330 includes/checkout-template.php:377 -msgid "Password" -msgstr "Hasło" - -#: includes/login-register.php:58 includes/checkout-template.php:320 -msgid "Login" -msgstr "Zaloguj się" - -#: includes/login-register.php:61 -msgid "Lost Password" -msgstr "Zapomniane hasło" - -#: includes/login-register.php:62 -msgid "Lost Password?" -msgstr "Zapomniałeś swoje hasło?" - -#: includes/login-register.php:69 -msgid "You are already logged in" -msgstr "Jesteś już zalogowany" - -#: includes/login-register.php:92 includes/process-purchase.php:472 -msgid "The password you entered is incorrect" -msgstr "Podane hasło jest nieprawidłowe" - -#: includes/login-register.php:95 includes/process-purchase.php:491 -msgid "The username you entered does not exist" -msgstr "Nie ma takiego użytkownika" - -#: includes/misc-functions.php:185 -msgid "US Dollars ($)" -msgstr "Dolar amerykański ($)" - -#: includes/misc-functions.php:186 -msgid "Euros (€)" -msgstr "Euro (€)" - -#: includes/misc-functions.php:187 -msgid "Pounds Sterling (£)" -msgstr "Funt szterling (£)" - -#: includes/misc-functions.php:188 -msgid "Australian Dollars ($)" -msgstr "Dolar australijski ($)" - -#: includes/misc-functions.php:189 -msgid "Brazilian Real ($)" -msgstr "Real brazylijski ($)" - -#: includes/misc-functions.php:190 -msgid "Canadian Dollars ($)" -msgstr "Dolar kanadyjski ($)" - -#: includes/misc-functions.php:191 -msgid "Czech Koruna" -msgstr "Korona czeska" - -#: includes/misc-functions.php:192 -msgid "Danish Krone" -msgstr "Korona duńska" - -#: includes/misc-functions.php:193 -msgid "Hong Kong Dollar ($)" -msgstr "Dolar hongkoński ($)" - -#: includes/misc-functions.php:194 -msgid "Hungarian Forint" -msgstr "Forint" - -#: includes/misc-functions.php:195 -msgid "Israeli Shekel" -msgstr "Nowy szekel izraelski" - -#: includes/misc-functions.php:196 -msgid "Japanese Yen (¥)" -msgstr "Jen (¥)" - -#: includes/misc-functions.php:197 -msgid "Malaysian Ringgits" -msgstr "Ringgit malezyjski" - -#: includes/misc-functions.php:198 -msgid "Mexican Peso ($)" -msgstr "Peso meksykańskie ($)" - -#: includes/misc-functions.php:199 -msgid "New Zealand Dollar ($)" -msgstr "Dolar nowozelandzki ($)" - -#: includes/misc-functions.php:200 -msgid "Norwegian Krone" -msgstr "Korona norweska" - -#: includes/misc-functions.php:201 -msgid "Philippine Pesos" -msgstr "Peso filipińskie" - -#: includes/misc-functions.php:202 -msgid "Polish Zloty" -msgstr "Złoty" - -#: includes/misc-functions.php:203 -msgid "Singapore Dollar ($)" -msgstr "Dolar singapurski ($)" - -#: includes/misc-functions.php:204 -msgid "Swedish Krona" -msgstr "Korona szwedzka" - -#: includes/misc-functions.php:205 -msgid "Swiss Franc" -msgstr "Frank szwajcarski" - -#: includes/misc-functions.php:206 -msgid "Taiwan New Dollars" -msgstr "Nowy dolar tajwański" - -#: includes/misc-functions.php:207 -msgid "Thai Baht" -msgstr "Bat" - -#: includes/misc-functions.php:208 -msgid "Indian Rupee" -msgstr "Rupia indyjska" - -#: includes/misc-functions.php:209 -msgid "Turkish Lira" -msgstr "Lira turecka" - -#: includes/misc-functions.php:210 -msgid "Iranian Rial" -msgstr "Rial irański" - -#: includes/process-download.php:266 includes/process-download.php:283 -msgid "Sorry but this file does not exist." -msgstr "Przepraszamy, ten plik nie istnieje." - -#: includes/process-download.php:294 -msgid "You do not have permission to download this file" -msgstr "Nie masz uprawnień, aby pobrać ten plik" - -#: includes/process-download.php:294 -msgid "Purchase Verification Failed" -msgstr "Weryfikacja zakupu nie powiodła się." - -#: includes/checkout-template.php:101 -msgid "Personal Info" -msgstr "Zamawiający" - -#: includes/checkout-template.php:104 -msgid "Email address" -msgstr "Adres e-mail" - -#: includes/checkout-template.php:105 -msgid "Email Address" -msgstr "Adres e-mail" - -#: includes/checkout-template.php:109 includes/checkout-template.php:110 -#: includes/checkout-template.php:343 includes/checkout-template.php:344 -msgid "First Name" -msgstr "Imię" - -#: includes/checkout-template.php:113 includes/checkout-template.php:347 -msgid "Last name" -msgstr "Nazwisko" - -#: includes/checkout-template.php:114 includes/checkout-template.php:348 -msgid "Last Name" -msgstr "Nazwisko" - -#: includes/checkout-template.php:142 -msgid "Show Terms" -msgstr "Pokaż warunki umowy" - -#: includes/checkout-template.php:143 -msgid "Hide Terms" -msgstr "Ukryj warunki umowy" - -#: includes/checkout-template.php:146 -msgid "Agree to Terms?" -msgstr "Akceptujesz warunki umowy?" - -#: includes/checkout-template.php:166 -msgid "Go back" -msgstr "wróć" - -#: includes/checkout-template.php:170 -msgid "You must be logged in to complete your purchase" -msgstr "Musisz być zalogowany, abu kontynuować zamawianie" - -#: includes/checkout-template.php:199 -msgid "Credit Card Info" -msgstr "Informacja o karcie kredytowej" - -#: includes/checkout-template.php:201 -msgid "Card name" -msgstr "Nazwa karty" - -#: includes/checkout-template.php:202 -msgid "Name on the Card" -msgstr "Imię i nazwisko użytkownika" - -#: includes/checkout-template.php:205 -msgid "Card number" -msgstr "Numer karty" - -#: includes/checkout-template.php:206 -msgid "Card Number" -msgstr "Numer karty" - -#: includes/checkout-template.php:209 -msgid "Security code" -msgstr "Kod CVC" - -#: includes/checkout-template.php:210 -msgid "CVC" -msgstr "CVC" - -#: includes/checkout-template.php:216 -msgid "Month" -msgstr "Miesiąc" - -#: includes/checkout-template.php:218 -msgid "Year" -msgstr "Rok" - -#: includes/checkout-template.php:219 -msgid "Expiration (MM/YYYY)" -msgstr "Ważna do (MM/YYYY)" - -#: includes/checkout-template.php:249 -msgid "Address line 1" -msgstr "Adres" - -#: includes/checkout-template.php:250 -msgid "Billing Address" -msgstr "Adres (na rachunku)" - -#: includes/checkout-template.php:253 -msgid "Address line 2" -msgstr "Adres cd." - -#: includes/checkout-template.php:254 -msgid "Billing Address Line 2" -msgstr "Adres (na rachunku) cd." - -#: includes/checkout-template.php:257 -msgid "City" -msgstr "Miasto" - -#: includes/checkout-template.php:258 -msgid "Billing City" -msgstr "Miasto (na rachunku)" - -#: includes/checkout-template.php:269 -msgid "Billing Country" -msgstr "Kraj (na rachunku)" - -#: includes/checkout-template.php:272 -msgid "State / Province" -msgstr "województwo" - -#: includes/checkout-template.php:289 -msgid "Billing State / Province" -msgstr "województwo (na rachunku)" - -#: includes/checkout-template.php:292 -msgid "Zip / Postal code" -msgstr "Kod pocztowy" - -#: includes/checkout-template.php:293 -msgid "Billing Zip / Postal Code" -msgstr "Kod pocztowy (na rachunku)" - -#: includes/checkout-template.php:320 -msgid "Already have an account?" -msgstr "Masz już konto?" - -#: includes/checkout-template.php:322 -msgid "Create an account" -msgstr "Załóż konto" - -#: includes/checkout-template.php:322 -msgid "(optional)" -msgstr "(opcjonalnie)" - -#: includes/checkout-template.php:333 -msgid "Confirm password" -msgstr "Potwierdź hasło" - -#: includes/checkout-template.php:334 -msgid "Password Again" -msgstr "Wprowadź hasło ponownie" - -#: includes/checkout-template.php:339 includes/checkout-template.php:340 -msgid "Email" -msgstr "Adres e-mail" - -#: includes/checkout-template.php:369 -msgid "Login to your account" -msgstr "Zaloguj się aby kontynuować" - -#: includes/checkout-template.php:372 -msgid "Your username" -msgstr "Twoja nazwa użytkownika" - -#: includes/checkout-template.php:376 -msgid "Your password" -msgstr "Twoje hasło" - -#: includes/checkout-template.php:384 -msgid "Need to create an account?" -msgstr "Nie masz jeszcze konta?" - -#: includes/checkout-template.php:386 -msgid "Register" -msgstr "Zarejestruj się" - -#: includes/checkout-template.php:386 -msgid "or checkout as a guest." -msgstr "lub kontynuuj bez rejestracji." - -#: includes/checkout-template.php:415 -msgid "Choose Your Payment Method" -msgstr "Wybierz formę płatności" - -#: includes/checkout-template.php:443 -msgid "Enter discount" -msgstr "wprowadź kod promocyjny" - -#: includes/checkout-template.php:445 -msgid "Discount" -msgstr "Kod promocyjny" - -#: includes/checkout-template.php:447 -msgid "Apply Discount" -msgstr "zastosuj " - -#: includes/checkout-template.php:473 -msgid "Next" -msgstr "Dalej" - -#: includes/email-functions.php:62 -msgid "Hello" -msgstr "Witaj" - -#: includes/email-functions.php:62 -msgid "A download purchase has been made" -msgstr "zostało złożone zamówienie" - -#: includes/email-functions.php:63 -msgid "Downloads sold:" -msgstr "Zakupione pliki:" - -#: includes/email-functions.php:76 -msgid "Purchased by: " -msgstr "Kupujący:" - -#: includes/email-functions.php:77 -msgid "Amount: " -msgstr "Wartość:" - -#: includes/email-functions.php:78 -msgid "Payment Method: " -msgstr "Forma płatności:" - -#: includes/email-functions.php:79 -msgid "Thank you" -msgstr "Dziękujemy!" - -#: includes/email-functions.php:82 -msgid "New download purchase" -msgstr "Nowe zamówienie" - -#: includes/install.php:42 -msgid "Purchase Confirmation" -msgstr "Potwierdzenie zamówienia" - -#: includes/install.php:43 -msgid "Thank you for your purchase!" -msgstr "Dziękujemy za złożenie zamówienia!" - -#: includes/process-purchase.php:206 -msgid "The selected gateway is not active" -msgstr "Wybrana bramka płatności nie jest aktywna" - -#: includes/process-purchase.php:210 -msgid "No gateway has been selected" -msgstr "Nie wybrałeś żadnej bramki płatności" - -#: includes/process-purchase.php:259 -msgid "You must agree to the terms of use" -msgstr "Musisz zgodzić się na warunki użytkowania" - -#: includes/process-purchase.php:289 -msgid "Please enter a valid email address." -msgstr "Wpisz poprawny adres e-mail." - -#: includes/process-purchase.php:303 -msgid "The user information is invalid." -msgstr "Informacja o użytkowniku jest nieprawidłowa." - -#: includes/process-purchase.php:349 -msgid "Username already taken" -msgstr "Nazwa użytkownika już jest zajęta" - -#: includes/process-purchase.php:355 -msgid "Invalid username" -msgstr "Nieprawidłowa nazwa użytkownika" - -#: includes/process-purchase.php:366 -msgid "You must register or login to complete your purchase" -msgstr "Musisz się zarejestrować lub zalogować, aby sfinalizować zakup" - -#: includes/process-purchase.php:378 includes/process-purchase.php:522 -msgid "Invalid email" -msgstr "Niepoprawny adres e-mail" - -#: includes/process-purchase.php:384 -msgid "Email already used" -msgstr "Adres e-mail jest już zapisany w naszej bazie." - -#: includes/process-purchase.php:396 includes/process-purchase.php:529 -msgid "Enter an email" -msgstr "Wprowadź adres e-mail" - -#: includes/process-purchase.php:407 -msgid "Passwords don't match" -msgstr "Hasła nie są identyczne" - -#: includes/process-purchase.php:422 includes/process-purchase.php:487 -msgid "Enter a password" -msgstr "Wprowadź hasło" - -#: includes/process-purchase.php:427 -msgid "Enter the password confirmation" -msgstr "Wprowadź hasło ponownie" - -#: includes/process-purchase.php:454 -msgid "You must login or register to complete your purchase" -msgstr "Musisz się zalogować lub zarejestrować, aby sfinalizować zakup" - -#: includes/gateway-functions.php:28 -msgid "Test Payment" -msgstr "Płatność testowa" - -#: includes/post-types.php:43 includes/post-types.php:81 -msgid "Add New" -msgstr "Dodaj nową" - -#: includes/post-types.php:44 -#, php-format -msgid "Add New %1$s" -msgstr "Dodaj nowy %1$s" - -#: includes/post-types.php:45 -#, php-format -msgid "Edit %1$s" -msgstr "Edytuj %1$s" - -#: includes/post-types.php:46 -#, php-format -msgid "New %1$s" -msgstr "Nowy %1$s" - -#: includes/post-types.php:47 -#, php-format -msgid "All %2$s" -msgstr "Wszystkie %2$s" - -#: includes/post-types.php:48 -#, php-format -msgid "View %1$s" -msgstr "Zobacz %1$s" - -#: includes/post-types.php:49 -#, php-format -msgid "Search %2$s" -msgstr "Szukaj %2$s" - -#: includes/post-types.php:50 -#, php-format -msgid "No %2$s found" -msgstr "Nie znaleziono %2$s" - -#: includes/post-types.php:51 -#, php-format -msgid "No %2$s found in Trash" -msgstr "Nie znaleziono %2$s w usuniętych" - -#: includes/post-types.php:53 -#, php-format -msgid "%2$s" -msgstr "%2$s" - -#: includes/post-types.php:79 -msgctxt "post type general name" -msgid "Payments" -msgstr "Płatności" - -#: includes/post-types.php:80 -msgctxt "post type singular name" -msgid "Payment" -msgstr "Płatność" - -#: includes/post-types.php:82 -msgid "Add New Payment" -msgstr "Dodaj nową płatność" - -#: includes/post-types.php:83 -msgid "Edit Payment" -msgstr "Edytuj płatność" - -#: includes/post-types.php:84 -msgid "New Payment" -msgstr "Nowa płatność" - -#: includes/post-types.php:85 -msgid "All Payments" -msgstr "Wszystkie płatności" - -#: includes/post-types.php:86 -msgid "View Payment" -msgstr "Zobacz płatność" - -#: includes/post-types.php:87 -msgid "Search Payments" -msgstr "Szukaj płatności" - -#: includes/post-types.php:88 -msgid "No Payments found" -msgstr "Nie znaleziono płatności" - -#: includes/post-types.php:89 -msgid "No Payments found in Trash" -msgstr "Nie znaleziono płatności w usuniętych" - -#: includes/post-types.php:91 -msgid "Payment History" -msgstr "Historia płatności" - -#: includes/post-types.php:125 -msgid "Download" -msgstr "Pobierz" - -#: includes/post-types.php:126 -msgid "Downloads" -msgstr "Pliki do pobrania" - -#: includes/post-types.php:173 -msgctxt "taxonomy general name" -msgid "Categories" -msgstr "Kategorie" - -#: includes/post-types.php:174 -msgctxt "taxonomy singular name" -msgid "Category" -msgstr "Kategoria" - -#: includes/post-types.php:175 -msgid "Search Categories" -msgstr "Szukaj kategorii" - -#: includes/post-types.php:176 -msgid "All Categories" -msgstr "Wszystkie kategorie" - -#: includes/post-types.php:177 -msgid "Parent Category" -msgstr "Kategoria nadrzędna" - -#: includes/post-types.php:178 -msgid "Parent Category:" -msgstr "Kategoria nadrzędna:" - -#: includes/post-types.php:179 -msgid "Edit Category" -msgstr "Edytuj kategorię" - -#: includes/post-types.php:180 -msgid "Update Category" -msgstr "Aktualizuj kategorię" - -#: includes/post-types.php:181 -msgid "Add New Category" -msgstr "Dodaj nową kategorię" - -#: includes/post-types.php:182 -msgid "New Category Name" -msgstr "Nazwa nowej kategorii" - -#: includes/post-types.php:198 -msgctxt "taxonomy general name" -msgid "Tags" -msgstr "Tagi" - -#: includes/post-types.php:199 -msgctxt "taxonomy singular name" -msgid "Tag" -msgstr "Tag" - -#: includes/post-types.php:200 -msgid "Search Tags" -msgstr "Szukaj tagów" - -#: includes/post-types.php:201 -msgid "All Tags" -msgstr "Wszystkie tagi" - -#: includes/post-types.php:202 -msgid "Parent Tag" -msgstr "Tag nadrzędny" - -#: includes/post-types.php:203 -msgid "Parent Tag:" -msgstr "Tag nadrzędny:" - -#: includes/post-types.php:204 -msgid "Edit Tag" -msgstr "Edytuj tag" - -#: includes/post-types.php:205 -msgid "Update Tag" -msgstr "Aktualizuj tag" - -#: includes/post-types.php:206 -msgid "Add New Tag" -msgstr "Dodaj nowy tag" - -#: includes/post-types.php:207 -msgid "New Tag Name" -msgstr "Nazwa nowego tagu" - -#: includes/post-types.php:239 includes/post-types.php:240 -msgid "Download updated." -msgstr "Plik został zaktualizowany." - -#: includes/post-types.php:241 -msgid "Download published." -msgstr "Plik został opublikowany." - -#: includes/post-types.php:242 -msgid "Download saved." -msgstr "Plik został zapisany." - -#: includes/post-types.php:243 -msgid "Download submitted." -msgstr "Plik został zgłoszony." - -#: includes/shortcodes.php:194 -msgid "Purchase All Items" -msgstr "Zamów wszystkie" - -#: includes/shortcodes.php:336 -#, php-format -msgctxt "download post type name" -msgid "No %s found" -msgstr "Nie znaleziono %s " - -#: includes/gateways/paypal.php:271 -msgid "Invalid IPN" -msgstr "Błędny IPN" - -#: includes/templates/history-purchases.php:11 -msgid "Purchase ID" -msgstr "Numer zamówienia" - -#: includes/templates/history-purchases.php:12 -#: includes/admin/export-functions.php:48 -msgid "Date" -msgstr "Data" - -#: includes/templates/history-purchases.php:13 -msgid "Amount" -msgstr "Ilość" - -#: includes/templates/history-purchases.php:14 -#: includes/templates/history-downloads.php:12 -msgid "Files" -msgstr "Pliki" - -#: includes/templates/history-purchases.php:61 -msgid "You have not made any purchases" -msgstr "Nie dokonałeś żadnego zamówienia" - -#: includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "Nazwa" - -#: includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "Cena" - -#: includes/templates/checkout_cart.php:8 -msgid "Actions" -msgstr "Akcje" - -#: includes/templates/checkout_cart.php:49 -msgid "Total" -msgstr "Łącznie" - -#: includes/templates/history-downloads.php:10 -msgid "Download Name" -msgstr "Nazwa pliku do pobrania" - -#: includes/templates/history-downloads.php:68 -msgid "You have not purchased any downloads" -msgstr "Nie zamówiłeś żadnych plików" - -#: includes/admin/export-functions.php:39 -msgid "ID" -msgstr "ID" - -#: includes/admin/export-functions.php:43 -msgid "Products" -msgstr "Pliki" - -#: includes/admin/export-functions.php:44 -msgid "Discounts," -msgstr "Upusty," - -#: includes/admin/export-functions.php:45 -msgid "Amount paid" -msgstr "Zapłacono" - -#: includes/admin/export-functions.php:46 -msgid "Payment method" -msgstr "Forma płatności" - -#: includes/admin/export-functions.php:47 -msgid "Key" -msgstr "Klucz" - -#: includes/admin/export-functions.php:49 -msgid "User" -msgstr "Użytkownik" - -#: includes/admin/export-functions.php:50 -msgid "Status" -msgstr "Status" - -#: includes/admin/export-functions.php:111 -#: includes/admin/export-functions.php:119 -msgid "none" -msgstr "brak" - -#: includes/admin/export-functions.php:127 -msgid "guest" -msgstr "gość" - -#: includes/admin/export-functions.php:135 -msgid "No payments recorded yet" -msgstr "Nie zarejestrowano jeszcze płatności" - -#: includes/admin/export-functions.php:169 -msgid "Export not allowed for non-administrators." -msgstr "Eksport dostępny tylko dla administratorów" - -#: includes/admin/thickbox.php:29 includes/admin/thickbox.php:123 -#, php-format -msgid "Insert %s" -msgstr "Wstaw %s" - -#: includes/admin/thickbox.php:30 -msgid "Insert Download" -msgstr "Wstaw plik do pobrania" - -#: includes/admin/thickbox.php:65 -msgid "You must choose a download" -msgstr "Musisz wybrać plik do pobrania" - -#: includes/admin/thickbox.php:88 -#, php-format -msgid "Use the form below to insert the short code for purchasing a %s" -msgstr "" -"Użyj formularza poniżej żeby skonfigurować krótki kod dla zamówienia %s" - -#: includes/admin/thickbox.php:91 -#, php-format -msgid "Choose a %s" -msgstr "Wybierz %s" - -#: includes/admin/thickbox.php:100 -msgid "Choose a style" -msgstr "Wybierz styl" - -#: includes/admin/thickbox.php:111 -msgid "Choose a button color" -msgstr "Wybierz kolor przycisku" - -#: includes/admin/thickbox.php:120 -msgid "Link text . . ." -msgstr "Tekst linku . . ." - -#: includes/admin/thickbox.php:124 -msgid "Cancel" -msgstr "Anuluj" - -#: includes/admin/thickbox.php:126 -msgid "Button Styles" -msgstr "Style przycisków" - -#: includes/admin/admin-pages.php:27 -#: includes/admin/discounts/discount-codes.php:34 -msgid "Discount Codes" -msgstr "Kody promocyjne" - -#: includes/admin/admin-pages.php:28 -msgid "Earnings and Sales Reports" -msgstr "Raporty sprzedaży i płatności." - -#: includes/admin/admin-pages.php:28 includes/admin/reporting/reports.php:28 -msgid "Reports" -msgstr "Raporty" - -#: includes/admin/admin-pages.php:29 -msgid "Easy Digital Download Settings" -msgstr "Easy Digital Download - Ustawienia" - -#: includes/admin/admin-pages.php:29 -msgid "Settings" -msgstr "Ustawienia" - -#: includes/admin/admin-pages.php:30 -msgid "Easy Digital Download Add Ons" -msgstr "Easy Digital Download - Dodatki" - -#: includes/admin/admin-pages.php:30 -msgid "Add Ons" -msgstr "Dodatki" - -#: includes/admin/admin-notices.php:30 -msgid "Discount code updated." -msgstr "Kod został zaktualizowany" - -#: includes/admin/admin-notices.php:33 -msgid "There was a problem updating your discount code, please try again." -msgstr "" -"Wystąpił problem z aktualizacją kodu promocyjnego. Spróbuj jeszcze raz." - -#: includes/admin/admin-notices.php:36 -msgid "The payment has been deleted." -msgstr "Płatność została usunięta." - -#: includes/admin/admin-notices.php:39 -msgid "The purchase receipt has been resent." -msgstr "Rachunek został ponownie wysłany." - -#: includes/admin/admin-notices.php:44 -#, php-format -msgid "The payment history needs updated. %s" -msgstr "Historia płatności musi być zaktualizowana. %s" - -#: includes/admin/admin-notices.php:44 -msgid "Click to Upgrade" -msgstr "Kliknij aby zaktualizować." - -#: includes/admin/admin-notices.php:62 -msgid "" -"There seems to be an issue with the server. Please try again in a few " -"minutes." -msgstr "" -"Wygląda na to, że są problemy z serwerem. Proszę, spróbuj ponownie za kilka " -"minut." - -#: includes/admin/add-ons.php:69 -msgid "Add Ons for Easy Digital Downloads" -msgstr "Dodatki do Easy Digital Downloads" - -#: includes/admin/add-ons.php:70 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "Te dodatki zwiększają funkcjonalność Easy Digital Downloads." - -#: includes/admin/add-ons.php:73 -msgid "Browse All Extensions" -msgstr "Zobacz wszystkie dodatki" - -#: includes/admin/downloads/dashboard-columns.php:26 -#: includes/admin/discounts/edit-discount.php:23 -#: includes/admin/discounts/discount-codes.php:39 -#: includes/admin/discounts/discount-codes.php:52 -#: includes/admin/discounts/add-discount.php:18 -msgid "Name" -msgstr "Nazwa" - -#: includes/admin/downloads/dashboard-columns.php:29 -#: includes/admin/downloads/dashboard-columns.php:242 -#: includes/admin/downloads/metabox.php:171 -#: includes/admin/payments/payments-history.php:142 -#: includes/admin/payments/payments-history.php:158 -#: includes/admin/reporting/pdf-reports.php:66 -msgid "Price" -msgstr "Cena" - -#: includes/admin/downloads/dashboard-columns.php:32 -msgid "Short Code" -msgstr "Kod promocyjny" - -#: includes/admin/downloads/dashboard-columns.php:200 -msgid "Show all categories" -msgstr "Wszystkie kategorie" - -#: includes/admin/downloads/dashboard-columns.php:211 -msgid "Show all tags" -msgstr "Wszystkie tagi" - -#: includes/admin/downloads/dashboard-columns.php:239 -#, php-format -msgid "%s Data" -msgstr "%s data" - -#: includes/admin/downloads/metabox.php:23 -#, php-format -msgid "%1$s Configuration" -msgstr "%1s Konfiguracja" - -#: includes/admin/downloads/metabox.php:26 -msgid "Product Notes" -msgstr "Nazwa produktu" - -#: includes/admin/downloads/metabox.php:29 -#, php-format -msgid "%1$s Stats" -msgstr "%1s Statystyki" - -#: includes/admin/downloads/metabox.php:32 -msgid "Purchase Log" -msgstr "Dziennik zamówień" - -#: includes/admin/downloads/metabox.php:35 -msgid "File Download Log" -msgstr "Dziennik pobrań" - -#: includes/admin/downloads/metabox.php:145 -msgid "Pricing Options:" -msgstr "Opcje cen:" - -#: includes/admin/downloads/metabox.php:151 -msgid "Enable variable pricing" -msgstr "Włącz różne ceny" - -#: includes/admin/downloads/metabox.php:170 -#: includes/admin/downloads/metabox.php:233 -msgid "Option Name" -msgstr "Nazwa opcji" - -#: includes/admin/downloads/metabox.php:199 -msgid "Add New Price" -msgstr "Dodaj nową cenę" - -#: includes/admin/downloads/metabox.php:275 -msgid "File Downloads:" -msgstr "Pobrania pliku:" - -#: includes/admin/downloads/metabox.php:284 -#: includes/admin/downloads/metabox.php:351 -msgid "File Name" -msgstr "Nazwa pliku" - -#: includes/admin/downloads/metabox.php:285 -msgid "File URL" -msgstr "URL pliku" - -#: includes/admin/downloads/metabox.php:286 -msgid "Price Assignment" -msgstr "Przypisanie cen" - -#: includes/admin/downloads/metabox.php:314 -msgid "Add New File" -msgstr "Dodaj nowy plik" - -#: includes/admin/downloads/metabox.php:355 -msgid "http://" -msgstr "http://" - -#: includes/admin/downloads/metabox.php:358 -msgid "Upload a File" -msgstr "Wyślij plik na serwer" - -#: includes/admin/downloads/metabox.php:364 -msgid "All Prices" -msgstr "Wszystkie ceny" - -#: includes/admin/downloads/metabox.php:391 -msgid "Button Options" -msgstr "Opcje przycisku" - -#: includes/admin/downloads/metabox.php:397 -msgid "Disable the automatic output of the purchase button" -msgstr "Wyłącz automatyczne wyjście z przycisku zakupu" - -#: includes/admin/downloads/metabox.php:457 -msgid "" -"Special notes or instructions for this product. These notes will be added to " -"the purchase receipt." -msgstr "" -"Specjalne wskazówki lub informacje o tym produkcie. Te informacje będą " -"dodane do potwierdzenia zakupu." - -#: includes/admin/downloads/metabox.php:479 -msgid "Sales:" -msgstr "Sprzedaż:" - -#: includes/admin/downloads/metabox.php:485 -msgid "Earnings:" -msgstr "Zyski:" - -#: includes/admin/downloads/metabox.php:521 -msgid "Sales Log" -msgstr "Dziennik sprzedaży" - -#: includes/admin/downloads/metabox.php:523 -msgid "Each sale for this download is listed below." -msgstr "Każda sprzedaż tego pliku jest podana poniżej." - -#: includes/admin/downloads/metabox.php:537 -#: includes/admin/downloads/metabox.php:629 -msgid "Date:" -msgstr "Data:" - -#: includes/admin/downloads/metabox.php:541 -msgid "Buyer:" -msgstr "Kupujący:" - -#: includes/admin/downloads/metabox.php:545 -msgid "Purchase ID:" -msgstr "ID zamówienia:" - -#: includes/admin/downloads/metabox.php:553 -msgid "No sales yet" -msgstr "Jeszcze nie było sprzedaży" - -#: includes/admin/downloads/metabox.php:569 -#: includes/admin/downloads/metabox.php:666 -#: includes/admin/payments/payments-history.php:318 -msgid "Previous" -msgstr "poprzedni" - -#: includes/admin/downloads/metabox.php:610 -msgid "Download Log" -msgstr "Dziennik pobrań" - -#: includes/admin/downloads/metabox.php:612 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "Za każdym razem gdy plik jest pobrany, jest to odnotowane poniżej." - -#: includes/admin/downloads/metabox.php:633 -msgid "Downloaded by:" -msgstr "Pobrany przez:" - -#: includes/admin/downloads/metabox.php:637 -msgid "IP Address:" -msgstr "Adres IP:" - -#: includes/admin/downloads/metabox.php:641 -msgid "File: " -msgstr "Plik:" - -#: includes/admin/downloads/metabox.php:650 -msgid "No file downloads yet yet" -msgstr "Jeszcze nie było pobrań" - -#: includes/admin/discounts/edit-discount.php:13 -msgid "Something went wrong." -msgstr "Coś poszło nie tak." - -#: includes/admin/discounts/edit-discount.php:17 -msgid "Edit Discount" -msgstr "Edytuj upust" - -#: includes/admin/discounts/edit-discount.php:17 -#: includes/admin/payments/edit-payment.php:16 -msgid "Go Back" -msgstr "Wróć" - -#: includes/admin/discounts/edit-discount.php:27 -#: includes/admin/discounts/add-discount.php:22 -msgid "The name of this discount" -msgstr "Nazwa promocji" - -#: includes/admin/discounts/edit-discount.php:32 -#: includes/admin/discounts/discount-codes.php:40 -#: includes/admin/discounts/discount-codes.php:53 -#: includes/admin/discounts/add-discount.php:27 -msgid "Code" -msgstr "Kod" - -#: includes/admin/discounts/edit-discount.php:36 -#: includes/admin/discounts/add-discount.php:31 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "Wpisz kod promocji, np. 10PROCENT" - -#: includes/admin/discounts/edit-discount.php:41 -#: includes/admin/discounts/add-discount.php:36 -msgid "Type" -msgstr "Typ" - -#: includes/admin/discounts/edit-discount.php:45 -#: includes/admin/discounts/add-discount.php:40 -msgid "Percentage" -msgstr "procentowa" - -#: includes/admin/discounts/edit-discount.php:46 -#: includes/admin/discounts/add-discount.php:41 -msgid "Flat amount" -msgstr "stała wartość" - -#: includes/admin/discounts/edit-discount.php:48 -#: includes/admin/discounts/add-discount.php:43 -msgid "The kind of discount to apply for this discount." -msgstr "Sposób, w jaki ma być obliczony upust." - -#: includes/admin/discounts/edit-discount.php:57 -#: includes/admin/discounts/add-discount.php:52 -msgid "The amount of this discount code." -msgstr "Wartość upustu." - -#: includes/admin/discounts/edit-discount.php:62 -#: includes/admin/discounts/add-discount.php:57 -msgid "Start date" -msgstr "Data początkowa" - -#: includes/admin/discounts/edit-discount.php:66 -#: includes/admin/discounts/add-discount.php:61 -msgid "" -"Enter the start date for this discount code in the format of mm/dd/yyyy. For " -"no start date, leave blank. If entered, the discount can only be used after " -"or on this date." -msgstr "" -"Podaj datę początkową w formacie yyyy-mm-dd. Zostaw puste, jeśli nie chcesz " -"określać daty początkowej. Jeśli ją podasz. upust może być użyty dopiero po " -"tej dacie." - -#: includes/admin/discounts/edit-discount.php:71 -#: includes/admin/discounts/add-discount.php:66 -msgid "Expiration date" -msgstr "Wygasa" - -#: includes/admin/discounts/edit-discount.php:75 -#: includes/admin/discounts/add-discount.php:70 -msgid "" -"Enter the expiration date for this discount code in the format of mm/dd/" -"yyyy. For no expiration, leave blank" -msgstr "" -"Podaj datę ważności w formacie yyyy-mm-dd. Zostaw puste, jeśli nie chcesz " -"określać daty ważności. " - -#: includes/admin/discounts/edit-discount.php:80 -#: includes/admin/discounts/discount-codes.php:43 -#: includes/admin/discounts/discount-codes.php:56 -#: includes/admin/discounts/add-discount.php:84 -msgid "Max Uses" -msgstr "Maksymalna ilość użyć" - -#: includes/admin/discounts/edit-discount.php:84 -#: includes/admin/discounts/add-discount.php:88 -msgid "" -"The maximum number of times this discount can be used. Leave blank for " -"unlimited." -msgstr "" -"Maksymalna ilość kodów. Zostaw puste jeśli nie chcesz określać ilości " -"maksymalnej." - -#: includes/admin/discounts/edit-discount.php:89 -#: includes/admin/discounts/add-discount.php:75 -msgid "Minimum Amount" -msgstr "Minimalna ilość" - -#: includes/admin/discounts/edit-discount.php:93 -#: includes/admin/discounts/add-discount.php:79 -msgid "" -"The minimum amount that must be purchased before this discount can be used. " -"Leave blank for no minimum." -msgstr "" -"Ilość minimalna, która musi zostać zamówiona zanim kod będzie mógł być " -"użyty. Zostaw puste jeśli nie chcesz określać ilości minimalnej." - -#: includes/admin/discounts/edit-discount.php:102 -msgid "Active" -msgstr "Aktywny" - -#: includes/admin/discounts/edit-discount.php:103 -msgid "Inactive" -msgstr "Nieaktywny" - -#: includes/admin/discounts/edit-discount.php:105 -msgid "The status of this discount code." -msgstr "Status kodu promocyjnego" - -#: includes/admin/discounts/edit-discount.php:115 -msgid "Update Discount Code" -msgstr "Aktualizuj kod promocyjny" - -#: includes/admin/discounts/discount-codes.php:42 -#: includes/admin/discounts/discount-codes.php:55 -msgid "Uses" -msgstr "Ilość użyć" - -#: includes/admin/discounts/discount-codes.php:44 -#: includes/admin/discounts/discount-codes.php:57 -msgid "Start Date" -msgstr "Data początkowa" - -#: includes/admin/discounts/discount-codes.php:45 -#: includes/admin/discounts/discount-codes.php:58 -msgid "Expiration" -msgstr "Wygasa" - -#: includes/admin/discounts/discount-codes.php:82 -#: includes/admin/discounts/discount-codes.php:84 -msgid "unlimited" -msgstr "bez limitu" - -#: includes/admin/discounts/discount-codes.php:93 -msgid "No start date" -msgstr "Brak daty początkowej" - -#: includes/admin/discounts/discount-codes.php:100 -msgid "Expired" -msgstr "Wygasa" - -#: includes/admin/discounts/discount-codes.php:102 -msgid "no expiration" -msgstr "nie wygasa nigdy" - -#: includes/admin/discounts/discount-codes.php:108 -#: includes/admin/payments/payments-history.php:183 -msgid "Edit" -msgstr "Edytuj" - -#: includes/admin/discounts/discount-codes.php:110 -msgid "Deactivate" -msgstr "Wyłącz" - -#: includes/admin/discounts/discount-codes.php:112 -msgid "Activate" -msgstr "Włącz" - -#: includes/admin/discounts/discount-codes.php:114 -#: includes/admin/payments/payments-history.php:185 -msgid "Delete" -msgstr "Usuń" - -#: includes/admin/discounts/discount-codes.php:119 -msgid "No discount codes have been created." -msgstr "Nie utworzyłeś kodu promocyjnego." - -#: includes/admin/discounts/add-discount.php:12 -msgid "Add New Discount" -msgstr "Dodaj nowy upust" - -#: includes/admin/discounts/add-discount.php:96 -msgid "Add Discount Code" -msgstr "Dodaj kod promocyjny" - -#: includes/admin/payments/payments-history.php:90 -msgid "All" -msgstr "Wszystkie" - -#: includes/admin/payments/payments-history.php:95 -msgid "Completed" -msgstr "Zakończone" - -#: includes/admin/payments/payments-history.php:106 -msgid "Export" -msgstr "Eksportuj" - -#: includes/admin/payments/payments-history.php:110 -msgid "Payment mode" -msgstr "Sposób płatności" - -#: includes/admin/payments/payments-history.php:112 -msgid "Live" -msgstr "tryb rzeczywisty" - -#: includes/admin/payments/payments-history.php:113 -msgid "Test" -msgstr "tryb testowy" - -#: includes/admin/payments/payments-history.php:123 -msgid "Payments per page" -msgstr "Płatności na stronie" - -#: includes/admin/payments/payments-history.php:125 -msgid "Show" -msgstr "Pokaż" - -#: includes/admin/payments/payments-history.php:131 -msgid "Showing payments for: " -msgstr "Pokaż płatności dla:" - -#: includes/admin/payments/payments-history.php:131 -msgid "clear" -msgstr "wyczyść" - -#: includes/admin/payments/payments-history.php:184 -msgid "Resend Purchase Receipt" -msgstr "Wyślij ponownie rachunek" - -#: includes/admin/payments/payments-history.php:197 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "Szczegóły zamówienia dla płatności #%s" - -#: includes/admin/payments/payments-history.php:197 -msgid "View Order Details" -msgstr "Pokaż szczegóły zamówienia" - -#: includes/admin/payments/payments-history.php:205 -msgid "Purchased File" -msgstr "Zakupiony plik" - -#: includes/admin/payments/payments-history.php:205 -msgid "Purchased Files" -msgstr "Zakupione pliki" - -#: includes/admin/payments/payments-history.php:249 -msgid "Date and Time:" -msgstr "Data i godzina" - -#: includes/admin/payments/payments-history.php:250 -msgid "Discount used:" -msgstr "Kod promocji:" - -#: includes/admin/payments/payments-history.php:251 -msgid "Total:" -msgstr "Łącznie:" - -#: includes/admin/payments/payments-history.php:254 -msgid "Buyer's Personal Details:" -msgstr "Informacje o kupującym:" - -#: includes/admin/payments/payments-history.php:256 -msgid "Name:" -msgstr "Imię:" - -#: includes/admin/payments/payments-history.php:257 -msgid "Email:" -msgstr "E-mail:" - -#: includes/admin/payments/payments-history.php:266 -msgid "Payment Method:" -msgstr "Forma płatności:" - -#: includes/admin/payments/payments-history.php:271 -msgid "Purchase Key" -msgstr "Klucz zamówienia" - -#: includes/admin/payments/payments-history.php:274 -#: includes/admin/payments/edit-payment.php:92 -msgid "Close" -msgstr "Zamknij" - -#: includes/admin/payments/payments-history.php:304 -msgid "Total Earnings:" -msgstr "Łączne zyski:" - -#: includes/admin/payments/edit-payment.php:22 -msgid "Buyer's Email" -msgstr "E-mail kupującego" - -#: includes/admin/payments/edit-payment.php:26 -msgid "If needed, you can update the buyer's email here." -msgstr "Jeżeli potrzebujesz, możesz zaktualizować adres e-mail kupującego" - -#: includes/admin/payments/edit-payment.php:31 -msgid "Downloads Purchased" -msgstr "Zamówione pliki" - -#: includes/admin/payments/edit-payment.php:43 -#, php-format -msgid "Add download to purchase #%s" -msgstr "Dodaj plik do zamówienia #%s" - -#: includes/admin/payments/edit-payment.php:43 -msgid "Add download to purchase" -msgstr "Dodaj plik do zamówienia" - -#: includes/admin/payments/edit-payment.php:48 -msgid "Payment Status" -msgstr "Status płatności" - -#: includes/admin/payments/edit-payment.php:64 -msgid "Send Purchase Receipt" -msgstr "Wyślij rachunek" - -#: includes/admin/payments/edit-payment.php:68 -msgid "" -"Check this box to send the purchase receipt, including all download links." -msgstr "Zaznacz, aby wysłać rachunek wraz ze wszystkimi linkami do pobrania" - -#: includes/admin/payments/edit-payment.php:78 -msgid "Update Payment" -msgstr "Odświerz informację o płatności" - -#: includes/admin/payments/edit-payment.php:91 -msgid "Add Selected Downloads" -msgstr "Dodaj zaznaczone pliki" - -#: includes/admin/reporting/reports.php:41 -msgid "Download Sales and Earnings PDF Report for all Products" -msgstr "Pobierz raport zysków i sprzedaży w PDF" - -#: includes/admin/reporting/reports.php:42 -msgid "Download a CSV Customers List" -msgstr "Pobierz listę klientów jako plik CSV" - -#: includes/admin/reporting/reports.php:44 -msgid "" -"Please Note: Transactions created while in test mode are not included on " -"this page or in the PDF reports." -msgstr "" -"Uwaga: transakcje utworzone w czasie testowania nie są zawarte na tej " -"stronie i w raporcie PDF." - -#: includes/admin/reporting/pdf-reports.php:36 -msgid "to" -msgstr "do" - -#: includes/admin/reporting/pdf-reports.php:41 -#: includes/admin/reporting/pdf-reports.php:52 -msgid "Sales and earnings reports for the current year for all products" -msgstr "Raporty sprzedaży i zysków za bieżący rok dla wszystkich produktów" - -#: includes/admin/reporting/pdf-reports.php:42 -#: includes/admin/reporting/pdf-reports.php:43 -msgid "Easy Digital Downloads" -msgstr "Easy Digital Downloads" - -#: includes/admin/reporting/pdf-reports.php:57 -msgid "Date Range: " -msgstr "Zakres dat:" - -#: includes/admin/reporting/pdf-reports.php:61 -msgid "Table View" -msgstr "Zobacz tabelę" - -#: includes/admin/reporting/pdf-reports.php:65 -msgid "Product Name" -msgstr "Nazwa produktu" - -#: includes/admin/reporting/pdf-reports.php:69 -msgid "Number of Sales" -msgstr "Liczba sprzedanych" - -#: includes/admin/reporting/pdf-reports.php:70 -msgid "Earnings to Date" -msgstr "Zysk na bieżąco" - -#: includes/admin/reporting/pdf-reports.php:112 -msgid "No Downloads found." -msgstr "Nie znaleziono plików" - -#: includes/admin/reporting/pdf-reports.php:119 -msgid "Graph View" -msgstr "Zobacz wykres" - -#: includes/admin/reporting/pdf-reports.php:212 -msgid "Sales and Earnings by Month for all Products" -msgstr "Sprzedaż i zarobki w miesiącu dla wszystkich produków" - -#: includes/admin/reporting/pdf-reports.php:226 -msgid "Jan" -msgstr "Styczeń" - -#: includes/admin/reporting/pdf-reports.php:227 -msgid "Feb" -msgstr "Luty" - -#: includes/admin/reporting/pdf-reports.php:228 -msgid "Mar" -msgstr "Marzec" - -#: includes/admin/reporting/pdf-reports.php:229 -msgid "Apr" -msgstr "Kwiecień" - -#: includes/admin/reporting/pdf-reports.php:230 -msgid "May" -msgstr "Maj" - -#: includes/admin/reporting/pdf-reports.php:231 -msgid "June" -msgstr "Czerwiec" - -#: includes/admin/reporting/pdf-reports.php:232 -msgid "July" -msgstr "Lipiec" - -#: includes/admin/reporting/pdf-reports.php:233 -msgid "Aug" -msgstr "Sierpień" - -#: includes/admin/reporting/pdf-reports.php:234 -msgid "Sept" -msgstr "Wrzesień" - -#: includes/admin/reporting/pdf-reports.php:235 -msgid "Oct" -msgstr "Pażdziernik" - -#: includes/admin/reporting/pdf-reports.php:236 -msgid "Nov" -msgstr "Listopad" - -#: includes/admin/reporting/pdf-reports.php:237 -msgid "Dec" -msgstr "Grudzień" - -#: includes/admin/reporting/graphing.php:42 -#, php-format -msgid "%s Performance in Sales" -msgstr "%s wyników sprzedaży" - -#: includes/admin/reporting/graphing.php:88 -#, php-format -msgid "%s Performance in Earnings" -msgstr "%s wyników zysków" - -#: includes/admin/reporting/graphing.php:136 -msgid "Earnings per month" -msgstr "Zyski miesięczne" - -#: includes/admin/reporting/graphing.php:168 -msgid "Day" -msgstr "Dzień" - -#: includes/admin/reporting/graphing.php:189 -#, php-format -msgid "Earnings per day for last %s days" -msgstr "Zyski / dzień w ciągu ostatnich %s dni" - -#: includes/admin/reporting/graphing.php:236 -msgid "Sales per month" -msgstr "Sprzedaż miesięczna" - -#: includes/admin/settings/settings.php:33 -msgid "General" -msgstr "Ustawienia ogólne" - -#: includes/admin/settings/settings.php:35 -msgid "Emails" -msgstr "E-maile" - -#: includes/admin/settings/settings.php:36 -msgid "Styles" -msgstr "Style" - -#: includes/admin/settings/settings.php:37 -msgid "Misc" -msgstr "Różne" diff --git a/languages/edd-pt_PT.mo b/languages/edd-pt_PT.mo deleted file mode 100755 index fc15e909a35..00000000000 Binary files a/languages/edd-pt_PT.mo and /dev/null differ diff --git a/languages/edd-pt_PT.po b/languages/edd-pt_PT.po deleted file mode 100755 index 0f87cd10a02..00000000000 --- a/languages/edd-pt_PT.po +++ /dev/null @@ -1,1354 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-04-07 16:33-0600\n" -"PO-Revision-Date: 2012-04-17 22:12-0000\n" -"Last-Translator: \n" -"Language-Team: MisterWho \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_n;_x\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-Language: Portuguese\n" -"X-Poedit-Country: PORTUGAL\n" -"X-Poedit-SearchPath-0: ..\n" -"X-Poedit-SearchPath-1: ../includes\n" -"X-Poedit-SearchPath-2: ../includes/admin-pages\n" -"X-Poedit-SearchPath-3: ../includes/admin-pages/forms\n" -"X-Poedit-SearchPath-4: ../includes/gateways\n" -"X-Poedit-SearchPath-5: .\n" - -#: ../includes/admin-notices.php:5 -msgid "Discount code updated." -msgstr "Código promocional actualizado." - -#: ../includes/admin-notices.php:8 -msgid "There was a problem updating your discount code, please try again." -msgstr "Ocorreu um erro ao actualizar o seu código promocional, por favor tente de novo." - -#: ../includes/admin-pages.php:6 -msgid "Payment History" -msgstr "Histórico de Pagamentos" - -#: ../includes/admin-pages.php:7 -msgid "Discount Codes" -msgstr "Códigos Promocionais" - -#: ../includes/admin-pages.php:8 -msgid "Earnings and Sales Reports" -msgstr "Relatórios de Vendas e Lucros" - -#: ../includes/admin-pages.php:8 -msgid "Reports" -msgstr "Relatórios" - -#: ../includes/admin-pages.php:9 -msgid "Easy Digital Download Settings" -msgstr "Configurações do Easy Digital Download" - -#: ../includes/admin-pages.php:9 -msgid "Settings" -msgstr "Configurações" - -#: ../includes/ajax-functions.php:52 -msgid "The discount you entered is invalid" -msgstr "O código promocional que indicou é inválido" - -#: ../includes/cart-functions.php:149 -#: ../includes/cart-template.php:61 -msgid "Your cart is empty" -msgstr "O seu carrinho está vazio" - -#: ../includes/cart-template.php:10 -msgid "Item Name" -msgstr "Nome do Artigo" - -#: ../includes/cart-template.php:11 -msgid "Item Price" -msgstr "Preço do Artigo" - -#: ../includes/cart-template.php:12 -msgid "Actions" -msgstr "Ações" - -#: ../includes/cart-template.php:24 -#: ../includes/cart-template.php:78 -msgid "remove" -msgstr "remover" - -#: ../includes/cart-template.php:30 -msgid "Your shopping cart is empty" -msgstr "O seu carrinho de compras está vazio" - -#: ../includes/cart-template.php:37 -msgid "Total" -msgstr "Total" - -#: ../includes/cart-template.php:59 -#: ../includes/cart-template.php:62 -msgid "Checkout" -msgstr "Pagar" - -#: ../includes/checkout-template.php:44 -msgid "Choose Your Payment Method" -msgstr "Escolha o Seu Método de Pagamento" - -#: ../includes/checkout-template.php:56 -msgid "Next" -msgstr "Seguinte" - -#: ../includes/checkout-template.php:77 -msgid "Email Address" -msgstr "Endereço de Email" - -#: ../includes/checkout-template.php:81 -msgid "First Name" -msgstr "Primeiro Nome" - -#: ../includes/checkout-template.php:85 -msgid "Last Name" -msgstr "Último Nome" - -#: ../includes/checkout-template.php:94 -msgid "Discount" -msgstr "Desconto" - -#: ../includes/checkout-template.php:96 -msgid "Apply Discount" -msgstr "Aplicar Desconto" - -#: ../includes/checkout-template.php:118 -#: ../includes/dashboard-columns.php:41 -msgid "Purchase" -msgstr "Comprar" - -#: ../includes/checkout-template.php:122 -msgid "Go back" -msgstr "Regressar" - -#: ../includes/checkout-template.php:144 -msgid "Name on the Card" -msgstr "Nome no Cartão" - -#: ../includes/checkout-template.php:148 -msgid "Card Number" -msgstr "Número do Cartão" - -#: ../includes/checkout-template.php:152 -msgid "CVC" -msgstr "CVC" - -#: ../includes/checkout-template.php:159 -msgid "Expiration (MM/YYYY)" -msgstr "Válido até (MM/YYYY)" - -#: ../includes/checkout-template.php:179 -msgid "Billing Address" -msgstr "Morada de Faturação" - -#: ../includes/checkout-template.php:183 -msgid "Billing Address Line 2" -msgstr "Morada de Faturação Linha 2" - -#: ../includes/checkout-template.php:187 -msgid "Billing City" -msgstr "Cidade de Faturação" - -#: ../includes/checkout-template.php:191 -msgid "Billing State / Province" -msgstr "Estado/Província de Faturação" - -#: ../includes/checkout-template.php:195 -msgid "Billing Zip / Postal Code" -msgstr "Código Postal de Faturação" - -#: ../includes/checkout-template.php:206 -msgid "Create an account" -msgstr "Criar uma conta" - -#: ../includes/checkout-template.php:208 -#: ../includes/checkout-template.php:209 -#: ../includes/checkout-template.php:237 -msgid "Username" -msgstr "Nome de utilizador" - -#: ../includes/checkout-template.php:212 -msgid "email@domain.com" -msgstr "email@domínio.com" - -#: ../includes/checkout-template.php:213 -msgid "Email" -msgstr "Email" - -#: ../includes/checkout-template.php:217 -#: ../includes/checkout-template.php:241 -msgid "Password" -msgstr "Senha" - -#: ../includes/checkout-template.php:221 -msgid "Password Again" -msgstr "Repetir senha" - -#: ../includes/checkout-template.php:223 -msgid "Already have an account?" -msgstr "Já tem uma conta?" - -#: ../includes/checkout-template.php:223 -msgid "Login" -msgstr "Entrar" - -#: ../includes/checkout-template.php:234 -msgid "Login to your account" -msgstr "Entre na sua conta" - -#: ../includes/checkout-template.php:236 -msgid "Your username" -msgstr "O seu nome de utilizador" - -#: ../includes/checkout-template.php:240 -msgid "Your password" -msgstr "A sua senha" - -#: ../includes/checkout-template.php:244 -msgid "Need to create an account?" -msgstr "Necessita de criar uma conta?" - -#: ../includes/checkout-template.php:244 -msgid "Register" -msgstr "Registar" - -#: ../includes/dashboard-columns.php:7 -msgid "Name" -msgstr "Nome" - -#: ../includes/dashboard-columns.php:8 -msgid "Categories" -msgstr "Categorias" - -#: ../includes/dashboard-columns.php:9 -msgid "Tags" -msgstr "Etiquetas" - -#: ../includes/dashboard-columns.php:10 -#: ../includes/graphing.php:14 -msgid "Sales" -msgstr "Vendas" - -#: ../includes/dashboard-columns.php:11 -#: ../includes/graphing.php:52 -#: ../includes/graphing.php:87 -msgid "Earnings" -msgstr "Lucros" - -#: ../includes/dashboard-columns.php:12 -msgid "Short Code" -msgstr "Short Code" - -#: ../includes/dashboard-columns.php:13 -msgid "Date" -msgstr "Data" - -#: ../includes/dashboard-columns.php:106 -msgid "Show all categories" -msgstr "Exibir todas as categorias" - -#: ../includes/dashboard-columns.php:117 -msgid "Show all tags" -msgstr "Exibir todas as etiquetas" - -#: ../includes/email-functions.php:23 -msgid "Hello" -msgstr "Olá" - -#: ../includes/email-functions.php:23 -msgid "A download purchase has been made" -msgstr "Um compra foi efectuada" - -#: ../includes/email-functions.php:24 -msgid "Downloads sold:" -msgstr "Downloads vendidos:" - -#: ../includes/email-functions.php:32 -msgid "Amount: " -msgstr "Quantia:" - -#: ../includes/email-functions.php:33 -msgid "Thank you" -msgstr "Obrigado" - -#: ../includes/email-functions.php:35 -msgid "New download purchase" -msgstr "Nova compra de download" - -#: ../includes/error-tracking.php:13 -msgid "Error" -msgstr "Erro" - -#: ../includes/gateway-functions.php:9 -msgid "Manual Payment" -msgstr "Pagamento Manual" - -#: ../includes/graphing.php:13 -#: ../includes/graphing.php:51 -msgid "Download" -msgstr "Download" - -#: ../includes/graphing.php:24 -msgid "Downloads Performance in Sales" -msgstr "" - -#: ../includes/graphing.php:62 -msgid "Downloads Performance in Earnings" -msgstr "" - -#: ../includes/graphing.php:86 -msgid "Month" -msgstr "Mês" - -#: ../includes/graphing.php:101 -msgid "Earnings per month" -msgstr "Lucros por mês" - -#: ../includes/metabox.php:11 -msgid "Price" -msgstr "Preço" - -#: ../includes/metabox.php:12 -msgid "Enter the download price" -msgstr "Introduza o preço do download" - -#: ../includes/metabox.php:19 -msgid "Download Files" -msgstr "Download de ficheiros" - -#: ../includes/metabox.php:20 -msgid "Upload the downloadable files." -msgstr "Enviar ficheiros de download" - -#: ../includes/metabox.php:27 -msgid "Show Purchase Link on Details Page" -msgstr "Exibir Ligação de Compra na Página de Detalhes" - -#: ../includes/metabox.php:28 -msgid "Check this to automatically append the purchase link to download detail pages. If unchecked, you must enter the short code manually." -msgstr "" - -#: ../includes/metabox.php:35 -msgid "Show Purchase Link on Archive Pages" -msgstr "Exibir Ligação de Compra nas Páginas Arquivadas" - -#: ../includes/metabox.php:36 -msgid "Check this to automatically append the purchase link to download excerpts." -msgstr "" - -#: ../includes/metabox.php:43 -msgid "Purchase Text" -msgstr "Texto da Compra" - -#: ../includes/metabox.php:44 -msgid "Add the text you would like displayed for the purchase text" -msgstr "" - -#: ../includes/metabox.php:51 -msgid "Link Style" -msgstr "Estilo da Ligação" - -#: ../includes/metabox.php:52 -#: ../includes/metabox.php:60 -msgid "Choose the style of the purchase link" -msgstr "Escolha o estilo da ligação de compra" - -#: ../includes/metabox.php:59 -msgid "Button Color" -msgstr "Côr do Botão" - -#: ../includes/metabox.php:74 -msgid "Download Stats" -msgstr "Estatísticas dos Downloads" - -#: ../includes/metabox.php:138 -#: ../includes/metabox.php:149 -msgid "file name" -msgstr "nome do ficheiro" - -#: ../includes/metabox.php:139 -#: ../includes/metabox.php:150 -msgid "file url" -msgstr "url do ficheiro" - -#: ../includes/metabox.php:154 -#: ../includes/post-types.php:8 -#: ../includes/post-types.php:41 -msgid "Add New" -msgstr "Adicionar Novo" - -#: ../includes/metabox.php:164 -msgid "Short Code for this Download" -msgstr "Código para este Download" - -#: ../includes/metabox.php:222 -msgid "Sales:" -msgstr "Vendas:" - -#: ../includes/metabox.php:228 -msgid "Earnings:" -msgstr "Lucros:" - -#: ../includes/misc-functions.php:24 -msgid "US Dollars ($)" -msgstr "Dolares Americanos ($)" - -#: ../includes/misc-functions.php:25 -msgid "Euros (€)" -msgstr "Euros (€)" - -#: ../includes/misc-functions.php:26 -msgid "Pounds Sterling (£)" -msgstr "Libras Estrelinas (£)" - -#: ../includes/misc-functions.php:27 -msgid "Australian Dollars ($)" -msgstr "Dolares Australianos ($)" - -#: ../includes/misc-functions.php:28 -msgid "Brazilian Real ($)" -msgstr "Real Brasileiro ($)" - -#: ../includes/misc-functions.php:29 -msgid "Canadian Dollars ($)" -msgstr "Dolars Canadianos ($)" - -#: ../includes/misc-functions.php:30 -msgid "Czech Koruna" -msgstr "Coroa da República Checa" - -#: ../includes/misc-functions.php:31 -msgid "Danish Krone" -msgstr "Coroa Dinamarquesa" - -#: ../includes/misc-functions.php:32 -msgid "Hong Kong Dollar ($)" -msgstr "Dólar de Hong Kong ($)" - -#: ../includes/misc-functions.php:33 -msgid "Hungarian Forint" -msgstr "Florim Húngaro" - -#: ../includes/misc-functions.php:34 -msgid "Israeli Shekel" -msgstr "Sheqel Israelense" - -#: ../includes/misc-functions.php:35 -msgid "Japanese Yen (¥)" -msgstr "Yen Japonês (¥)" - -#: ../includes/misc-functions.php:36 -msgid "Malaysian Ringgits" -msgstr "Ringgit Malásio" - -#: ../includes/misc-functions.php:37 -msgid "Mexican Peso ($)" -msgstr "Peso Mexicano ($)" - -#: ../includes/misc-functions.php:38 -msgid "New Zealand Dollar ($)" -msgstr "Dólar da Nova Zelândia ($)" - -#: ../includes/misc-functions.php:39 -msgid "Norwegian Krone" -msgstr "Coroa Norueguesa" - -#: ../includes/misc-functions.php:40 -msgid "Philippine Pesos" -msgstr "Pesos Filipinos" - -#: ../includes/misc-functions.php:41 -msgid "Polish Zloty" -msgstr "Zloty Polaco" - -#: ../includes/misc-functions.php:42 -msgid "Singapore Dollar ($)" -msgstr "Dólar de Singapura ($)" - -#: ../includes/misc-functions.php:43 -msgid "Swedish Krona" -msgstr "Coroa Sueca" - -#: ../includes/misc-functions.php:44 -msgid "Swiss Franc" -msgstr "Franco Suíço" - -#: ../includes/misc-functions.php:45 -msgid "Taiwan New Dollars" -msgstr "Novo Dólar Tailandês" - -#: ../includes/misc-functions.php:46 -msgid "Thai Baht" -msgstr "Baht Tailandês" - -#: ../includes/payment-functions.php:126 -msgid "pending" -msgstr "pendente" - -#: ../includes/payment-functions.php:129 -msgid "complete" -msgstr "completo" - -#: ../includes/post-types.php:6 -#: ../includes/post-types.php:18 -msgid "Downloads" -msgstr "Downloads" - -#: ../includes/post-types.php:9 -msgid "Add New Download" -msgstr "Adicionar Novo Download" - -#: ../includes/post-types.php:10 -msgid "Edit Download" -msgstr "Editar Download" - -#: ../includes/post-types.php:11 -msgid "New Download" -msgstr "Novo Download" - -#: ../includes/post-types.php:12 -msgid "All Downloads" -msgstr "Todos os Downloads" - -#: ../includes/post-types.php:13 -msgid "View Download" -msgstr "Visualizar Download" - -#: ../includes/post-types.php:14 -msgid "Search Downloads" -msgstr "Pesquisar Downloads" - -#: ../includes/post-types.php:15 -msgid "No Downloads found" -msgstr "Não foram encontrados Downloads" - -#: ../includes/post-types.php:16 -msgid "No Downloads found in Trash" -msgstr "Não foram encontrados Downloads no Lixo" - -#: ../includes/post-types.php:39 -msgid "Payments" -msgstr "Pagamentos" - -#: ../includes/post-types.php:40 -msgid "Payment" -msgstr "Pagamento" - -#: ../includes/post-types.php:42 -msgid "Add New Payment" -msgstr "Adicionar Novo Pagamento" - -#: ../includes/post-types.php:43 -msgid "Edit Payment" -msgstr "Editar Pagamento" - -#: ../includes/post-types.php:44 -msgid "New Payment" -msgstr "Novo Pagamento" - -#: ../includes/post-types.php:45 -msgid "All Payments" -msgstr "Todos os Pagamentos" - -#: ../includes/post-types.php:46 -msgid "View Payment" -msgstr "Visualizar Pagamento" - -#: ../includes/post-types.php:47 -msgid "Search Payments" -msgstr "Pesquisar Pagamentos" - -#: ../includes/post-types.php:48 -msgid "No Payments found" -msgstr "Não foram encontrados Pagamentos" - -#: ../includes/post-types.php:49 -msgid "No Payments found in Trash" -msgstr "Não foram encontrados Pagamentos no Lixo" - -#: ../includes/post-types.php:77 -msgid "Category" -msgstr "Categoria" - -#: ../includes/post-types.php:78 -msgid "Search Categories" -msgstr "Pesquisar Categorias" - -#: ../includes/post-types.php:79 -msgid "All Categories" -msgstr "Todas as Categorias" - -#: ../includes/post-types.php:80 -msgid "Parent Category" -msgstr "Categoria Pai" - -#: ../includes/post-types.php:81 -msgid "Parent Category:" -msgstr "Categoria Pai:" - -#: ../includes/post-types.php:82 -msgid "Edit Category" -msgstr "Editar Categoria" - -#: ../includes/post-types.php:83 -msgid "Update Category" -msgstr "Actualizar Categoria" - -#: ../includes/post-types.php:84 -msgid "Add New Category" -msgstr "Adicionar Nova Categoria" - -#: ../includes/post-types.php:85 -msgid "New Category Name" -msgstr "Nome da Nova Categoria" - -#: ../includes/post-types.php:99 -msgid "Tag" -msgstr "Etiqueta" - -#: ../includes/post-types.php:100 -msgid "Search Tags" -msgstr "Pesquisar Etiquetas" - -#: ../includes/post-types.php:101 -msgid "All Tags" -msgstr "Todas as Etiquetas" - -#: ../includes/post-types.php:102 -msgid "Parent Tag" -msgstr "Etiqueta Mãe" - -#: ../includes/post-types.php:103 -msgid "Parent Tag:" -msgstr "Etiqueta Mãe:" - -#: ../includes/post-types.php:104 -msgid "Edit Tag" -msgstr "Editar Etiqueta" - -#: ../includes/post-types.php:105 -msgid "Update Tag" -msgstr "Actualizar Etiqueta" - -#: ../includes/post-types.php:106 -msgid "Add New Tag" -msgstr "Adicionar Nova Etiqueta" - -#: ../includes/post-types.php:107 -msgid "New Tag Name" -msgstr "Novo Nome de Etiqueta" - -#: ../includes/post-types.php:125 -#: ../includes/post-types.php:126 -msgid "Download updated." -msgstr "Download actualizado." - -#: ../includes/post-types.php:127 -msgid "Download published." -msgstr "Download publicado." - -#: ../includes/post-types.php:128 -msgid "Download saved." -msgstr "Download gravado." - -#: ../includes/post-types.php:129 -msgid "Download submitted." -msgstr "" - -#: ../includes/process-download.php:56 -msgid "You do not have permission to download this file" -msgstr "" - -#: ../includes/process-download.php:56 -msgid "Purchase Verification Failed" -msgstr "" - -#: ../includes/process-purchase.php:28 -msgid "Username already taken" -msgstr "" - -#: ../includes/process-purchase.php:32 -msgid "Invalid username" -msgstr "Nome de Utilizador Inválido" - -#: ../includes/process-purchase.php:36 -msgid "Enter a username" -msgstr "" - -#: ../includes/process-purchase.php:40 -msgid "Invalid email" -msgstr "" - -#: ../includes/process-purchase.php:44 -msgid "Email already used" -msgstr "" - -#: ../includes/process-purchase.php:48 -msgid "Enter a password" -msgstr "" - -#: ../includes/process-purchase.php:52 -msgid "Passwords don't match" -msgstr "" - -#: ../includes/process-purchase.php:67 -msgid "The password you entered is incorrect" -msgstr "" - -#: ../includes/process-purchase.php:70 -msgid "The username you entered does not exist" -msgstr "" - -#: ../includes/process-purchase.php:74 -msgid "Something has gone wrong, please try again" -msgstr "" - -#: ../includes/register-settings.php:23 -msgid "Use this plugin in test mode" -msgstr "" - -#: ../includes/register-settings.php:24 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "" - -#: ../includes/register-settings.php:29 -msgid "Purchase Page" -msgstr "" - -#: ../includes/register-settings.php:30 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "" - -#: ../includes/register-settings.php:36 -msgid "Success Page" -msgstr "" - -#: ../includes/register-settings.php:37 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "" - -#: ../includes/register-settings.php:43 -msgid "Currency Settings" -msgstr "" - -#: ../includes/register-settings.php:44 -msgid "Configure the currency options" -msgstr "" - -#: ../includes/register-settings.php:49 -msgid "Currency" -msgstr "" - -#: ../includes/register-settings.php:50 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "" - -#: ../includes/register-settings.php:56 -msgid "Currency Position" -msgstr "" - -#: ../includes/register-settings.php:57 -msgid "Choose the location of the currency sign." -msgstr "" - -#: ../includes/register-settings.php:60 -msgid "Before - $10" -msgstr "" - -#: ../includes/register-settings.php:61 -msgid "After - 10$" -msgstr "" - -#: ../includes/register-settings.php:66 -msgid "Thousands Separator" -msgstr "" - -#: ../includes/register-settings.php:67 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "" - -#: ../includes/register-settings.php:74 -msgid "Decimal Separator" -msgstr "" - -#: ../includes/register-settings.php:75 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "" - -#: ../includes/register-settings.php:86 -msgid "Payment Gateways" -msgstr "" - -#: ../includes/register-settings.php:87 -msgid "Choose the payment gateways you want to enable." -msgstr "" - -#: ../includes/register-settings.php:93 -msgid "PayPal Settings" -msgstr "Opções do Paypal" - -#: ../includes/register-settings.php:94 -msgid "Configure the PayPal settings" -msgstr "Configurar as opções do Paypal" - -#: ../includes/register-settings.php:99 -msgid "PayPal Email" -msgstr "Email da conta Paypal" - -#: ../includes/register-settings.php:100 -msgid "Enter your PayPal account's email" -msgstr "" - -#: ../includes/register-settings.php:106 -msgid "Disable cURL for PayPal IPN" -msgstr "" - -#: ../includes/register-settings.php:107 -msgid "If payments are not getting marked as complete, check this option" -msgstr "" - -#: ../includes/register-settings.php:112 -msgid "Alternate PayPal Purchase Verification" -msgstr "" - -#: ../includes/register-settings.php:113 -msgid "If payments are not getting marked as complete, and disabling cURL does not fix the problem, then check this box" -msgstr "" - -#: ../includes/register-settings.php:122 -msgid "Purchase Email Subject" -msgstr "" - -#: ../includes/register-settings.php:123 -msgid "Enter the subject line for the purchase receipt email" -msgstr "" - -#: ../includes/register-settings.php:128 -msgid "Purchase Receipt" -msgstr "" - -#: ../includes/register-settings.php:129 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "" - -#: ../includes/register-settings.php:130 -msgid "A list of download URLs for each download purchased" -msgstr "" - -#: ../includes/register-settings.php:131 -msgid "The buyer's name" -msgstr "" - -#: ../includes/register-settings.php:132 -msgid "The date of the purchase" -msgstr "" - -#: ../includes/register-settings.php:133 -msgid "Your site name" -msgstr "" - -#: ../includes/register-settings.php:142 -msgid "Enable Ajax" -msgstr "Activar Ajax" - -#: ../includes/register-settings.php:143 -msgid "Check this to enable AJAX for the shopping cart." -msgstr "" - -#: ../includes/register-settings.php:148 -msgid "Logged-In Only" -msgstr "Apenas Utilizadores Ligados" - -#: ../includes/register-settings.php:149 -msgid "Require that users be logged-in to purchase files." -msgstr "" - -#: ../includes/register-settings.php:154 -msgid "Show Register Form?" -msgstr "" - -#: ../includes/register-settings.php:155 -msgid "Display the registration form for non-logged-in users" -msgstr "" - -#: ../includes/register-settings.php:178 -msgid "General Settings" -msgstr "Configurações Gerais" - -#: ../includes/register-settings.php:204 -msgid "Payment Gateway Settings" -msgstr "" - -#: ../includes/register-settings.php:230 -msgid "Email Settings" -msgstr "Configurações de Email" - -#: ../includes/register-settings.php:256 -msgid "Misc Settings" -msgstr "Configurações Diversas" - -#: ../includes/register-settings.php:290 -#: ../includes/register-settings.php:294 -#: ../includes/register-settings.php:298 -#: ../includes/register-settings.php:302 -msgid "Configure the settings below" -msgstr "" - -#: ../includes/scripts.php:16 -msgid "Please enter a discount code" -msgstr "" - -#: ../includes/scripts.php:17 -msgid "Discount Applied" -msgstr "" - -#: ../includes/scripts.php:18 -msgid "Loading" -msgstr "A Carregar" - -#: ../includes/shortcodes.php:54 -msgid "You have not purchased any downloads" -msgstr "" - -#: ../includes/shortcodes.php:79 -msgid "Add to Cart" -msgstr "" - -#: ../includes/shortcodes.php:106 -msgid "No downloads found" -msgstr "" - -#: ../includes/template-functions.php:43 -msgid "added to your cart" -msgstr "" - -#: ../includes/template-functions.php:47 -msgid "here" -msgstr "aqui" - -#: ../includes/template-functions.php:48 -msgid "You have already purchased this item." -msgstr "" - -#: ../includes/template-functions.php:48 -#, php-format -msgid "Click %s to purchase again." -msgstr "" - -#: ../includes/thickbox.php:9 -#: ../includes/thickbox.php:10 -#: ../includes/thickbox.php:96 -msgid "Insert Download" -msgstr "Inserir Download" - -#: ../includes/thickbox.php:39 -msgid "You must choose a download" -msgstr "" - -#: ../includes/thickbox.php:62 -msgid "Use the form below to insert the short code for purchasing a download." -msgstr "" - -#: ../includes/thickbox.php:65 -msgid "Choose a download" -msgstr "" - -#: ../includes/thickbox.php:74 -msgid "Choose a style" -msgstr "" - -#: ../includes/thickbox.php:84 -msgid "Choose a button color" -msgstr "" - -#: ../includes/thickbox.php:93 -msgid "Link text . . ." -msgstr "" - -#: ../includes/thickbox.php:97 -msgid "Cancel" -msgstr "Cancelar" - -#: ../includes/thickbox.php:99 -msgid "Button Styles" -msgstr "" - -#: ../includes/widgets.php:20 -msgid "Downloads Cart" -msgstr "" - -#: ../includes/widgets.php:20 -msgid "Display the downloads shopping cart" -msgstr "" - -#: ../includes/widgets.php:60 -msgid "Title:" -msgstr "Título:" - -#: ../includes/widgets.php:64 -msgid "Show Quantity:" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:20 -#: ../includes/admin-pages/discount-codes.php:33 -msgid "Code" -msgstr "Código" - -#: ../includes/admin-pages/discount-codes.php:21 -#: ../includes/admin-pages/discount-codes.php:34 -msgid "Type" -msgstr "Tipo" - -#: ../includes/admin-pages/discount-codes.php:22 -#: ../includes/admin-pages/discount-codes.php:35 -msgid "Amount" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:23 -#: ../includes/admin-pages/discount-codes.php:36 -msgid "Uses" -msgstr "Utilizações" - -#: ../includes/admin-pages/discount-codes.php:24 -#: ../includes/admin-pages/discount-codes.php:37 -msgid "Max Uses" -msgstr "Limite Máximo de Utilizações" - -#: ../includes/admin-pages/discount-codes.php:25 -#: ../includes/admin-pages/discount-codes.php:38 -msgid "Expiration" -msgstr "Validade" - -#: ../includes/admin-pages/discount-codes.php:26 -#: ../includes/admin-pages/discount-codes.php:39 -msgid "Status" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:63 -#: ../includes/admin-pages/discount-codes.php:65 -msgid "unlimited" -msgstr "ilimitado" - -#: ../includes/admin-pages/discount-codes.php:71 -msgid "Expired" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:73 -msgid "no expiration" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:78 -msgid "Edit" -msgstr "Editar" - -#: ../includes/admin-pages/discount-codes.php:80 -msgid "Deactivate" -msgstr "Desactivar" - -#: ../includes/admin-pages/discount-codes.php:82 -msgid "Activate" -msgstr "Activar" - -#: ../includes/admin-pages/discount-codes.php:84 -msgid "Delete" -msgstr "Eliminar" - -#: ../includes/admin-pages/discount-codes.php:89 -msgid "No discount codes have been created." -msgstr "" - -#: ../includes/admin-pages/payments-history.php:24 -msgid "Payment mode" -msgstr "Método de Pagamento" - -#: ../includes/admin-pages/payments-history.php:26 -msgid "Live" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:27 -msgid "Test" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:31 -msgid "Payments per page" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:33 -msgid "Show" -msgstr "Exibir" - -#: ../includes/admin-pages/payments-history.php:38 -#: ../includes/admin-pages/payments-history.php:51 -msgid "ID" -msgstr "ID" - -#: ../includes/admin-pages/payments-history.php:40 -#: ../includes/admin-pages/payments-history.php:53 -msgid "Key" -msgstr "Chave" - -#: ../includes/admin-pages/payments-history.php:41 -#: ../includes/admin-pages/payments-history.php:54 -msgid "Products" -msgstr "Produtos" - -#: ../includes/admin-pages/payments-history.php:44 -#: ../includes/admin-pages/payments-history.php:57 -msgid "User" -msgstr "Utilizador" - -#: ../includes/admin-pages/payments-history.php:74 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:74 -msgid "View Order Details" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:77 -msgid "Purchased File" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:77 -msgid "Purchased Files" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:82 -msgid "Price: " -msgstr "Preço:" - -#: ../includes/admin-pages/payments-history.php:87 -msgid "Discount used:" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:87 -msgid "none" -msgstr "nenhum" - -#: ../includes/admin-pages/payments-history.php:88 -msgid "Total:" -msgstr "Total:" - -#: ../includes/admin-pages/payments-history.php:91 -msgid "Buyer's Personal Details:" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:93 -msgid "Name:" -msgstr "Nome:" - -#: ../includes/admin-pages/payments-history.php:94 -msgid "Email:" -msgstr "Email:" - -#: ../includes/admin-pages/payments-history.php:97 -msgid "Close" -msgstr "Fechar" - -#: ../includes/admin-pages/payments-history.php:104 -msgid "Resend Purchase Receipt" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:110 -msgid "No payments recorded yet" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:125 -msgid "« Previous" -msgstr "« Anterior" - -#: ../includes/admin-pages/payments-history.php:126 -msgid "Next »" -msgstr "Próximo »" - -#: ../includes/admin-pages/reports.php:14 -msgid "Transactions created while in test mode are not included on this page." -msgstr "" - -#: ../includes/admin-pages/settings-old.php:11 -msgid "Easy Digital Downloads" -msgstr "" - -#: ../includes/admin-pages/settings-old.php:12 -msgid "Pages" -msgstr "Páginas" - -#: ../includes/admin-pages/settings-old.php:14 -#: ../includes/admin-pages/settings.php:16 -msgid "Misc" -msgstr "Diversos" - -#: ../includes/admin-pages/settings-old.php:25 -msgid "Purchase Form" -msgstr "Formulário de Compra" - -#: ../includes/admin-pages/settings-old.php:49 -#: ../includes/admin-pages/settings-old.php:78 -msgid "No pages found" -msgstr "Não foram encontradas páginas" - -#: ../includes/admin-pages/settings-old.php:53 -msgid "Choose the page that holds the checkout form short code." -msgstr "" - -#: ../includes/admin-pages/settings-old.php:82 -msgid "Choose the page users are directed to after a successful purchase." -msgstr "" - -#: ../includes/admin-pages/settings-old.php:95 -#: ../includes/admin-pages/settings-old.php:101 -#: ../includes/admin-pages/settings-old.php:106 -msgid "Test Mode" -msgstr "Modo de Teste" - -#: ../includes/admin-pages/settings-old.php:110 -msgid "Check this to use the plugin in test mode." -msgstr "Coloque um visto aqui para usar o plugin em modo de teste." - -#: ../includes/admin-pages/settings-old.php:124 -#: ../includes/admin-pages/settings-old.php:129 -msgid "Gateways" -msgstr "Gateways" - -#: ../includes/admin-pages/settings-old.php:140 -msgid "Check each of the payment gateways you would like to enable. Configure the selected gateways below." -msgstr "" - -#: ../includes/admin-pages/settings-old.php:163 -msgid "Enter your PayPal Email." -msgstr "Introduza o seu email Paypal." - -#: ../includes/admin-pages/settings-old.php:177 -msgid "Miscellaneous" -msgstr "Diversos" - -#: ../includes/admin-pages/settings-old.php:261 -msgid "Choose your currency." -msgstr "Escolha a sua moeda." - -#: ../includes/admin-pages/settings-old.php:261 -msgid "Note" -msgstr "Nota" - -#: ../includes/admin-pages/settings-old.php:261 -msgid "If you use Stripe, you MUST use USD." -msgstr "Se usar Stripe, TEM de escolher USD." - -#: ../includes/admin-pages/settings-old.php:288 -msgid "Messages" -msgstr "Mensagens" - -#: ../includes/admin-pages/settings-old.php:294 -#: ../includes/admin-pages/settings-old.php:299 -msgid "Payment Confirmation" -msgstr "Confirmação de Pagamento" - -#: ../includes/admin-pages/settings-old.php:303 -msgid "Enter the message displayed after a user makes a successful purchase. HTML is accepted." -msgstr "Escreva a mensagem a ser exibida quando um utilizador finaliza uma compra. Pode usar HTML." - -#: ../includes/admin-pages/settings-old.php:313 -msgid "Save Options" -msgstr "Gravar Opções" - -#: ../includes/admin-pages/settings.php:13 -msgid "General" -msgstr "Geral" - -#: ../includes/admin-pages/settings.php:15 -msgid "Emails" -msgstr "Emails" - -#: ../includes/admin-pages/forms/add-discount.php:1 -msgid "Add New Discount" -msgstr "Adicionar Novo Desconto" - -#: ../includes/admin-pages/forms/add-discount.php:11 -#: ../includes/admin-pages/forms/edit-discount.php:17 -msgid "The name of this discount" -msgstr "Nome deste desconto" - -#: ../includes/admin-pages/forms/add-discount.php:20 -#: ../includes/admin-pages/forms/edit-discount.php:26 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "" - -#: ../includes/admin-pages/forms/add-discount.php:29 -#: ../includes/admin-pages/forms/edit-discount.php:35 -msgid "Percentage" -msgstr "Percentagem" - -#: ../includes/admin-pages/forms/add-discount.php:30 -#: ../includes/admin-pages/forms/edit-discount.php:36 -msgid "Flat amount" -msgstr "Valor Fixo" - -#: ../includes/admin-pages/forms/add-discount.php:32 -#: ../includes/admin-pages/forms/edit-discount.php:38 -msgid "The kind of discount to apply for this discount." -msgstr "Tipo de desconto a aplicar neste código promocional." - -#: ../includes/admin-pages/forms/add-discount.php:41 -#: ../includes/admin-pages/forms/edit-discount.php:47 -msgid "The amount of this discount code." -msgstr "Valor deste código promocional." - -#: ../includes/admin-pages/forms/add-discount.php:46 -#: ../includes/admin-pages/forms/edit-discount.php:52 -msgid "Expiration date" -msgstr "Data Limite" - -#: ../includes/admin-pages/forms/add-discount.php:50 -#: ../includes/admin-pages/forms/edit-discount.php:56 -msgid "Enter the expiration date for this discount code in the format of yyyy-mm-dd. For no expiration, leave blank" -msgstr "" - -#: ../includes/admin-pages/forms/add-discount.php:59 -#: ../includes/admin-pages/forms/edit-discount.php:65 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "" - -#: ../includes/admin-pages/forms/add-discount.php:67 -msgid "Add Discount Code" -msgstr "Adicionar Código Promocional" - -#: ../includes/admin-pages/forms/edit-discount.php:3 -msgid "Something went wrong." -msgstr "Algo correu mal." - -#: ../includes/admin-pages/forms/edit-discount.php:7 -msgid "Edit Discount" -msgstr "Editar Desconto" - -#: ../includes/admin-pages/forms/edit-discount.php:7 -msgid "Go Back" -msgstr "Regressar" - -#: ../includes/admin-pages/forms/edit-discount.php:74 -msgid "Active" -msgstr "Activado" - -#: ../includes/admin-pages/forms/edit-discount.php:75 -msgid "Inactive" -msgstr "Desactivado" - -#: ../includes/admin-pages/forms/edit-discount.php:77 -msgid "The status of this discount code." -msgstr "Estado deste código promocional." - -#: ../includes/admin-pages/forms/edit-discount.php:87 -msgid "Update Discount Code" -msgstr "Actualizar Código Promocional" - -#: ../includes/gateways/paypal.php:171 -msgid "Invalid IPN" -msgstr "IPN Inválido" - diff --git a/languages/edd-tr_TR.mo b/languages/edd-tr_TR.mo deleted file mode 100644 index 20a1d7715e9..00000000000 Binary files a/languages/edd-tr_TR.mo and /dev/null differ diff --git a/languages/edd-tr_TR.po b/languages/edd-tr_TR.po deleted file mode 100644 index b6cb14c6253..00000000000 --- a/languages/edd-tr_TR.po +++ /dev/null @@ -1,1703 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Easy Digital Downloads\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-04-30 21:43-0600\n" -"PO-Revision-Date: 2012-06-02 05:42+0200\n" -"Last-Translator: \n" -"Language-Team: Pippin's Plugins\n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Poedit-KeywordsList: __;_e;_n;_x\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-Language: English\n" -"X-Poedit-Country: UNITED STATES\n" -"X-Poedit-SearchPath-0: ..\n" -"X-Poedit-SearchPath-1: ../includes\n" -"X-Poedit-SearchPath-2: ../includes/admin-pages\n" -"X-Poedit-SearchPath-3: ../includes/admin-pages/forms\n" -"X-Poedit-SearchPath-4: ../includes/gateways\n" -"X-Poedit-SearchPath-5: .\n" - -#: ../includes/template-functions.php:8 -#: ../includes/dashboard-columns.php:41 -msgid "Purchase" -msgstr "Öde" - -#: ../includes/template-functions.php:60 -#: ../includes/template-functions.php:71 -#: ../includes/cart-template.php:23 -#: ../includes/cart-template.php:26 -msgid "Checkout" -msgstr "Kasaya git" - -#: ../includes/template-functions.php:78 -msgid "added to your cart" -msgstr "" - -#: ../includes/template-functions.php:112 -msgid "Gray" -msgstr "" - -#: ../includes/template-functions.php:113 -msgid "Pink" -msgstr "" - -#: ../includes/template-functions.php:114 -msgid "blue" -msgstr "" - -#: ../includes/template-functions.php:115 -msgid "Green" -msgstr "" - -#: ../includes/template-functions.php:116 -msgid "Teal" -msgstr "" - -#: ../includes/template-functions.php:117 -msgid "Black" -msgstr "" - -#: ../includes/template-functions.php:118 -msgid "Dark Gray" -msgstr "" - -#: ../includes/template-functions.php:119 -msgid "Orange" -msgstr "" - -#: ../includes/template-functions.php:120 -msgid "Purple" -msgstr "" - -#: ../includes/template-functions.php:121 -msgid "Slate" -msgstr "" - -#: ../includes/template-functions.php:127 -msgid "You have already purchased this item, but you may purchase it again." -msgstr "" - -#: ../includes/payment-functions.php:141 -msgid "pending" -msgstr "" - -#: ../includes/payment-functions.php:144 -msgid "complete" -msgstr "" - -#: ../includes/cart-template.php:44 -msgid "remove" -msgstr "kaldır" - -#: ../includes/cart-template.php:52 -msgid "Your cart is empty." -msgstr "Sepetiniz boş." - -#: ../includes/thickbox.php:9 -#: ../includes/thickbox.php:10 -#: ../includes/thickbox.php:97 -msgid "Insert Download" -msgstr "" - -#: ../includes/thickbox.php:39 -msgid "You must choose a download" -msgstr "" - -#: ../includes/thickbox.php:62 -msgid "Use the form below to insert the short code for purchasing a download." -msgstr "" - -#: ../includes/thickbox.php:65 -msgid "Choose a download" -msgstr "" - -#: ../includes/thickbox.php:74 -msgid "Choose a style" -msgstr "" - -#: ../includes/thickbox.php:85 -msgid "Choose a button color" -msgstr "" - -#: ../includes/thickbox.php:94 -msgid "Link text . . ." -msgstr "" - -#: ../includes/thickbox.php:98 -msgid "Cancel" -msgstr "" - -#: ../includes/thickbox.php:100 -msgid "Button Styles" -msgstr "" - -#: ../includes/register-settings.php:23 -msgid "Test Mode" -msgstr "" - -#: ../includes/register-settings.php:24 -msgid "While in test mode no live transactions are processed. To fully use test mode, you must have a sandbox (test) account for the payment gateway you are testing." -msgstr "" - -#: ../includes/register-settings.php:29 -msgid "Purchase Page" -msgstr "" - -#: ../includes/register-settings.php:30 -msgid "This is the checkout page where buyers will complete their purchases" -msgstr "" - -#: ../includes/register-settings.php:36 -msgid "Success Page" -msgstr "" - -#: ../includes/register-settings.php:37 -msgid "This is the page buyers are sent to after completing their purchases" -msgstr "" - -#: ../includes/register-settings.php:43 -msgid "Currency Settings" -msgstr "" - -#: ../includes/register-settings.php:44 -msgid "Configure the currency options" -msgstr "" - -#: ../includes/register-settings.php:49 -msgid "Currency" -msgstr "" - -#: ../includes/register-settings.php:50 -msgid "Choose your currency. Note that some payment gateways have currency restrictions." -msgstr "" - -#: ../includes/register-settings.php:56 -msgid "Currency Position" -msgstr "" - -#: ../includes/register-settings.php:57 -msgid "Choose the location of the currency sign." -msgstr "" - -#: ../includes/register-settings.php:60 -msgid "Before - $10" -msgstr "" - -#: ../includes/register-settings.php:61 -msgid "After - 10$" -msgstr "" - -#: ../includes/register-settings.php:66 -msgid "Thousands Separator" -msgstr "" - -#: ../includes/register-settings.php:67 -msgid "The symbol (usually , or .) to separate thousands" -msgstr "" - -#: ../includes/register-settings.php:74 -msgid "Decimal Separator" -msgstr "" - -#: ../includes/register-settings.php:75 -msgid "The symbol (usually , or .) to separate decimal points" -msgstr "" - -#: ../includes/register-settings.php:86 -msgid "Payment Gateways" -msgstr "" - -#: ../includes/register-settings.php:87 -msgid "Choose the payment gateways you want to enable." -msgstr "" - -#: ../includes/register-settings.php:93 -msgid "Accepted Payment Method Icons" -msgstr "" - -#: ../includes/register-settings.php:94 -msgid "Display icons for the selected payment methods" -msgstr "" - -#: ../includes/register-settings.php:94 -msgid "You will also need to configure your gateway settings if you are accepting credit cards" -msgstr "" - -#: ../includes/register-settings.php:106 -msgid "PayPal Settings" -msgstr "" - -#: ../includes/register-settings.php:107 -msgid "Configure the PayPal settings" -msgstr "" - -#: ../includes/register-settings.php:112 -msgid "PayPal Email" -msgstr "" - -#: ../includes/register-settings.php:113 -msgid "Enter your PayPal account's email" -msgstr "" - -#: ../includes/register-settings.php:119 -msgid "Disable cURL for PayPal IPN" -msgstr "" - -#: ../includes/register-settings.php:120 -msgid "If payments are not getting marked as complete, check this option" -msgstr "" - -#: ../includes/register-settings.php:125 -msgid "Alternate PayPal Purchase Verification" -msgstr "" - -#: ../includes/register-settings.php:126 -msgid "If payments are not getting marked as complete, and disabling cURL does not fix the problem, then check this box" -msgstr "" - -#: ../includes/register-settings.php:135 -msgid "From Name" -msgstr "" - -#: ../includes/register-settings.php:136 -msgid "The name purchase receipts are said to come from. This should probably be your site or shop name." -msgstr "" - -#: ../includes/register-settings.php:141 -msgid "From Email" -msgstr "" - -#: ../includes/register-settings.php:142 -msgid "Email to send purchase receipts from. This will act as the \"from\" and \"reply-to\" address." -msgstr "" - -#: ../includes/register-settings.php:147 -msgid "Purchase Email Subject" -msgstr "" - -#: ../includes/register-settings.php:148 -msgid "Enter the subject line for the purchase receipt email" -msgstr "" - -#: ../includes/register-settings.php:153 -msgid "Purchase Receipt" -msgstr "" - -#: ../includes/register-settings.php:154 -msgid "Enter the email that is sent to users after completing a successful purchase. HTML is accepted. Available template tags:" -msgstr "" - -#: ../includes/register-settings.php:155 -msgid "A list of download URLs for each download purchased" -msgstr "" - -#: ../includes/register-settings.php:156 -msgid "The buyer's name" -msgstr "" - -#: ../includes/register-settings.php:157 -msgid "The date of the purchase" -msgstr "" - -#: ../includes/register-settings.php:158 -msgid "The total price of the purchase" -msgstr "" - -#: ../includes/register-settings.php:159 -msgid "Your site name" -msgstr "" - -#: ../includes/register-settings.php:168 -msgid "Disable Styles" -msgstr "" - -#: ../includes/register-settings.php:169 -msgid "Check this to disable all included styling" -msgstr "" - -#: ../includes/register-settings.php:174 -msgid "Checkout Button Color" -msgstr "" - -#: ../includes/register-settings.php:175 -msgid "Choose the button color you want to use for the checkout buttons." -msgstr "" - -#: ../includes/register-settings.php:185 -msgid "Enable Ajax" -msgstr "" - -#: ../includes/register-settings.php:186 -msgid "Check this to enable AJAX for the shopping cart." -msgstr "" - -#: ../includes/register-settings.php:191 -msgid "Enable jQuery Validation" -msgstr "" - -#: ../includes/register-settings.php:192 -msgid "Check this to enable jQuery validation on the checkout form." -msgstr "" - -#: ../includes/register-settings.php:197 -msgid "Disable Guest Checkout" -msgstr "" - -#: ../includes/register-settings.php:198 -msgid "Require that users be logged-in to purchase files." -msgstr "" - -#: ../includes/register-settings.php:203 -msgid "Show Register / Login Form?" -msgstr "" - -#: ../includes/register-settings.php:204 -msgid "Display the registration and login forms on the checkout page for non-logged-in users" -msgstr "" - -#: ../includes/register-settings.php:209 -msgid "Disable Redownload?" -msgstr "" - -#: ../includes/register-settings.php:210 -msgid "Check this if you do not want to allow users to redownload items from their purchase history" -msgstr "" - -#: ../includes/register-settings.php:215 -msgid "Terms of Agreement" -msgstr "" - -#: ../includes/register-settings.php:221 -msgid "Agree to Terms" -msgstr "" - -#: ../includes/register-settings.php:222 -msgid "Check this to show an agree to terms on the checkout that users must agree to before purchasing" -msgstr "" - -#: ../includes/register-settings.php:227 -msgid "Agree to Terms Label" -msgstr "" - -#: ../includes/register-settings.php:228 -msgid "Label shown next to the agree to terms check box" -msgstr "" - -#: ../includes/register-settings.php:234 -msgid "Agreement Text" -msgstr "" - -#: ../includes/register-settings.php:235 -msgid "If Agree to Terms is checked, enter the agreement terms here" -msgstr "" - -#: ../includes/register-settings.php:261 -msgid "General Settings" -msgstr "" - -#: ../includes/register-settings.php:287 -msgid "Payment Gateway Settings" -msgstr "" - -#: ../includes/register-settings.php:313 -msgid "Email Settings" -msgstr "" - -#: ../includes/register-settings.php:339 -msgid "Style Settings" -msgstr "" - -#: ../includes/register-settings.php:366 -msgid "Misc Settings" -msgstr "" - -#: ../includes/dashboard-columns.php:7 -msgid "Name" -msgstr "" - -#: ../includes/dashboard-columns.php:8 -msgid "Categories" -msgstr "" - -#: ../includes/dashboard-columns.php:9 -msgid "Tags" -msgstr "" - -#: ../includes/dashboard-columns.php:10 -#: ../includes/graphing.php:14 -msgid "Sales" -msgstr "" - -#: ../includes/dashboard-columns.php:11 -#: ../includes/graphing.php:52 -#: ../includes/graphing.php:87 -msgid "Earnings" -msgstr "" - -#: ../includes/dashboard-columns.php:12 -msgid "Short Code" -msgstr "" - -#: ../includes/dashboard-columns.php:13 -msgid "Date" -msgstr "" - -#: ../includes/dashboard-columns.php:106 -msgid "Show all categories" -msgstr "" - -#: ../includes/dashboard-columns.php:117 -msgid "Show all tags" -msgstr "" - -#: ../includes/widgets.php:21 -msgid "Downloads Cart" -msgstr "" - -#: ../includes/widgets.php:21 -msgid "Display the downloads shopping cart" -msgstr "" - -#: ../includes/widgets.php:72 -msgid "Title:" -msgstr "" - -#: ../includes/widgets.php:77 -msgid "Show Quantity:" -msgstr "" - -#: ../includes/scripts.php:19 -msgid "Please enter a discount code" -msgstr "" - -#: ../includes/scripts.php:20 -msgid "Discount Applied" -msgstr "" - -#: ../includes/scripts.php:21 -msgid "You have already added this item to your cart" -msgstr "" - -#: ../includes/scripts.php:22 -msgid "Your cart is empty" -msgstr "" - -#: ../includes/scripts.php:23 -msgid "Loading" -msgstr "" - -#: ../includes/graphing.php:13 -#: ../includes/graphing.php:51 -msgid "Download" -msgstr "" - -#: ../includes/graphing.php:24 -msgid "Downloads Performance in Sales" -msgstr "" - -#: ../includes/graphing.php:62 -msgid "Downloads Performance in Earnings" -msgstr "" - -#: ../includes/graphing.php:86 -msgid "Month" -msgstr "" - -#: ../includes/graphing.php:101 -msgid "Earnings per month" -msgstr "" - -#: ../includes/metabox.php:4 -msgid "Download Configuration" -msgstr "" - -#: ../includes/metabox.php:5 -msgid "Download Stats" -msgstr "" - -#: ../includes/metabox.php:6 -msgid "Purchase Log" -msgstr "" - -#: ../includes/metabox.php:7 -msgid "File Download Log" -msgstr "" - -#: ../includes/metabox.php:8 -msgid "Payment Info" -msgstr "" - -#: ../includes/metabox.php:32 -msgid "Pricing" -msgstr "" - -#: ../includes/metabox.php:36 -msgid "Check this to enable variable pricing." -msgstr "" - -#: ../includes/metabox.php:57 -msgid "price option name" -msgstr "" - -#: ../includes/metabox.php:58 -#: ../includes/metabox.php:68 -msgid "9.99" -msgstr "" - -#: ../includes/metabox.php:67 -#: ../includes/metabox.php:106 -#: ../includes/metabox.php:117 -msgid "file name" -msgstr "" - -#: ../includes/metabox.php:71 -msgid "Add New Price Option" -msgstr "" - -#: ../includes/metabox.php:84 -msgid "Enter the download price. Do not include a currency symbol" -msgstr "" - -#: ../includes/metabox.php:96 -msgid "Download Files" -msgstr "" - -#: ../includes/metabox.php:107 -#: ../includes/metabox.php:118 -msgid "file url" -msgstr "" - -#: ../includes/metabox.php:108 -#: ../includes/metabox.php:119 -msgid "Upload File" -msgstr "" - -#: ../includes/metabox.php:122 -#: ../includes/post-types.php:28 -#: ../includes/post-types.php:60 -msgid "Add New" -msgstr "" - -#: ../includes/metabox.php:122 -msgid "Upload the downloadable files." -msgstr "" - -#: ../includes/metabox.php:134 -msgid "Purchase Text" -msgstr "" - -#: ../includes/metabox.php:136 -msgid "Add the text you would like displayed for the purchase text" -msgstr "" - -#: ../includes/metabox.php:146 -msgid "Link Style" -msgstr "" - -#: ../includes/metabox.php:148 -msgid "Button" -msgstr "" - -#: ../includes/metabox.php:149 -msgid "Text" -msgstr "" - -#: ../includes/metabox.php:150 -msgid "Choose the style of the purchase link" -msgstr "" - -#: ../includes/metabox.php:162 -msgid "Button Color" -msgstr "" - -#: ../includes/metabox.php:170 -msgid "Choose the color of the purchase link, if button was selected above." -msgstr "" - -#: ../includes/metabox.php:179 -msgid "Disable the purchase button?" -msgstr "" - -#: ../includes/metabox.php:182 -msgid "Check this if you do not want the purchase button displayed." -msgstr "" - -#: ../includes/metabox.php:191 -msgid "Notes" -msgstr "" - -#: ../includes/metabox.php:194 -msgid "The style options above do NOT reflect the style of short code. The short code allows you to place a purchase button for this download anywhere on the site." -msgstr "" - -#: ../includes/metabox.php:200 -msgid "This short code can be placed anywhere on your site" -msgstr "" - -#: ../includes/metabox.php:263 -msgid "Sales:" -msgstr "" - -#: ../includes/metabox.php:269 -msgid "Earnings:" -msgstr "" - -#: ../includes/metabox.php:285 -msgid "Sales Log" -msgstr "" - -#: ../includes/metabox.php:287 -msgid "Each sale for this download is listed below." -msgstr "" - -#: ../includes/metabox.php:301 -#: ../includes/metabox.php:350 -msgid "Date:" -msgstr "" - -#: ../includes/metabox.php:305 -msgid "Buyer:" -msgstr "" - -#: ../includes/metabox.php:309 -msgid "Purchase ID:" -msgstr "" - -#: ../includes/metabox.php:317 -msgid "No sales yet" -msgstr "" - -#: ../includes/metabox.php:332 -msgid "Download Log" -msgstr "" - -#: ../includes/metabox.php:334 -msgid "Each time a file is downloaded, it is recorded below." -msgstr "" - -#: ../includes/metabox.php:354 -msgid "Downloaded by:" -msgstr "" - -#: ../includes/metabox.php:358 -msgid "IP Address:" -msgstr "" - -#: ../includes/metabox.php:362 -msgid "File: " -msgstr "" - -#: ../includes/metabox.php:371 -msgid "No file downloads yet yet" -msgstr "" - -#: ../includes/ajax-functions.php:57 -#: ../includes/process-purchase.php:17 -msgid "The discount you entered is invalid" -msgstr "" - -#: ../includes/admin-pages.php:6 -#: ../includes/post-types.php:70 -msgid "Payment History" -msgstr "" - -#: ../includes/admin-pages.php:7 -msgid "Discount Codes" -msgstr "" - -#: ../includes/admin-pages.php:8 -msgid "Earnings and Sales Reports" -msgstr "" - -#: ../includes/admin-pages.php:8 -msgid "Reports" -msgstr "" - -#: ../includes/admin-pages.php:9 -msgid "Easy Digital Download Settings" -msgstr "" - -#: ../includes/admin-pages.php:9 -msgid "Settings" -msgstr "" - -#: ../includes/admin-pages.php:10 -msgid "Easy Digital Download Add Ons" -msgstr "" - -#: ../includes/admin-pages.php:10 -msgid "Add Ons" -msgstr "" - -#: ../includes/error-tracking.php:13 -msgid "Error" -msgstr "" - -#: ../includes/misc-functions.php:40 -msgid "US Dollars ($)" -msgstr "" - -#: ../includes/misc-functions.php:41 -msgid "Euros (€)" -msgstr "" - -#: ../includes/misc-functions.php:42 -msgid "Pounds Sterling (£)" -msgstr "" - -#: ../includes/misc-functions.php:43 -msgid "Australian Dollars ($)" -msgstr "" - -#: ../includes/misc-functions.php:44 -msgid "Brazilian Real ($)" -msgstr "" - -#: ../includes/misc-functions.php:45 -msgid "Canadian Dollars ($)" -msgstr "" - -#: ../includes/misc-functions.php:46 -msgid "Czech Koruna" -msgstr "" - -#: ../includes/misc-functions.php:47 -msgid "Danish Krone" -msgstr "" - -#: ../includes/misc-functions.php:48 -msgid "Hong Kong Dollar ($)" -msgstr "" - -#: ../includes/misc-functions.php:49 -msgid "Hungarian Forint" -msgstr "" - -#: ../includes/misc-functions.php:50 -msgid "Israeli Shekel" -msgstr "" - -#: ../includes/misc-functions.php:51 -msgid "Japanese Yen (¥)" -msgstr "" - -#: ../includes/misc-functions.php:52 -msgid "Malaysian Ringgits" -msgstr "" - -#: ../includes/misc-functions.php:53 -msgid "Mexican Peso ($)" -msgstr "" - -#: ../includes/misc-functions.php:54 -msgid "New Zealand Dollar ($)" -msgstr "" - -#: ../includes/misc-functions.php:55 -msgid "Norwegian Krone" -msgstr "" - -#: ../includes/misc-functions.php:56 -msgid "Philippine Pesos" -msgstr "" - -#: ../includes/misc-functions.php:57 -msgid "Polish Zloty" -msgstr "" - -#: ../includes/misc-functions.php:58 -msgid "Singapore Dollar ($)" -msgstr "" - -#: ../includes/misc-functions.php:59 -msgid "Swedish Krona" -msgstr "" - -#: ../includes/misc-functions.php:60 -msgid "Swiss Franc" -msgstr "" - -#: ../includes/misc-functions.php:61 -msgid "Taiwan New Dollars" -msgstr "" - -#: ../includes/misc-functions.php:62 -msgid "Thai Baht" -msgstr "" - -#: ../includes/misc-functions.php:63 -msgid "Indian Rupee" -msgstr "" - -#: ../includes/process-download.php:64 -msgid "You do not have permission to download this file" -msgstr "" - -#: ../includes/process-download.php:64 -msgid "Purchase Verification Failed" -msgstr "" - -#: ../includes/checkout-template.php:57 -msgid "Choose Your Payment Method" -msgstr "Ödeme yöntemini Seçiniz" - -#: ../includes/checkout-template.php:68 -msgid "Next" -msgstr "İleri" - -#: ../includes/checkout-template.php:115 -msgid "Email address" -msgstr "Email adresiniz" - -#: ../includes/checkout-template.php:116 -msgid "Email Address" -msgstr "" - -#: ../includes/checkout-template.php:119 -#: ../includes/checkout-template.php:120 -#: ../includes/checkout-template.php:289 -#: ../includes/checkout-template.php:290 -msgid "First Name" -msgstr "Adınız" - -#: ../includes/checkout-template.php:123 -#: ../includes/checkout-template.php:293 -msgid "Last name" -msgstr "Soyadınız" - -#: ../includes/checkout-template.php:124 -#: ../includes/checkout-template.php:294 -msgid "Last Name" -msgstr "Soyadınız" - -#: ../includes/checkout-template.php:132 -msgid "Enter discount" -msgstr "" - -#: ../includes/checkout-template.php:134 -msgid "Discount" -msgstr "" - -#: ../includes/checkout-template.php:136 -msgid "Apply Discount" -msgstr "" - -#: ../includes/checkout-template.php:162 -msgid "Show Terms" -msgstr "" - -#: ../includes/checkout-template.php:163 -msgid "Hide Terms" -msgstr "" - -#: ../includes/checkout-template.php:166 -msgid "Agree to Terms?" -msgstr "" - -#: ../includes/checkout-template.php:190 -msgid "Go back" -msgstr "Geri git" - -#: ../includes/checkout-template.php:194 -msgid "You must be logged in to complete your purchase" -msgstr "" - -#: ../includes/checkout-template.php:214 -msgid "Card name" -msgstr "" - -#: ../includes/checkout-template.php:215 -msgid "Name on the Card" -msgstr "" - -#: ../includes/checkout-template.php:218 -msgid "Card number" -msgstr "" - -#: ../includes/checkout-template.php:219 -msgid "Card Number" -msgstr "" - -#: ../includes/checkout-template.php:222 -msgid "Security code" -msgstr "" - -#: ../includes/checkout-template.php:223 -msgid "CVC" -msgstr "" - -#: ../includes/checkout-template.php:229 -msgid "Year" -msgstr "" - -#: ../includes/checkout-template.php:230 -msgid "Expiration (MM/YYYY)" -msgstr "" - -#: ../includes/checkout-template.php:248 -msgid "Credit Card Info" -msgstr "" - -#: ../includes/checkout-template.php:250 -msgid "Address line 1" -msgstr "" - -#: ../includes/checkout-template.php:251 -msgid "Billing Address" -msgstr "" - -#: ../includes/checkout-template.php:254 -msgid "Address line 2" -msgstr "" - -#: ../includes/checkout-template.php:255 -msgid "Billing Address Line 2" -msgstr "" - -#: ../includes/checkout-template.php:258 -msgid "City" -msgstr "" - -#: ../includes/checkout-template.php:259 -msgid "Billing City" -msgstr "" - -#: ../includes/checkout-template.php:262 -msgid "State / Province" -msgstr "" - -#: ../includes/checkout-template.php:263 -msgid "Billing State / Province" -msgstr "" - -#: ../includes/checkout-template.php:266 -msgid "Zip / Postal code" -msgstr "" - -#: ../includes/checkout-template.php:267 -msgid "Billing Zip / Postal Code" -msgstr "" - -#: ../includes/checkout-template.php:279 -msgid "Create an account" -msgstr "Hesap açabilirsiniz" - -#: ../includes/checkout-template.php:279 -msgid "(optional)" -msgstr "(isteğe bağlı)" - -#: ../includes/checkout-template.php:281 -#: ../includes/checkout-template.php:282 -#: ../includes/checkout-template.php:318 -msgid "Username" -msgstr "Kullanıcı adı" - -#: ../includes/checkout-template.php:285 -#: ../includes/checkout-template.php:286 -msgid "Email" -msgstr "" - -#: ../includes/checkout-template.php:297 -#: ../includes/checkout-template.php:298 -#: ../includes/checkout-template.php:322 -msgid "Password" -msgstr "Şifre" - -#: ../includes/checkout-template.php:301 -msgid "Confirm password" -msgstr "Şifreyi tekrar girin" - -#: ../includes/checkout-template.php:302 -msgid "Password Again" -msgstr "Şifrenizi tekrar yazın" - -#: ../includes/checkout-template.php:304 -msgid "Already have an account?" -msgstr "Zaten bir hesabınız var mı?" - -#: ../includes/checkout-template.php:304 -msgid "Login" -msgstr "Giriş" - -#: ../includes/checkout-template.php:315 -msgid "Login to your account" -msgstr "Hesabınıza girin" - -#: ../includes/checkout-template.php:317 -msgid "Your username" -msgstr "Kullanıcı adınız" - -#: ../includes/checkout-template.php:321 -msgid "Your password" -msgstr "Şifreniz" - -#: ../includes/checkout-template.php:325 -msgid "Need to create an account?" -msgstr "Bir hesap açmalısınız" - -#: ../includes/checkout-template.php:325 -msgid "Register" -msgstr "Kayıt ol" - -#: ../includes/email-functions.php:26 -msgid "Hello" -msgstr "" - -#: ../includes/email-functions.php:26 -msgid "A download purchase has been made" -msgstr "" - -#: ../includes/email-functions.php:27 -msgid "Downloads sold:" -msgstr "" - -#: ../includes/email-functions.php:36 -msgid "Amount: " -msgstr "" - -#: ../includes/email-functions.php:37 -msgid "Thank you" -msgstr "" - -#: ../includes/email-functions.php:39 -msgid "New download purchase" -msgstr "" - -#: ../includes/process-purchase.php:20 -msgid "You must agree to the terms of use" -msgstr "" - -#: ../includes/process-purchase.php:37 -msgid "Username already taken" -msgstr "" - -#: ../includes/process-purchase.php:41 -msgid "Invalid username" -msgstr "" - -#: ../includes/process-purchase.php:45 -msgid "Enter a username" -msgstr "" - -#: ../includes/process-purchase.php:49 -msgid "Invalid email" -msgstr "" - -#: ../includes/process-purchase.php:53 -msgid "Email already used" -msgstr "" - -#: ../includes/process-purchase.php:57 -msgid "Enter a password" -msgstr "" - -#: ../includes/process-purchase.php:61 -msgid "Passwords don't match" -msgstr "" - -#: ../includes/process-purchase.php:78 -msgid "The password you entered is incorrect" -msgstr "" - -#: ../includes/process-purchase.php:82 -msgid "The username you entered does not exist" -msgstr "" - -#: ../includes/process-purchase.php:86 -msgid "Something has gone wrong, please try again" -msgstr "" - -#: ../includes/process-purchase.php:90 -msgid "You must enter a valid email address." -msgstr "" - -#: ../includes/gateway-functions.php:9 -msgid "Manual Payment" -msgstr "Elden Ödeme" - -#: ../includes/admin-notices.php:6 -msgid "Discount code updated." -msgstr "" - -#: ../includes/admin-notices.php:9 -msgid "There was a problem updating your discount code, please try again." -msgstr "" - -#: ../includes/admin-notices.php:12 -msgid "The payment has been deleted." -msgstr "" - -#: ../includes/admin-notices.php:15 -msgid "The purchase receipt has been resent." -msgstr "" - -#: ../includes/post-types.php:26 -#: ../includes/post-types.php:38 -msgid "Downloads" -msgstr "" - -#: ../includes/post-types.php:29 -msgid "Add New Download" -msgstr "" - -#: ../includes/post-types.php:30 -msgid "Edit Download" -msgstr "" - -#: ../includes/post-types.php:31 -msgid "New Download" -msgstr "" - -#: ../includes/post-types.php:32 -msgid "All Downloads" -msgstr "" - -#: ../includes/post-types.php:33 -msgid "View Download" -msgstr "" - -#: ../includes/post-types.php:34 -msgid "Search Downloads" -msgstr "" - -#: ../includes/post-types.php:35 -msgid "No Downloads found" -msgstr "" - -#: ../includes/post-types.php:36 -msgid "No Downloads found in Trash" -msgstr "" - -#: ../includes/post-types.php:58 -msgid "Payments" -msgstr "" - -#: ../includes/post-types.php:59 -msgid "Payment" -msgstr "" - -#: ../includes/post-types.php:61 -msgid "Add New Payment" -msgstr "" - -#: ../includes/post-types.php:62 -msgid "Edit Payment" -msgstr "" - -#: ../includes/post-types.php:63 -msgid "New Payment" -msgstr "" - -#: ../includes/post-types.php:64 -msgid "All Payments" -msgstr "" - -#: ../includes/post-types.php:65 -msgid "View Payment" -msgstr "" - -#: ../includes/post-types.php:66 -msgid "Search Payments" -msgstr "" - -#: ../includes/post-types.php:67 -msgid "No Payments found" -msgstr "" - -#: ../includes/post-types.php:68 -msgid "No Payments found in Trash" -msgstr "" - -#: ../includes/post-types.php:96 -msgid "Category" -msgstr "" - -#: ../includes/post-types.php:97 -msgid "Search Categories" -msgstr "" - -#: ../includes/post-types.php:98 -msgid "All Categories" -msgstr "" - -#: ../includes/post-types.php:99 -msgid "Parent Category" -msgstr "" - -#: ../includes/post-types.php:100 -msgid "Parent Category:" -msgstr "" - -#: ../includes/post-types.php:101 -msgid "Edit Category" -msgstr "" - -#: ../includes/post-types.php:102 -msgid "Update Category" -msgstr "" - -#: ../includes/post-types.php:103 -msgid "Add New Category" -msgstr "" - -#: ../includes/post-types.php:104 -msgid "New Category Name" -msgstr "" - -#: ../includes/post-types.php:118 -msgid "Tag" -msgstr "" - -#: ../includes/post-types.php:119 -msgid "Search Tags" -msgstr "" - -#: ../includes/post-types.php:120 -msgid "All Tags" -msgstr "" - -#: ../includes/post-types.php:121 -msgid "Parent Tag" -msgstr "" - -#: ../includes/post-types.php:122 -msgid "Parent Tag:" -msgstr "" - -#: ../includes/post-types.php:123 -msgid "Edit Tag" -msgstr "" - -#: ../includes/post-types.php:124 -msgid "Update Tag" -msgstr "" - -#: ../includes/post-types.php:125 -msgid "Add New Tag" -msgstr "" - -#: ../includes/post-types.php:126 -msgid "New Tag Name" -msgstr "" - -#: ../includes/post-types.php:144 -#: ../includes/post-types.php:145 -msgid "Download updated." -msgstr "" - -#: ../includes/post-types.php:146 -msgid "Download published." -msgstr "" - -#: ../includes/post-types.php:147 -msgid "Download saved." -msgstr "" - -#: ../includes/post-types.php:148 -msgid "Download submitted." -msgstr "" - -#: ../includes/shortcodes.php:35 -msgid "Download Name" -msgstr "Ürün ismi" - -#: ../includes/shortcodes.php:36 -msgid "Files" -msgstr "Dosya" - -#: ../includes/shortcodes.php:58 -msgid "No downloadable files found." -msgstr "" - -#: ../includes/shortcodes.php:68 -msgid "You have not purchased any downloads" -msgstr "Herhangi bir indirme işlemi için ödeme yapılmamış." - -#: ../includes/shortcodes.php:95 -msgid "Add to Cart" -msgstr "" - -#: ../includes/shortcodes.php:122 -msgid "No downloads found" -msgstr "" - -#: ../includes/gateways/paypal.php:198 -msgid "Invalid IPN" -msgstr "" - -#: ../includes/templates/checkout_cart.php:6 -msgid "Item Name" -msgstr "Ürünün İsmi" - -#: ../includes/templates/checkout_cart.php:7 -msgid "Item Price" -msgstr "Ürün Fiyatı" - -#: ../includes/templates/checkout_cart.php:8 -#: ../includes/admin-pages/discount-codes.php:28 -#: ../includes/admin-pages/discount-codes.php:42 -msgid "Actions" -msgstr "Eylemler" - -#: ../includes/templates/checkout_cart.php:43 -msgid "Total" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:20 -#: ../includes/admin-pages/discount-codes.php:34 -#: ../includes/admin-pages/forms/edit-discount.php:22 -#: ../includes/admin-pages/forms/add-discount.php:16 -msgid "Code" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:21 -#: ../includes/admin-pages/discount-codes.php:35 -#: ../includes/admin-pages/forms/edit-discount.php:31 -#: ../includes/admin-pages/forms/add-discount.php:25 -msgid "Type" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:22 -#: ../includes/admin-pages/discount-codes.php:36 -#: ../includes/admin-pages/forms/edit-discount.php:43 -#: ../includes/admin-pages/forms/add-discount.php:37 -msgid "Amount" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:23 -#: ../includes/admin-pages/discount-codes.php:37 -msgid "Uses" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:24 -#: ../includes/admin-pages/discount-codes.php:38 -#: ../includes/admin-pages/forms/edit-discount.php:70 -#: ../includes/admin-pages/forms/add-discount.php:64 -msgid "Max Uses" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:25 -#: ../includes/admin-pages/discount-codes.php:39 -msgid "Start Date" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:26 -#: ../includes/admin-pages/discount-codes.php:40 -msgid "Expiration" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:27 -#: ../includes/admin-pages/discount-codes.php:41 -#: ../includes/admin-pages/payments-history.php:51 -#: ../includes/admin-pages/payments-history.php:63 -#: ../includes/admin-pages/forms/edit-discount.php:79 -msgid "Status" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:65 -#: ../includes/admin-pages/discount-codes.php:67 -msgid "unlimited" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:76 -msgid "No start date" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:83 -msgid "Expired" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:85 -msgid "no expiration" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:91 -#: ../includes/admin-pages/payments-history.php:83 -msgid "Edit" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:93 -msgid "Deactivate" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:95 -msgid "Activate" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:97 -#: ../includes/admin-pages/payments-history.php:85 -msgid "Delete" -msgstr "" - -#: ../includes/admin-pages/discount-codes.php:102 -msgid "No discount codes have been created." -msgstr "" - -#: ../includes/admin-pages/reports.php:14 -msgid "Transactions created while in test mode are not included on this page." -msgstr "" - -#: ../includes/admin-pages/payments-history.php:30 -msgid "Payment mode" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:32 -msgid "Live" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:33 -msgid "Test" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:37 -msgid "Payments per page" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:39 -msgid "Show" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:44 -#: ../includes/admin-pages/payments-history.php:56 -msgid "ID" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:46 -#: ../includes/admin-pages/payments-history.php:58 -msgid "Key" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:47 -#: ../includes/admin-pages/payments-history.php:59 -msgid "Products" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:48 -#: ../includes/admin-pages/payments-history.php:60 -msgid "Price" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:50 -#: ../includes/admin-pages/payments-history.php:62 -msgid "User" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:84 -msgid "Resend Purchase Receipt" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:98 -#, php-format -msgid "Purchase Details for Payment #%s" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:98 -msgid "View Order Details" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:103 -msgid "Purchased File" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:103 -msgid "Purchased Files" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:118 -msgid "Price: " -msgstr "" - -#: ../includes/admin-pages/payments-history.php:124 -msgid "Discount used:" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:124 -msgid "none" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:125 -msgid "Total:" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:128 -msgid "Buyer's Personal Details:" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:130 -msgid "Name:" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:131 -msgid "Email:" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:134 -#: ../includes/admin-pages/forms/edit-payment.php:58 -msgid "Close" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:139 -msgid "guest" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:146 -msgid "No payments recorded yet" -msgstr "" - -#: ../includes/admin-pages/payments-history.php:161 -msgid "Previous" -msgstr "" - -#: ../includes/admin-pages/settings.php:13 -msgid "General" -msgstr "" - -#: ../includes/admin-pages/settings.php:15 -msgid "Emails" -msgstr "" - -#: ../includes/admin-pages/settings.php:16 -msgid "Styles" -msgstr "" - -#: ../includes/admin-pages/settings.php:17 -msgid "Misc" -msgstr "" - -#: ../includes/admin-pages/add-ons.php:9 -msgid "Add Ons for Easy Digital Downloads" -msgstr "" - -#: ../includes/admin-pages/add-ons.php:10 -msgid "These add-ons extend the functionality of Easy Digital Downloads." -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:3 -msgid "Something went wrong." -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:7 -msgid "Edit Discount" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:7 -#: ../includes/admin-pages/forms/edit-payment.php:3 -msgid "Go Back" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:17 -#: ../includes/admin-pages/forms/add-discount.php:11 -msgid "The name of this discount" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:26 -#: ../includes/admin-pages/forms/add-discount.php:20 -msgid "Enter a code for this discount, such as 10PERCENT" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:35 -#: ../includes/admin-pages/forms/add-discount.php:29 -msgid "Percentage" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:36 -#: ../includes/admin-pages/forms/add-discount.php:30 -msgid "Flat amount" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:38 -#: ../includes/admin-pages/forms/add-discount.php:32 -msgid "The kind of discount to apply for this discount." -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:47 -#: ../includes/admin-pages/forms/add-discount.php:41 -msgid "The amount of this discount code." -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:52 -#: ../includes/admin-pages/forms/add-discount.php:46 -msgid "Start date" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:56 -#: ../includes/admin-pages/forms/add-discount.php:50 -msgid "Enter the start date for this discount code in the format of yyyy-mm-dd. For no start date, leave blank. If entered, the discount can only be used after or on this date." -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:61 -#: ../includes/admin-pages/forms/add-discount.php:55 -msgid "Expiration date" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:65 -#: ../includes/admin-pages/forms/add-discount.php:59 -msgid "Enter the expiration date for this discount code in the format of yyyy-mm-dd. For no expiration, leave blank" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:74 -#: ../includes/admin-pages/forms/add-discount.php:68 -msgid "The maximum number of times this discount can be used. Leave blank for unlimited." -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:83 -msgid "Active" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:84 -msgid "Inactive" -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:86 -msgid "The status of this discount code." -msgstr "" - -#: ../includes/admin-pages/forms/edit-discount.php:96 -msgid "Update Discount Code" -msgstr "" - -#: ../includes/admin-pages/forms/add-discount.php:1 -msgid "Add New Discount" -msgstr "" - -#: ../includes/admin-pages/forms/add-discount.php:76 -msgid "Add Discount Code" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:9 -msgid "Downloads Purchased" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:21 -#, php-format -msgid "Add download to purchase #%s" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:21 -msgid "Add download to purchase" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:26 -msgid "Payment Status" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:31 -msgid "Pending" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:32 -msgid "Complete" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:44 -msgid "Update Payment" -msgstr "" - -#: ../includes/admin-pages/forms/edit-payment.php:57 -msgid "Add Selected Downloads" -msgstr "" - diff --git a/languages/index.php b/languages/index.php new file mode 100755 index 00000000000..16df6be3122 --- /dev/null +++ b/languages/index.php @@ -0,0 +1,6 @@ +=10.0.0", + "npm": ">=6.9.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", + "integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.2.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", + "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz", + "integrity": "sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz", + "integrity": "sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.20.2.tgz", + "integrity": "sha512-KS/G8YI8uwMGKErLFOHS/ekhqdHhpEloxs43NecQHVgo2QuQSyJhGIY1fL8UGl9wy5ItVwwoUL4YxVqsplGq2g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", + "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", + "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz", + "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==", + "dev": true, + "dependencies": { + "core-js-pure": "^3.25.1", + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "dependencies": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + }, + "bin": { + "watch": "cli.js" + }, + "engines": { + "node": ">=0.1.95" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", + "deprecated": "Moved to 'npm install @sideway/address'", + "dev": true + }, + "node_modules/@hapi/bourne": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", + "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==", + "deprecated": "This version has been deprecated and is no longer supported or maintained", + "dev": true + }, + "node_modules/@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", + "deprecated": "This version has been deprecated and is no longer supported or maintained", + "dev": true + }, + "node_modules/@hapi/joi": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", + "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "deprecated": "Switch to 'npm install joi'", + "dev": true, + "dependencies": { + "@hapi/address": "2.x.x", + "@hapi/bourne": "1.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/topo": "3.x.x" + } + }, + "node_modules/@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "deprecated": "This version has been deprecated and is no longer supported or maintained", + "dev": true, + "dependencies": { + "@hapi/hoek": "^8.3.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.5.0.tgz", + "integrity": "sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-message-util": "^25.5.0", + "jest-util": "^25.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.5.4.tgz", + "integrity": "sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==", + "dev": true, + "dependencies": { + "@jest/console": "^25.5.0", + "@jest/reporters": "^25.5.1", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^25.5.0", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-resolve-dependencies": "^25.5.4", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "jest-watcher": "^25.5.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "realpath-native": "^2.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.5.0.tgz", + "integrity": "sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/fake-timers": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.5.0.tgz", + "integrity": "sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "lolex": "^5.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/globals": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-25.5.2.tgz", + "integrity": "sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA==", + "dev": true, + "dependencies": { + "@jest/environment": "^25.5.0", + "@jest/types": "^25.5.0", + "expect": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/reporters": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.5.1.tgz", + "integrity": "sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^25.5.1", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^3.1.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^4.1.3" + }, + "engines": { + "node": ">= 8.3" + }, + "optionalDependencies": { + "node-notifier": "^6.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/source-map": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.5.0.tgz", + "integrity": "sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/test-result": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.5.0.tgz", + "integrity": "sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==", + "dev": true, + "dependencies": { + "@jest/console": "^25.5.0", + "@jest/types": "^25.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz", + "integrity": "sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/transform": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.5.1.tgz", + "integrity": "sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^25.5.0", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^3.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-regex-util": "^25.2.6", + "jest-util": "^25.5.0", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "realpath-native": "^2.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@romainberger/css-diff": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@romainberger/css-diff/-/css-diff-1.0.3.tgz", + "integrity": "sha512-zR2EvxtJvQXRxFtTnqazMsJADngyVIulzYQ+wVYWRC1Hw3e4gfEIbigX46wTsPUyjAI+lRXFrBSoCWcgZ6ZSlQ==", + "dev": true, + "dependencies": { + "lodash.merge": "^4.4.0", + "postcss": "^5.0.21" + } + }, + "node_modules/@romainberger/css-diff/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/chalk/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/@romainberger/css-diff/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@romainberger/css-diff/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@stylelint/postcss-css-in-js": { + "version": "0.37.3", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.3.tgz", + "integrity": "sha512-scLk3cSH1H9KggSniseb2KNAU5D9FWc3H7BxCSAIdtU9OWIyw0zkEZ9qEKHryRM+SExYXRKNb7tOOVNAsQ3iwg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.17.9" + }, + "peerDependencies": { + "postcss": ">=7.0.0", + "postcss-syntax": ">=0.36.2" + } + }, + "node_modules/@stylelint/postcss-markdown": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", + "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", + "deprecated": "Use the original unforked package instead: postcss-markdown", + "dev": true, + "dependencies": { + "remark": "^13.0.0", + "unist-util-find-all-after": "^3.0.2" + }, + "peerDependencies": { + "postcss": ">=7.0.0", + "postcss-syntax": ">=0.36.2" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "dev": true, + "dependencies": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.6" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@types/babel__core": { + "version": "7.1.20", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", + "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/cheerio": { + "version": "0.22.31", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", + "integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", + "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/q": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==", + "dev": true + }, + "node_modules/@types/react": { + "version": "16.14.34", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.34.tgz", + "integrity": "sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "16.9.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.17.tgz", + "integrity": "sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g==", + "dev": true, + "dependencies": { + "@types/react": "^16" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "node_modules/@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "node_modules/@types/tapable": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", + "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", + "dev": true + }, + "node_modules/@types/uglify-js": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz", + "integrity": "sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "node_modules/@types/webpack": { + "version": "4.41.33", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.33.tgz", + "integrity": "sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@types/webpack-sources": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", + "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + } + }, + "node_modules/@types/webpack-sources/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/yargs": { + "version": "15.0.14", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", + "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "dependencies": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "node_modules/@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "node_modules/@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@wordpress/babel-plugin-import-jsx-pragma": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-2.7.0.tgz", + "integrity": "sha512-yR+rSyfHKfevW84vKBOERpjEslD/o00CaYMftywVYOjsOQ8GLS6xv/VgDcpQ8JomJ9eRRInLRpeGKTM3lOa4xQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@wordpress/babel-preset-default": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-4.20.0.tgz", + "integrity": "sha512-VKPoC5We2GNxon5umOeZ7NIP4CfP7X5gqslSnNrLW4kD1XgmbVaCs2ISFF8+mObVVb6KAzbaUjI6OWljcUb5UA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.9", + "@babel/plugin-transform-react-jsx": "^7.12.7", + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/preset-env": "^7.12.7", + "@babel/runtime": "^7.12.5", + "@wordpress/babel-plugin-import-jsx-pragma": "^2.7.0", + "@wordpress/browserslist-config": "^2.7.0", + "@wordpress/element": "^2.19.0", + "@wordpress/warning": "^1.3.0", + "core-js": "^3.6.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wordpress/base-styles": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-3.1.0.tgz", + "integrity": "sha512-+HR6Cw0E95IHLixWmDCy54kMCmPxTxwAx7UTkJY/9YvOZyK8Nu3plWbX4c/6MhsASJ9RVFVhJPSJWleQ8bDEkQ==", + "dev": true + }, + "node_modules/@wordpress/browserslist-config": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-2.7.0.tgz", + "integrity": "sha512-pB45JlfmHuEigNFZ1X+CTgIsOT3/TTb9iZxw1DHXge/7ytY8FNhtcNwTfF9IgnS6/xaFRZBqzw4DyH4sP1Lyxg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wordpress/dependency-extraction-webpack-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.9.0.tgz", + "integrity": "sha512-Eo8ByPd3iZ6az4UmdLD2xYLp1/7os/H80l28Y5OlS4DozkD3vcWCBReynWoBax74u3oJ9wWN5b/8oSxGwIKXYQ==", + "dev": true, + "dependencies": { + "json2php": "^0.0.4", + "webpack-sources": "^1.3.0" + }, + "peerDependencies": { + "webpack": "^4.8.3 || ^5.0.0" + } + }, + "node_modules/@wordpress/dom": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-2.15.0.tgz", + "integrity": "sha512-eoNfM7QnrZJfdJr1DMaIi1oWlaFJ0BtHBy/0IjGhDYeZIzKRhGzCkz4vhRMwxeTPCGbG0PZg4uwPvys4Vugp9Q==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "lodash": "^4.17.19" + } + }, + "node_modules/@wordpress/element": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.20.3.tgz", + "integrity": "sha512-f4ZPTDf9CxiiOXiMxc4v1K7jcBMT4dsiehVOpkKzCDKboNXp4qVf8oe5PE23VGZNEjcOj5Mkg9hB57R0nqvMTw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.13.10", + "@types/react": "^16.9.0", + "@types/react-dom": "^16.9.0", + "@wordpress/escape-html": "^1.12.2", + "lodash": "^4.17.19", + "react": "^16.13.1", + "react-dom": "^16.13.1" + } + }, + "node_modules/@wordpress/escape-html": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-1.12.2.tgz", + "integrity": "sha512-FabgSwznhdaUwe6hr1CsGpgxQbzqEoGevv73WIL1B9GvlZ6csRWodgHfWh4P6fYqpzxFL4WYB8wPJ1PdO32XFA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@wordpress/eslint-plugin": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-HJpDYz2drtC9rY8MiYtYJ3cimioEIweGyb3P2DQTjUZ3sC4AGg+97PhXLHUdKfsFQ31JRxyLS9kKuGdDVBwWww==", + "dev": true, + "dependencies": { + "@wordpress/prettier-config": "^0.4.0", + "babel-eslint": "^10.1.0", + "cosmiconfig": "^7.0.0", + "eslint-config-prettier": "^6.10.1", + "eslint-plugin-jest": "^23.8.2", + "eslint-plugin-jsdoc": "^30.2.2", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.20.0", + "eslint-plugin-react-hooks": "^4.0.4", + "globals": "^12.0.0", + "prettier": "npm:wp-prettier@2.2.1-beta-1", + "requireindex": "^1.2.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6.9" + }, + "peerDependencies": { + "eslint": "^6 || ^7" + } + }, + "node_modules/@wordpress/eslint-plugin/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wordpress/eslint-plugin/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wordpress/jest-console": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-3.10.0.tgz", + "integrity": "sha512-iS1GSO+o7+p2PhvScOquD+IK7WqmVxa2s9uTUQyNEo06f9EUv6KNw0B1iZ00DpbgLqDCiczfdCNapC816UXIIA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "jest-matcher-utils": "^25.3.0", + "lodash": "^4.17.19" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "jest": ">=24" + } + }, + "node_modules/@wordpress/jest-preset-default": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-6.6.0.tgz", + "integrity": "sha512-9HbKUNRMUCooXAKt+6jj5SZjDMtWoR9yMb9bJ5eCd9wUfrfQ/x2nUJK/RXiv1aI85HHmzl5KfQquZF76lYEkcw==", + "dev": true, + "dependencies": { + "@jest/reporters": "^25.3.0", + "@wordpress/jest-console": "^3.10.0", + "babel-jest": "^25.3.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", + "enzyme-to-json": "^3.4.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "jest": ">=25" + } + }, + "node_modules/@wordpress/npm-package-json-lint-config": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-3.1.0.tgz", + "integrity": "sha512-SYRWpzpQaSsBUiRO+ssqg6AHjgCF4j2npstGTGaKdVs/B720fLFzeyONuMmo1ZtMb9v6MyEWxVz5ON6dDgmVYg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "npm-package-json-lint": ">=3.6.0" + } + }, + "node_modules/@wordpress/postcss-plugins-preset": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-1.6.0.tgz", + "integrity": "sha512-WPToVlX99PiUSSxSbwAR2wJtIpbcnnRkB48sIIkDvw7rCpSWkh6OLuzfj0o5g+JCYuNL1OnQXFA8EtydNEZ9Sw==", + "dev": true, + "dependencies": { + "@wordpress/base-styles": "^3.3.0", + "@wordpress/postcss-themes": "^2.6.0", + "autoprefixer": "^9.8.6", + "postcss-custom-properties": "^10.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@wordpress/postcss-plugins-preset/node_modules/@wordpress/base-styles": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-3.6.0.tgz", + "integrity": "sha512-6/vXAmc9FSX7Y17UjKgUJoVU++Pv1U1G8uMx7iClRUaLetc7/jj2DD9PTyX/cdJjHr32e3yXuLVT9wfEbo6SEg==", + "dev": true + }, + "node_modules/@wordpress/postcss-themes": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-themes/-/postcss-themes-2.6.0.tgz", + "integrity": "sha512-Q22s1KSVdtoK0Z0ND06V2QwTx/U4KvJhWFmoI8IzYW/LGlk8BkQJhHH157Y9vFliwpMlQpqfXW6/zOg2XtvHzQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.32" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wordpress/prettier-config": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-0.4.0.tgz", + "integrity": "sha512-7c4VeugkCwDkaHSD7ffxoP0VC5c///gCTEAT032OhI5Rik2dPxE3EkNAB2NhotGE8M4dMAg4g5Wj2OWZIn8TFw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@wordpress/scripts": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-12.6.1.tgz", + "integrity": "sha512-pDLtACFrP5gUA414qrE49dUrR7yMy40+//1e/5Nx821lnmDb7GAGWGo1gX4lJ2gbfSjePwmRoZe6Mph87vSnLQ==", + "dev": true, + "dependencies": { + "@svgr/webpack": "^5.2.0", + "@wordpress/babel-preset-default": "^4.20.0", + "@wordpress/dependency-extraction-webpack-plugin": "^2.9.0", + "@wordpress/eslint-plugin": "^7.4.0", + "@wordpress/jest-preset-default": "^6.6.0", + "@wordpress/npm-package-json-lint-config": "^3.1.0", + "@wordpress/postcss-plugins-preset": "^1.6.0", + "@wordpress/prettier-config": "^0.4.0", + "babel-jest": "^25.3.0", + "babel-loader": "^8.1.0", + "chalk": "^4.0.0", + "check-node-version": "^3.1.1", + "clean-webpack-plugin": "^3.0.0", + "cross-spawn": "^5.1.0", + "css-loader": "^3.5.2", + "dir-glob": "^3.0.1", + "eslint": "^7.1.0", + "eslint-plugin-markdown": "^1.0.2", + "ignore-emit-webpack-plugin": "^2.0.6", + "jest": "^25.3.0", + "jest-puppeteer": "^4.4.0", + "markdownlint": "^0.18.0", + "markdownlint-cli": "^0.21.0", + "mini-css-extract-plugin": "^0.9.0", + "minimist": "^1.2.0", + "npm-package-json-lint": "^5.0.0", + "postcss-loader": "^3.0.0", + "prettier": "npm:wp-prettier@2.2.1-beta-1", + "puppeteer": "npm:puppeteer-core@3.0.0", + "read-pkg-up": "^1.0.1", + "resolve-bin": "^0.4.0", + "sass": "^1.26.11", + "sass-loader": "^8.0.2", + "source-map-loader": "^0.2.4", + "stylelint": "^13.6.0", + "stylelint-config-wordpress": "^17.0.0", + "terser-webpack-plugin": "^3.0.3", + "thread-loader": "^2.1.3", + "url-loader": "^3.0.0", + "webpack": "^4.42.0", + "webpack-bundle-analyzer": "^3.6.1", + "webpack-cli": "^3.3.11", + "webpack-livereload-plugin": "^2.3.0" + }, + "bin": { + "wp-scripts": "bin/wp-scripts.js" + }, + "engines": { + "node": ">=10", + "npm": ">=6.9" + } + }, + "node_modules/@wordpress/warning": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-1.4.2.tgz", + "integrity": "sha512-MjrkSp6Jyfx+92AE32A83P503noUtGb6//BYUH4GiWzzzSNhDHgbQ0UcOJwJaEYK166DxSNpMk/JXc4YENi1Cw==", + "dev": true + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dev": true, + "dependencies": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/airbnb-prop-types": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", + "integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==", + "dev": true, + "dependencies": { + "array.prototype.find": "^2.1.1", + "function.prototype.name": "^1.1.2", + "is-regex": "^1.1.0", + "object-is": "^1.1.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.13.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0-alpha" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/applause": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/applause/-/applause-2.0.4.tgz", + "integrity": "sha512-wFhNjSoflbAEgelX3psyKSXV2iQFjuYW31DEhcCOD/bQ98VdfltLclK4p1mI6E58Qp4Q7+5RCbBdr+Nc9b5QhA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "optional-require": "^1.0.2" + }, + "engines": { + "node": ">=10" + }, + "optionalDependencies": { + "cson-parser": "^4.0.8", + "js-yaml": "^4.0.0" + } + }, + "node_modules/applause/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "optional": true + }, + "node_modules/applause/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "optional": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/archiver": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", + "integrity": "sha512-4q/CtGPNVyC5aT9eYHhFP7SAEjKYzQIDIJWXfexUIPNxitNs1y6hORdX+sYxERSZ6qPeNNBJ5UolFsJdWTU02g==", + "dev": true, + "dependencies": { + "archiver-utils": "^1.3.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "walkdir": "^0.0.11", + "zip-stream": "^1.1.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/archiver-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", + "integrity": "sha512-h+hTREBXcW5e1L9RihGXdH4PHHdGipG/jE2sMZrqIH6BmZAxeGU5IWjVsKhokdCSWX7km6Kkh406zZNEElHFPQ==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "graceful-fs": "^4.1.0", + "lazystream": "^1.0.0", + "lodash": "^4.8.0", + "normalize-path": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/archiver-utils/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.filter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.2.tgz", + "integrity": "sha512-us+UrmGOilqttSOgoWZTpOvHu68vZT2YCjc/H4vhu56vzZpaDFBhB+Se2UwqWzMKbDv7Myq5M5pcZLAtUvTQdQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.find": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.1.tgz", + "integrity": "sha512-I2ri5Z9uMpMvnsNrHre9l3PaX+z9D0/z6F7Yt2u15q7wt0I62g5kX6xUKR1SJiefgG+u2/gJUmM8B47XRvQR6w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true, + "optional": true + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/axe-core": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", + "integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-jest": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.5.1.tgz", + "integrity": "sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==", + "dev": true, + "dependencies": { + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz", + "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz", + "integrity": "sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz", + "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^25.5.0", + "babel-preset-current-node-syntax": "^0.1.2" + }, + "engines": { + "node": ">= 8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bfj": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", + "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.5", + "check-types": "^8.0.3", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", + "dev": true, + "dependencies": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/body/node_modules/bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", + "dev": true + }, + "node_modules/body/node_modules/raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", + "dev": true, + "dependencies": { + "bytes": "1", + "string_decoder": "0.10" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/body/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "dependencies": { + "resolve": "1.1.7" + } + }, + "node_modules/browser-resolve/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001435", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001435.tgz", + "integrity": "sha512-kdCkUTjR+v4YAJelyiDTqiu82BDr4W4CP5sgTA0ZBmqn30XfS2ZghPLMowik9TPhS+psWJiUNxsqLyurDbmutA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "dependencies": { + "rsvp": "^4.8.4" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chart.js": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", + "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", + "dependencies": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "node_modules/chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "dependencies": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "node_modules/chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/chartjs-color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/chartjs-color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/check-node-version": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-3.3.0.tgz", + "integrity": "sha512-OAtp7prQf+8YYKn2UB/fK1Ppb9OT+apW56atoKYUvucYLPq69VozOY0B295okBwCKymk2cictrS3qsdcZwyfzw==", + "dev": true, + "dependencies": { + "chalk": "^2.3.0", + "map-values": "^1.0.1", + "minimist": "^1.2.0", + "object-filter": "^1.0.2", + "object.assign": "^4.0.4", + "run-parallel": "^1.1.4", + "semver": "^5.0.3" + }, + "bin": { + "check-node-version": "bin.js" + } + }, + "node_modules/check-node-version/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-node-version/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-node-version/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/check-node-version/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/check-node-version/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/check-node-version/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-node-version/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/check-node-version/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-types": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", + "dev": true + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", + "dev": true, + "dependencies": { + "@types/webpack": "^4.4.31", + "del": "^4.1.1" + }, + "engines": { + "node": ">=8.9.0" + }, + "peerDependencies": { + "webpack": "*" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "dev": true, + "dependencies": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-regexp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", + "dev": true, + "dependencies": { + "is-regexp": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/coa/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/coa/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/coa/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/coa/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true, + "optional": true, + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-parser": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz", + "integrity": "sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/compress-commons": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", + "integrity": "sha512-SLTU8iWWmcORfUN+4351Z2aZXKJe1tr0jSilPMCZlLPzpdTXnkBW1LevW/MfuANBKJek8Xu9ggqrtVmQrChLtg==", + "dev": true, + "dependencies": { + "buffer-crc32": "^0.2.1", + "crc32-stream": "^2.0.0", + "normalize-path": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/compress-commons/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "optional": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "dependencies": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "node_modules/copy-concurrently/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/copy-concurrently/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.0.tgz", + "integrity": "sha512-1s/VbhIX73FBFBYF4D0KdeBLkjEnAlCQn0Ufo2a/IyJ41jHpQ9ZzM4JAfbE7yTOhbmwRFkARErJ/XIiLceja6Q==", + "dev": true, + "dependencies": { + "cacache": "^15.0.5", + "fast-glob": "^3.2.4", + "find-cache-dir": "^3.3.1", + "glob-parent": "^5.1.1", + "globby": "^11.0.1", + "loader-utils": "^2.0.0", + "normalize-path": "^3.0.0", + "p-limit": "^3.0.2", + "schema-utils": "^2.7.1", + "serialize-javascript": "^5.0.1", + "webpack-sources": "^1.4.3" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + } + }, + "node_modules/core-js": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", + "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", + "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc32-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", + "integrity": "sha512-UjZSqFCbn+jZUHJIh6Y3vMF7EJLcJWNm4tKDf2peJRwlZKHvkkvOMTvAei6zjU9gO1xONVr3rRFw0gixm2eUng==", + "dev": true, + "dependencies": { + "crc": "^3.4.4", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-env": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-env/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-env/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-env/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-env/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/cross-spawn/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/cross-spawn/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "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", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "optional": true, + "dependencies": { + "coffeescript": "1.12.7" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + }, + "engines": { + "node": ">4" + } + }, + "node_modules/css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/css-loader/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/css-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/css-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-preset-default": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", + "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.3", + "postcss-unique-selectors": "^4.0.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cssnano/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, + "node_modules/cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "dev": true, + "dependencies": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", + "dev": true + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/del/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "optional": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "dev": true, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "dependencies": { + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/enhanced-resolve/node_modules/memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "dev": true, + "dependencies": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/enzyme-adapter-react-16": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.7.tgz", + "integrity": "sha512-LtjKgvlTc/H7adyQcj+aq0P0H07LDL480WQl1gU512IUyaDo/sbOaNDdZsJXYW2XaoPqrLLE9KbZS+X2z6BASw==", + "dev": true, + "dependencies": { + "enzyme-adapter-utils": "^1.14.1", + "enzyme-shallow-equal": "^1.0.5", + "has": "^1.0.3", + "object.assign": "^4.1.4", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "react-is": "^16.13.1", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "peerDependencies": { + "enzyme": "^3.0.0", + "react": "^16.0.0-0", + "react-dom": "^16.0.0-0" + } + }, + "node_modules/enzyme-adapter-react-16/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/enzyme-adapter-utils": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.1.tgz", + "integrity": "sha512-JZgMPF1QOI7IzBj24EZoDpaeG/p8Os7WeBZWTJydpsH7JRStc7jYbHE4CmNQaLqazaGFyLM8ALWA3IIZvxW3PQ==", + "dev": true, + "dependencies": { + "airbnb-prop-types": "^2.16.0", + "function.prototype.name": "^1.1.5", + "has": "^1.0.3", + "object.assign": "^4.1.4", + "object.fromentries": "^2.0.5", + "prop-types": "^15.8.1", + "semver": "^5.7.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "peerDependencies": { + "react": "0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0" + } + }, + "node_modules/enzyme-adapter-utils/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/enzyme-shallow-equal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.5.tgz", + "integrity": "sha512-i6cwm7hN630JXenxxJFBKzgLC3hMTafFQXflvzHgPmDhOBhxUWDe8AeRv1qp2/uWJ2Y8z5yLWMzmAfkTOiOCZg==", + "dev": true, + "dependencies": { + "has": "^1.0.3", + "object-is": "^1.1.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/enzyme-to-json": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.2.tgz", + "integrity": "sha512-Ynm6Z6R6iwQ0g2g1YToz6DWhxVnt8Dy1ijR2zynRKxTyBGA8rCDXU3rs2Qc4OKvUvc2Qoe1bcFK6bnPs20TrTg==", + "dev": true, + "dependencies": { + "@types/cheerio": "^0.22.22", + "lodash": "^4.17.21", + "react-is": "^16.12.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "enzyme": "^3.4.0" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "dependencies": { + "string-template": "~0.2.1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", + "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "dependencies": { + "get-stdin": "^6.0.0" + }, + "bin": { + "eslint-config-prettier-check": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=3.14.1" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "23.20.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz", + "integrity": "sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "^2.5.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "30.7.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.13.tgz", + "integrity": "sha512-YM4WIsmurrp0rHX6XiXQppqKB8Ne5ATiZLJe2+/fkp9l9ExXFr43BbAbjZaVrpCT+tuPYOZ8k1MICARHnURUNQ==", + "dev": true, + "dependencies": { + "comment-parser": "^0.7.6", + "debug": "^4.3.1", + "jsdoctypeparser": "^9.0.0", + "lodash": "^4.17.20", + "regextras": "^0.7.1", + "semver": "^7.3.4", + "spdx-expression-parse": "^3.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", + "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.18.9", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.3", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.2", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-markdown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-1.0.2.tgz", + "integrity": "sha512-BfvXKsO0K+zvdarNc801jsE/NTLmig4oKhZ1U3aSUgTf2dB/US5+CrfGxMsCK2Ki1vS1R3HPok+uYpufFndhzw==", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "remark-parse": "^5.0.0", + "unified": "^6.1.2" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.31.11", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz", + "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/exec-sh": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", + "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", + "dev": true + }, + "node_modules/execa": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": "^8.12.0 || >=9.7.0" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execall": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", + "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", + "dev": true, + "dependencies": { + "clone-regexp": "^2.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz", + "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "ansi-styles": "^4.0.0", + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-regex-util": "^25.2.6" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/expect-puppeteer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.4.0.tgz", + "integrity": "sha512-6Ey4Xy2xvmuQu7z7YQtMsaMV0EHJRpVxIDOd5GRrm04/I3nkTKIutELfECsLp6le+b3SSa3cXhPiw6PgqzxYWA==", + "dev": true + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", + "dev": true + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "dev": true, + "dependencies": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-parent-dir": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz", + "integrity": "sha512-o4UcykWV/XN9wm+jMEtWLPlV8RXCZnMhQI6F6OdHeSez7iiJWePw8ijOlskJZMsaQoGR/b7dH6lO02HhaTN7+A==", + "dev": true + }, + "node_modules/find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "dev": true, + "dependencies": { + "find-file-up": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-process": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", + "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", + "integrity": "sha512-Udxo3C9A6alt2GZ2MNsgnIvX7De0V3VGxeP/x98NSVgSlizcDHdmJza61LI7zJy4OEtSiJyE72s0/+tBl5/ZxA==", + "dev": true, + "dependencies": { + "colors": "~0.6.0-1", + "commander": "~2.1.0" + }, + "bin": { + "findup": "bin/findup.js" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha512-z8Nrwhi6wzxNMIbxlrTzuUW6KWuKkogZ/7OdDVq+0+kxn77KUH1nipx8iU6suqkHqc4y6n7a9A8IpmxY/pTjWg==", + "dev": true, + "dependencies": { + "glob": "~5.0.0" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/findup-sync/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/findup/node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/findup/node_modules/commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha512-J2wnb6TKniXNOtoHS8TSrG9IOQluPrsmyAJ8oCUJOBmv+uLBCyPYAZkD2jFvw2DCzIXNnISIM01NIvr35TkBMQ==", + "dev": true, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/flot": { + "version": "0.8.0-alpha", + "resolved": "https://registry.npmjs.org/flot/-/flot-0.8.0-alpha.tgz", + "integrity": "sha512-LMXH1jkhEcEOHdjF0sC0AuF1adcv8vx/VCdZEmuceWqQtw/ZJz23jgvsS+sS2QVGneV/lkVfz4H7yHjzfZoB/g==" + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "optional": true, + "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" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/getobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", + "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "optional": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "dev": true, + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-modules/node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", + "dev": true + }, + "node_modules/growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", + "dev": true, + "optional": true + }, + "node_modules/grunt": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.3.tgz", + "integrity": "sha512-mKwmo4X2d8/4c/BmcOETHek675uOqw0RuA/zy12jaspWqvTp4+ZeQF1W+OTpcbncnaBsfbQJ6l0l4j+Sn/GmaQ==", + "dev": true, + "dependencies": { + "dateformat": "~3.0.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~0.3.0", + "glob": "~7.1.6", + "grunt-cli": "~1.4.3", + "grunt-known-options": "~2.0.0", + "grunt-legacy-log": "~3.0.0", + "grunt-legacy-util": "~2.0.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "mkdirp": "~1.0.4", + "nopt": "~3.0.6", + "rimraf": "~3.0.2" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/grunt-checktextdomain": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt-checktextdomain/-/grunt-checktextdomain-1.0.1.tgz", + "integrity": "sha512-WX6EMOnvrhj8MDqmWJAlZRpM1rE/Mmqy9DCYYeWB1pFV+JotP6pNx2uO58+uHFOEFTcoridr4ECJ6aqGnLmgJw==", + "dev": true, + "dependencies": { + "chalk": "~0.2.1", + "text-table": "~0.2.0" + }, + "engines": { + "node": ">= 0.8.0" + }, + "peerDependencies": { + "grunt": ">=0.4.1" + } + }, + "node_modules/grunt-checktextdomain/node_modules/ansi-styles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", + "integrity": "sha512-YyQBeLj0juxUC9uUXRpQ1ZAzPT1dnsn5vVeJLHYFq4Ct1p0rymUSyvckKCXCH9I0bh3jWDIETA5nXIaZVKlDyA==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-checktextdomain/node_modules/chalk": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.2.1.tgz", + "integrity": "sha512-nmVapomwGksziCuynboy7I+dtW4ytIdqXPlrfY/ySx8l8EqFRGHyA04q6NMNpOri8XliGUGwXyfScVl48zFHbw==", + "dev": true, + "dependencies": { + "ansi-styles": "~0.2.0", + "has-color": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-cli": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", + "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", + "dev": true, + "dependencies": { + "grunt-known-options": "~2.0.0", + "interpret": "~1.1.0", + "liftup": "~3.0.1", + "nopt": "~4.0.1", + "v8flags": "~3.2.0" + }, + "bin": { + "grunt": "bin/grunt" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-cli/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/grunt-composer": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt-composer/-/grunt-composer-0.4.5.tgz", + "integrity": "sha512-TqTxpedQiDlP9rrOSJpEFLT0Hr6njsm3306+jHK8C8EdoHXTkzFl2LiJE09dz/uyH34O7MXVG4K6WBYOYrxw3g==", + "dev": true, + "dependencies": { + "shelljs": "~0.2.6" + }, + "engines": { + "node": ">= 0.8.0" + }, + "peerDependencies": { + "grunt": ">=0.4.0" + } + }, + "node_modules/grunt-contrib-clean": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz", + "integrity": "sha512-tET+TYTd8vCtKeGwbLjoH8+SdI8ngVzGbPr7vlWkewG7mYYHIccd2Ldxq+PK3DyBp5Www3ugdkfsjoNKUl5MTg==", + "dev": true, + "dependencies": { + "async": "^1.5.2", + "rimraf": "^2.5.1" + }, + "engines": { + "node": ">= 0.10.0" + }, + "peerDependencies": { + "grunt": ">=0.4.5" + } + }, + "node_modules/grunt-contrib-clean/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/grunt-contrib-compress": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-compress/-/grunt-contrib-compress-1.6.0.tgz", + "integrity": "sha512-wIFuvk+/Ny4E+OgEfJYFZgoH7KcU/nnNFbYasB7gRvrcRyW6vmTp3Pj8a4rFSR3tbFMjrGvTUszdO6fgLajgZQ==", + "dev": true, + "dependencies": { + "archiver": "^1.3.0", + "chalk": "^1.1.1", + "lodash": "^4.7.0", + "pretty-bytes": "^4.0.2", + "stream-buffers": "^2.1.0" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "iltorb": "^2.4.3" + } + }, + "node_modules/grunt-contrib-compress/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-compress/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-compress/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-compress/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-compress/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-compress/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-copy/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-force-task": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-force-task/-/grunt-force-task-2.0.0.tgz", + "integrity": "sha512-GHh+jKzGFaTGIiAq4ln+42gJSWGiz3l3qAqiVHRgVtRUkWEuKqAnFedmuxlC2CBS9938DYb0iCihPJH8haSPyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/grunt-known-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", + "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-legacy-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", + "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "dev": true, + "dependencies": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/grunt-legacy-log-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", + "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "dev": true, + "dependencies": { + "chalk": "~4.1.0", + "lodash": "~4.17.19" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-legacy-util": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", + "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "dev": true, + "dependencies": { + "async": "~3.2.0", + "exit": "~0.1.2", + "getobject": "~1.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.21", + "underscore.string": "~3.3.5", + "which": "~2.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt-legacy-util/node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/grunt-legacy-util/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/grunt-replace": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/grunt-replace/-/grunt-replace-2.0.2.tgz", + "integrity": "sha512-cv7ua3vBeUpCBkb+L4vZ8hwE8Sz6Sf/mb3031kufKVfKAestekHSsDO0BK63mhrRknsAHlnkHkieIxrjHS/fJA==", + "dev": true, + "dependencies": { + "applause": "^2.0.0", + "chalk": "^4.1.0", + "file-sync-cmp": "^0.1.0", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/grunt/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/grunt/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "optional": true + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==", + "dev": true + }, + "node_modules/hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==", + "dev": true + }, + "node_modules/html-element-map": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz", + "integrity": "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==", + "dev": true, + "dependencies": { + "array.prototype.filter": "^1.0.0", + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.1" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.14" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", + "dev": true + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-emit-webpack-plugin": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/ignore-emit-webpack-plugin/-/ignore-emit-webpack-plugin-2.0.6.tgz", + "integrity": "sha512-/zC18RWCC2wz4ZwnS4UoujGWzvSKy28DLjtE+jrGBOXej6YdmityhBDzE8E0NlktEqi4tgdNbydX8B6G4haHSQ==", + "dev": true + }, + "node_modules/iltorb": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/iltorb/-/iltorb-2.4.5.tgz", + "integrity": "sha512-EMCMl3LnnNSZJS5QrxyZmMTaAC4+TJkM5woD+xbpm9RB+mFYCr7C05GFE3TEGCsVQSVHmjX+3sf5AiwsylNInQ==", + "deprecated": "The zlib module provides APIs for brotli compression/decompression starting with Node.js v10.16.0, please use it over iltorb", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "nan": "^2.14.0", + "npmlog": "^4.1.2", + "prebuild-install": "^5.3.3", + "which-pm-runs": "^1.0.0" + } + }, + "node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "node_modules/import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg==", + "dev": true, + "dependencies": { + "import-from": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha512-0vdnLL2wSGnhlRmzHJAg5JHjt1l2vYhzJ7tNLGbeVg0fse56tpGaH0uzH+r9Slej+BSXXEHvBKDEnVSLLE9/+w==", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==", + "dev": true + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", + "dev": true + }, + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/irregular-plurals": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", + "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==", + "dev": true, + "dependencies": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "optional": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==", + "dev": true + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "optional": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.4.tgz", + "integrity": "sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==", + "dev": true, + "dependencies": { + "@jest/core": "^25.5.4", + "import-local": "^3.0.2", + "jest-cli": "^25.5.4" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-changed-files": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.5.0.tgz", + "integrity": "sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "execa": "^3.2.0", + "throat": "^5.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-cli": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.5.4.tgz", + "integrity": "sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==", + "dev": true, + "dependencies": { + "@jest/core": "^25.5.4", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^25.5.4", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "prompts": "^2.0.1", + "realpath-native": "^2.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.5.4.tgz", + "integrity": "sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^25.5.4", + "@jest/types": "^25.5.0", + "babel-jest": "^25.5.1", + "chalk": "^3.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^25.5.0", + "jest-environment-node": "^25.5.0", + "jest-get-type": "^25.2.6", + "jest-jasmine2": "^25.5.4", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "micromatch": "^4.0.2", + "pretty-format": "^25.5.0", + "realpath-native": "^2.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-dev-server": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-4.4.0.tgz", + "integrity": "sha512-STEHJ3iPSC8HbrQ3TME0ozGX2KT28lbT4XopPxUm2WimsX3fcB3YOptRh12YphQisMhfqNSNTZUmWyT3HEXS2A==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "cwd": "^0.10.0", + "find-process": "^1.4.3", + "prompts": "^2.3.0", + "spawnd": "^4.4.0", + "tree-kill": "^1.2.2", + "wait-on": "^3.3.0" + } + }, + "node_modules/jest-dev-server/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-25.3.0.tgz", + "integrity": "sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-each": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz", + "integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz", + "integrity": "sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A==", + "dev": true, + "dependencies": { + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "jsdom": "^15.2.1" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-environment-node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.5.0.tgz", + "integrity": "sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA==", + "dev": true, + "dependencies": { + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-environment-puppeteer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.0.tgz", + "integrity": "sha512-iV8S8+6qkdTM6OBR/M9gKywEk8GDSOe05hspCs5D8qKSwtmlUfdtHfB4cakdc68lC6YfK3AUsLirpfgodCHjzQ==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "cwd": "^0.10.0", + "jest-dev-server": "^4.4.0", + "merge-deep": "^3.0.2" + } + }, + "node_modules/jest-environment-puppeteer/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-haste-map": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz", + "integrity": "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "@types/graceful-fs": "^4.1.2", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-serializer": "^25.5.0", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7", + "which": "^2.0.2" + }, + "engines": { + "node": ">= 8.3" + }, + "optionalDependencies": { + "fsevents": "^2.1.2" + } + }, + "node_modules/jest-haste-map/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/jest-jasmine2": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz", + "integrity": "sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^25.5.0", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "co": "^4.6.0", + "expect": "^25.5.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^25.5.0", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0", + "throat": "^5.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-jasmine2/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-leak-detector": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz", + "integrity": "sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA==", + "dev": true, + "dependencies": { + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-matcher-utils": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz", + "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "jest-diff": "^25.5.0", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", + "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^1.0.1" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.5.0.tgz", + "integrity": "sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-puppeteer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-4.4.0.tgz", + "integrity": "sha512-ZaiCTlPZ07B9HW0erAWNX6cyzBqbXMM7d2ugai4epBDKpKvRDpItlRQC6XjERoJELKZsPziFGS0OhhUvTvQAXA==", + "dev": true, + "dependencies": { + "expect-puppeteer": "^4.4.0", + "jest-environment-puppeteer": "^4.4.0" + }, + "peerDependencies": { + "puppeteer": ">= 1.5.0 < 3" + } + }, + "node_modules/jest-regex-util": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", + "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", + "dev": true, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-resolve": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.5.1.tgz", + "integrity": "sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "browser-resolve": "^1.11.3", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.1", + "read-pkg-up": "^7.0.1", + "realpath-native": "^2.0.0", + "resolve": "^1.17.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz", + "integrity": "sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-snapshot": "^25.5.1" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-resolve/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.5.4.tgz", + "integrity": "sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==", + "dev": true, + "dependencies": { + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-docblock": "^25.3.0", + "jest-haste-map": "^25.5.1", + "jest-jasmine2": "^25.5.4", + "jest-leak-detector": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", + "jest-runtime": "^25.5.4", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.5.4.tgz", + "integrity": "sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==", + "dev": true, + "dependencies": { + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/globals": "^25.5.2", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "realpath-native": "^2.0.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "jest-runtime": "bin/jest-runtime.js" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-serializer": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.5.0.tgz", + "integrity": "sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-snapshot": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.5.1.tgz", + "integrity": "sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/prettier": "^1.19.0", + "chalk": "^3.0.0", + "expect": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-diff": "^25.5.0", + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", + "make-dir": "^3.0.0", + "natural-compare": "^1.4.0", + "pretty-format": "^25.5.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", + "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "make-dir": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.5.0.tgz", + "integrity": "sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q==", + "dev": true, + "dependencies": { + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "jest-util": "^25.5.0", + "string-length": "^3.1.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "dev": true, + "dependencies": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/jquery": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + }, + "node_modules/jquery-creditcardvalidator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jquery-creditcardvalidator/-/jquery-creditcardvalidator-1.0.0.tgz", + "integrity": "sha512-fuXWRPGMPO6UoLj1HwnENG7WwyLr43QNupS81VpC4NEDF8DAtB5pH4lyyZ7ApzgIxvn9LKtvsV/ilL963QzoFQ==" + }, + "node_modules/jquery-validation": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.5.tgz", + "integrity": "sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==", + "peerDependencies": { + "jquery": "^1.7 || ^2.0 || ^3.1" + } + }, + "node_modules/jquery.payment": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jquery.payment/-/jquery.payment-3.0.0.tgz", + "integrity": "sha512-b02U7bf5HIhmfwsODomtLe119VeZ4c5hBvyKkdV3Ih4tTrD08sEPZuhgrX0Sv9iWIb08vNWn4RpaCyZ48mhOJA==", + "dependencies": { + "jquery": ">=1.7" + } + }, + "node_modules/js-base64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/jsdoctypeparser": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", + "integrity": "sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==", + "dev": true, + "bin": { + "jsdoctypeparser": "bin/jsdoctypeparser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsdom": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", + "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", + "dev": true, + "dependencies": { + "abab": "^2.0.0", + "acorn": "^7.1.0", + "acorn-globals": "^4.3.2", + "array-equal": "^1.0.0", + "cssom": "^0.4.1", + "cssstyle": "^2.0.0", + "data-urls": "^1.1.0", + "domexception": "^1.0.1", + "escodegen": "^1.11.1", + "html-encoding-sniffer": "^1.0.2", + "nwsapi": "^2.2.0", + "parse5": "5.1.0", + "pn": "^1.1.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "saxes": "^3.1.9", + "symbol-tree": "^3.2.2", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.1.2", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^7.0.0", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json2php": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/json2php/-/json2php-0.0.4.tgz", + "integrity": "sha512-hFzejhs28f70sGnutcsRS459MnAsjRVI85RgPAL1KQIZEpjiDitc27CZv4IgOtaR86vrqOVlu9vJNew2XyTH4g==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", + "dev": true + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz", + "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", + "dev": true + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.6.tgz", + "integrity": "sha512-HNkaCgM8wZgE/BZACeotAAgpL9FUjEnhgF0FVQMIgH//zqTPreLYMb3rWYkYAqPoF75Jwuycp1da7uz66cfFQg==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/liftup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", + "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^4.0.0", + "fined": "^1.2.0", + "flagged-respawn": "^1.0.1", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.1", + "rechoir": "^0.7.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/liftup/node_modules/findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/liftup/node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftup/node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftup/node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, + "node_modules/load-grunt-tasks": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-3.5.2.tgz", + "integrity": "sha512-dwBbJ+Fmf1IrtASRdgT/KJNtczdlm+R3iLSi8KOGdCGl4V05uA055JHtafIXTyk5EJ1zDZbmEHndOQwU6uj8Jw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "multimatch": "^2.0.0", + "pkg-up": "^1.0.0", + "resolve-pkg": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "grunt": ">=0.4.0" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/locutus": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.16.tgz", + "integrity": "sha512-pGfl6Hb/1mXLzrX5kl5lH7gz25ey0vwQssZp8Qo2CEF59di6KrAgdFm+0pW8ghLnvNzzJGj5tlWhhv2QbK3jeQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.differencewith": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz", + "integrity": "sha512-/8JFjydAS+4bQuo3CpLMBv7WxGFyk7/etOAsrQUCu0a9QVDemxv0YQ0rFyeZvqlUD314SERfNlgnlqqHmaQ0Cg==", + "dev": true + }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-iterator/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-values/-/map-values-1.0.1.tgz", + "integrity": "sha512-BbShUnr5OartXJe1GeccAWtfro11hhgNJg6G9/UtWKjVGvV5U4C09cg5nk8JUevhXODaXY+hQ3xxMUKSs62ONQ==", + "dev": true + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, + "node_modules/markdownlint": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.18.0.tgz", + "integrity": "sha512-nQAfK9Pbq0ZRoMC/abNGterEnV3kL8MZmi0WHhw8WJKoIbsm3cXGufGsxzCRvjW15cxe74KWcxRSKqwplS26Bw==", + "dev": true, + "dependencies": { + "markdown-it": "10.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/markdownlint-cli": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.21.0.tgz", + "integrity": "sha512-gvnczz3W3Wgex851/cIQ/2y8GNhY+EVK8Ael8kRd8hoSQ0ps9xjhtwPwMyJPoiYbAoPxG6vSBFISiysaAbCEZg==", + "dev": true, + "dependencies": { + "commander": "~2.9.0", + "deep-extend": "~0.5.1", + "get-stdin": "~5.0.1", + "glob": "~7.1.2", + "ignore": "~5.1.4", + "js-yaml": "~3.13.1", + "jsonc-parser": "~2.2.0", + "lodash.differencewith": "~4.5.0", + "lodash.flatten": "~4.4.0", + "markdownlint": "~0.18.0", + "markdownlint-rule-helpers": "~0.6.0", + "minimatch": "~3.0.4", + "rc": "~1.2.7" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/markdownlint-cli/node_modules/commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", + "dev": true, + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/markdownlint-cli/node_modules/get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/markdownlint-cli/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/markdownlint-cli/node_modules/ignore": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", + "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/markdownlint-cli/node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/markdownlint-cli/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/markdownlint-rule-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.6.0.tgz", + "integrity": "sha512-LiZVAbg9/cqkBHtLNNqHV3xuy4Y2L/KuGU6+ZXqCT9NnCdEkIoxeI5/96t+ExquBY0iHy2CVWxPH16nG1RKQVQ==", + "dev": true + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/meow": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", + "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-deep": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", + "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^4.4.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minimist-options/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "dependencies": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mississippi/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "dev": true, + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/moment": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", + "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", + "integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true + }, + "node_modules/move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", + "dev": true, + "dependencies": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "node_modules/move-concurrently/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/move-concurrently/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha512-0mzK8ymiWdehTBiJh0vClAzGyQbdtyWqzSVx//EK4N/D+599RFlGfTAsKw2zMSABtDG9C6Ul2+t8f2Lbdjf5mA==", + "dev": true, + "dependencies": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/multimatch/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dev": true, + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/nearley/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dev": true, + "optional": true, + "dependencies": { + "semver": "^5.4.1" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/node-libs-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/node-notifier": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz", + "integrity": "sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==", + "dev": true, + "optional": true, + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^2.1.1", + "semver": "^6.3.0", + "shellwords": "^0.1.1", + "which": "^1.3.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==", + "dev": true, + "optional": true + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-selector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", + "integrity": "sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==", + "dev": true + }, + "node_modules/normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-package-json-lint": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-5.4.2.tgz", + "integrity": "sha512-DH1MSvYvm+cuQFXcPehIIu/WiYzMYs7BOxlhOOFHaH2SNrA+P2uDtTEe5LOG90Ci7PTwgF/dCmSKM2HWTgWXNA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.6", + "ajv-errors": "^1.0.1", + "chalk": "^4.1.2", + "cosmiconfig": "^7.0.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "ignore": "^5.1.9", + "is-plain-obj": "^3.0.0", + "jsonc-parser": "^3.0.0", + "log-symbols": "^4.1.0", + "meow": "^6.1.1", + "plur": "^4.0.0", + "semver": "^7.3.5", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "npmPkgJsonLint": "src/cli.js" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/npm-package-json-lint/node_modules/ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-package-json-lint/node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/npm-package-json-lint/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", + "dev": true + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-filter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz", + "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.defaults/node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "dev": true, + "dependencies": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map/node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optional-require": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", + "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "dev": true, + "dependencies": { + "require-at": "^1.0.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "dependencies": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true, + "optional": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", + "integrity": "sha512-L+d849d9lz20hnRpUnWBRXOh+mAvygQpK7UuXiw+6QbPwL55RVgl+G+V936wCzs/6J7fj0pvgLY9OknZ+FqaNA==", + "dev": true, + "dependencies": { + "find-up": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plur": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", + "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", + "dev": true, + "dependencies": { + "irregular-plurals": "^3.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", + "dev": true, + "dependencies": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-colormin/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-convert-values/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-custom-properties": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-10.0.0.tgz", + "integrity": "sha512-55BPj5FudpCiPZzBaO+MOeqmwMDa+nV9/0QBJBfhZjYg6D9hE+rW9lpMBLTJoF4OTXnS5Po4yM1nMlgkPbCxFg==", + "dev": true, + "dependencies": { + "postcss": "^7.0.17", + "postcss-values-parser": "^4.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-html": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", + "dev": true, + "dependencies": { + "htmlparser2": "^3.10.0" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "node_modules/postcss-html/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/postcss-html/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/postcss-html/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/postcss-html/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/postcss-html/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/postcss-html/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/postcss-html/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/postcss-html/node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/postcss-html/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-less": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.14" + }, + "engines": { + "node": ">=6.14.4" + } + }, + "node_modules/postcss-load-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", + "dev": true, + "dependencies": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-load-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-load-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-load-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-load-config/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/postcss-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-loader/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "dependencies": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-merge-longhand/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-font-values/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-gradients/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "dependencies": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-params/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "dependencies": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "dependencies": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "dependencies": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "dependencies": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-display-values/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-positions/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-repeat-style/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "dependencies": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-string/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "dependencies": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-timing-functions/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-unicode/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "dev": true, + "dependencies": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-url/node_modules/normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/postcss-normalize-url/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-normalize-whitespace/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "dev": true, + "dependencies": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-ordered-values/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "dependencies": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-reduce-transforms/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-rtl": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/postcss-rtl/-/postcss-rtl-1.7.3.tgz", + "integrity": "sha512-PMwlrQSeZKChNJQGtWz9Xfk3rY1W7P5Jp4sFRXVufczQIH6vRhTNSc5gnEwKHaWrU8SMoZMi2VY7ihOmwVvW7g==", + "dev": true, + "dependencies": { + "rtlcss": "2.5.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "dev": true, + "dependencies": { + "postcss": "^7.0.26" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/postcss-sass": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz", + "integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==", + "dev": true, + "dependencies": { + "gonzales-pe": "^4.3.0", + "postcss": "^7.0.21" + } + }, + "node_modules/postcss-scss": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz", + "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", + "dev": true, + "dependencies": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-svgo/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true, + "peerDependencies": { + "postcss": ">=5.0.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "dev": true, + "dependencies": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/postcss-values-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-4.0.0.tgz", + "integrity": "sha512-R9x2D87FcbhwXUmoCXJR85M1BLII5suXRuXibGYyBJ7lVDEpRIdKZh4+8q5S+/+A4m0IoG1U5tFw39asyhX/Hw==", + "dev": true, + "dependencies": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "postcss": "^7.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz", + "integrity": "sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==", + "dev": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "name": "wp-prettier", + "version": "2.2.1-beta-1", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", + "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-bytes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "integrity": "sha512-yJAF+AjbHKlxQ8eezMd/34Mnj/YTQ3i6kLzvVsH4l/BfIFtp444n0wVbnsn66JimZ9uBofv815aRp1zCppxlWw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "dependencies": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "dev": true, + "dependencies": { + "has": "^1.0.3", + "object.assign": "^4.1.0", + "reflect.ownkeys": "^0.2.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "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", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "name": "puppeteer-core", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-3.0.0.tgz", + "integrity": "sha512-oWjZFGMc0q2ak+8OxdmMffS79LIT0UEtmpV4h1/AARvESIqqKljf8mrfP+dQ2kas7XttsAZIxRBuWu7Y5JH8KQ==", + "dev": true, + "dependencies": { + "@types/mime-types": "^2.1.0", + "debug": "^4.1.0", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "mime-types": "^2.1.25", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dev": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "dev": true + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/react-test-renderer": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", + "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "react-is": "^16.8.6", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/realpath-native": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz", + "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha512-qOLsBKHCpSOFKK1NUOCGC5VyeufB6lEsFe92AL2bhIJsacZS1qdoOZSbPk3MYKuT2cFlRDnulKXuuElIrMjGUg==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regextras": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", + "integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==", + "dev": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/remark": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", + "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", + "dev": true, + "dependencies": { + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.0", + "unified": "^9.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", + "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "dev": true, + "dependencies": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, + "node_modules/remark-stringify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "dev": true, + "dependencies": { + "mdast-util-to-markdown": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/remark/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/remark/node_modules/remark-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/unified": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", + "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "dev": true, + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.19" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.12.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/require-at": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-bin": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/resolve-bin/-/resolve-bin-0.4.3.tgz", + "integrity": "sha512-9u8TMpc+SEHXxQXblXHz5yRvRZERkCZimFN9oz85QI3uhkh7nqfjm6OGTLg+8vucpXGcY4jLK6WkylPmt7GSvw==", + "dev": true, + "dependencies": { + "find-parent-dir": "~0.3.0" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "dev": true, + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-dir/node_modules/expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-0.1.0.tgz", + "integrity": "sha512-x11rPP22t6W9p+eSOhDeT6whjFGmsZQf76rDq5gtbgh9UdBxZeR1PuW6sYkCyN/IrD2hpJ0yYn0hE1toHAadEw==", + "dev": true, + "dependencies": { + "resolve-from": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg/node_modules/resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==", + "dev": true + }, + "node_modules/rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==", + "dev": true, + "dependencies": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, + "node_modules/rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true, + "engines": { + "node": "6.* || >= 7.*" + } + }, + "node_modules/rtlcss": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.5.0.tgz", + "integrity": "sha512-NCVdF45w70/3CQeqVvQ84bu2HN8agNn+CDjw+RxXaiWb7mPOmEvltdd1z4qzm9kin4Jnu9ShFBIx28yvWerZ2g==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "findup": "^0.1.5", + "mkdirp": "^0.5.1", + "postcss": "^6.0.23", + "strip-json-comments": "^2.0.0" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + } + }, + "node_modules/rtlcss/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rtlcss/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rtlcss/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/rtlcss/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/rtlcss/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/rtlcss/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rtlcss/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/rtlcss/node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/rtlcss/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rtlcss/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", + "dev": true, + "dependencies": { + "aproba": "^1.1.1" + } + }, + "node_modules/rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "deprecated": "some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added", + "dev": true, + "dependencies": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "bin": { + "sane": "src/cli.js" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/sane/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/sane/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/sane/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sane/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sane/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sane/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sane/node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/sane/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/sane/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sane/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sass": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz", + "integrity": "sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", + "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.6.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0", + "sass": "^1.3.0", + "webpack": "^4.36.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/sass-loader/node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sass-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/sass-loader/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sass-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/sass-loader/node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/saxes": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", + "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", + "dev": true, + "dependencies": { + "xmlchars": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", + "dev": true, + "dependencies": { + "is-buffer": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shelljs": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.2.6.tgz", + "integrity": "sha512-LQiM15qPbSyzHDFfI4v7EVhjBXG5PUAKWVBnVMBXwdlQSHZtzKYeKGzDHBIqpenPrCsPWqBSOF5o7oSvSfX+CA==", + "dev": true, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "optional": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "dependencies": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/source-map-loader/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/source-map-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/source-map-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/spawnd": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.4.0.tgz", + "integrity": "sha512-jLPOfB6QOEgMOQY15Z6+lwZEhH3F5ncXxIaZ7WHPIapwNNLyjrs61okj3VJ3K6tmP5TZ6cO0VAu9rEY4MD4YQg==", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "signal-exit": "^3.0.2", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.7" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/specificity": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "dev": true, + "bin": { + "specificity": "bin/specificity" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true + }, + "node_modules/stack-utils": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", + "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-length": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", + "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", + "dev": true, + "dependencies": { + "astral-regex": "^1.0.0", + "strip-ansi": "^5.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", + "dev": true + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "node_modules/stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/stylehacks/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint": { + "version": "13.13.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.13.1.tgz", + "integrity": "sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==", + "dev": true, + "dependencies": { + "@stylelint/postcss-css-in-js": "^0.37.2", + "@stylelint/postcss-markdown": "^0.36.2", + "autoprefixer": "^9.8.6", + "balanced-match": "^2.0.0", + "chalk": "^4.1.1", + "cosmiconfig": "^7.0.0", + "debug": "^4.3.1", + "execall": "^2.0.0", + "fast-glob": "^3.2.5", + "fastest-levenshtein": "^1.0.12", + "file-entry-cache": "^6.0.1", + "get-stdin": "^8.0.0", + "global-modules": "^2.0.0", + "globby": "^11.0.3", + "globjoin": "^0.1.4", + "html-tags": "^3.1.0", + "ignore": "^5.1.8", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "known-css-properties": "^0.21.0", + "lodash": "^4.17.21", + "log-symbols": "^4.1.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.4", + "normalize-selector": "^0.2.0", + "postcss": "^7.0.35", + "postcss-html": "^0.36.0", + "postcss-less": "^3.1.4", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^4.0.2", + "postcss-sass": "^0.4.4", + "postcss-scss": "^2.1.1", + "postcss-selector-parser": "^6.0.5", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^4.1.0", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "specificity": "^0.4.1", + "string-width": "^4.2.2", + "strip-ansi": "^6.0.0", + "style-search": "^0.1.0", + "sugarss": "^2.0.0", + "svg-tags": "^1.0.0", + "table": "^6.6.0", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^3.0.3" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz", + "integrity": "sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==", + "dev": true, + "peerDependencies": { + "stylelint": ">=10.1.0" + } + }, + "node_modules/stylelint-config-recommended-scss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-4.3.0.tgz", + "integrity": "sha512-/noGjXlO8pJTr/Z3qGMoaRFK8n1BFfOqmAbX1RjTIcl4Yalr+LUb1zb9iQ7pRx1GsEBXOAm4g2z5/jou/pfMPg==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^5.0.0" + }, + "peerDependencies": { + "stylelint": "^10.1.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "stylelint-scss": "^3.0.0" + } + }, + "node_modules/stylelint-config-recommended-scss/node_modules/stylelint-config-recommended": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-5.0.0.tgz", + "integrity": "sha512-c8aubuARSu5A3vEHLBeOSJt1udOdS+1iue7BmJDTSXoCBmfEQmmWX+59vYIj3NQdJBY6a/QRv1ozVFpaB9jaqA==", + "dev": true, + "peerDependencies": { + "stylelint": "^13.13.0" + } + }, + "node_modules/stylelint-config-wordpress": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-wordpress/-/stylelint-config-wordpress-17.0.0.tgz", + "integrity": "sha512-qUU2kVMd2ezIV9AzRdgietIfnavRRENt4180A1OMoVXIowRjjhohZgBiyVPV5EtNKo3GTO63l8g/QGNG27/h9g==", + "deprecated": "This package has been deprecated, please use @wordpress/stylelint-config or @wordpress/scripts", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^3.0.0", + "stylelint-config-recommended-scss": "^4.2.0", + "stylelint-scss": "^3.17.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "stylelint": "^10.1.0 || ^11.0.0 || ^12.0.0 || ^13.0.0" + } + }, + "node_modules/stylelint-scss": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.21.0.tgz", + "integrity": "sha512-CMI2wSHL+XVlNExpauy/+DbUcB/oUZLARDtMIXkpV/5yd8nthzylYd1cdHeDMJVBXeYHldsnebUX6MoV5zPW4A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "stylelint": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/stylelint/node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint/node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint/node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stylelint/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/stylelint/node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/stylelint/node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/stylelint/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stylelint/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/sugarss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.2" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/svgo/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/svgo/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/svgo/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/svgo/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/tar-fs/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.1.0.tgz", + "integrity": "sha512-cjdZte66fYkZ65rQ2oJfrdCAkkhJA7YLYk5eGOcGCSGlq0ieZupRdjedSQXYknMPo2IveQL+tPdrxUkERENCFA==", + "dev": true, + "dependencies": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.2.1", + "p-limit": "^3.0.2", + "schema-utils": "^2.6.6", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.8.0", + "webpack-sources": "^1.4.3" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thread-loader": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-2.1.3.tgz", + "integrity": "sha512-wNrVKH2Lcf8ZrWxDF/khdlLlsTMczdcwPA9VEK4c2exlEPynYWxi9op3nPTo5lAnDIkE0rQEB3VBP+4Zncc9Hg==", + "dev": true, + "dependencies": { + "loader-runner": "^2.3.1", + "loader-utils": "^1.1.0", + "neo-async": "^2.6.0" + }, + "engines": { + "node": ">= 6.9.0 <7.0.0 || >= 8.9.0" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/thread-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/thread-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", + "dev": true + }, + "node_modules/tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "dependencies": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + } + }, + "node_modules/tiny-lr/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", + "dev": true + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "dependencies": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", + "dev": true + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "dev": true + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "deprecated": "support for ECMAScript is superseded by `uglify-js` as of v3.13.0", + "dev": true, + "dependencies": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-es/node_modules/commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "node_modules/uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "dev": true, + "dependencies": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "engines": { + "node": ">= 4.8 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha512-46TFiBOzX7xq/PcSWfFwkyjpemdRnMe31UQF+os0y+1W3k95f6R4SEt02Hj4p3X0Mir9gfrkmOtshFidS0VPUg==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==", + "dev": true, + "dependencies": { + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "dev": true, + "dependencies": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/underscore.string/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, + "node_modules/unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", + "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "dev": true, + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^2.0.0", + "x-is-string": "^0.1.0" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", + "dev": true + }, + "node_modules/uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ==", + "dev": true + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unist-util-find-all-after": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz", + "integrity": "sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==", + "dev": true, + "dependencies": { + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "dev": true, + "dependencies": { + "unist-util-visit": "^1.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "dev": true + }, + "node_modules/unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "dependencies": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "dependencies": { + "unist-util-is": "^3.0.0" + } + }, + "node_modules/unist-util-visit-parents/node_modules/unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", + "dev": true + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-browserslist-db/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-3.0.0.tgz", + "integrity": "sha512-a84JJbIA5xTFTWyjjcPdnsu+41o/SNE8SpXMdUvXs6Q+LuhCD9E2+0VCiuDWqgo3GGXVlFHzArDmBpj9PgWn4A==", + "dev": true, + "dependencies": { + "loader-utils": "^1.2.3", + "mime": "^2.4.4", + "schema-utils": "^2.5.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/url-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/uuid-random": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.0.tgz", + "integrity": "sha512-FSIlv8RFRPOjcHeDYStV7u6aJRfp+THrcWkbAJpw51JCyQLDxsFz+4dHgTYP8hSpZeSMXBpb/1qrK4bodXpSRA==", + "dev": true + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz", + "integrity": "sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": "8.x.x || >=10.10.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/vfile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", + "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.4", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + }, + "node_modules/vfile-location": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "dependencies": { + "unist-util-stringify-position": "^1.1.1" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", + "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "dev": true, + "dependencies": { + "domexception": "^1.0.1", + "webidl-conversions": "^4.0.2", + "xml-name-validator": "^3.0.0" + } + }, + "node_modules/wait-on": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", + "integrity": "sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ==", + "dev": true, + "dependencies": { + "@hapi/joi": "^15.0.3", + "core-js": "^2.6.5", + "minimist": "^1.2.0", + "request": "^2.88.0", + "rx": "^4.1.0" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/wait-on/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/wait-port": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz", + "integrity": "sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wait-port/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/wait-port/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/wait-port/node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "node_modules/wait-port/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/wait-port/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/walkdir": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", + "integrity": "sha512-lMFYXGpf7eg+RInVL021ZbJJT4hqsvsBvq5sZBp874jfhs3IWlA7OPoG0ojQrYcXHuUSi+Nqp6qGN+pPGaMgPQ==", + "dev": true, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + }, + "optionalDependencies": { + "chokidar": "^3.4.1", + "watchpack-chokidar2": "^2.0.1" + } + }, + "node_modules/watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "dependencies": { + "chokidar": "^2.1.8" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "optional": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "optional": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "optional": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "optional": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "optional": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "optional": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/micromatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "optional": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/webpack": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + }, + "webpack-command": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", + "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.19", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 6.14.4" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/webpack-cli": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=6.11.5" + }, + "peerDependencies": { + "webpack": "4.x.x" + } + }, + "node_modules/webpack-cli/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/webpack-cli/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/webpack-cli/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/webpack-cli/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/webpack-cli/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/webpack-cli/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack-cli/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/webpack-cli/node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "dependencies": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/webpack-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/webpack-cli/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack-cli/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-cli/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/resolve-dir/node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/resolve-dir/node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/webpack-cli/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-cli/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/webpack-cli/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/webpack-fix-style-only-entries": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/webpack-fix-style-only-entries/-/webpack-fix-style-only-entries-0.5.1.tgz", + "integrity": "sha512-G4TBoc5JvIVNR0GXG+t314V4AqpqLJuApX7aDNTZl8yhXojAXCwJXKQeF4SF65UpP/bjrvptmHLPmdA+aGUZMw==", + "dev": true + }, + "node_modules/webpack-livereload-plugin": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/webpack-livereload-plugin/-/webpack-livereload-plugin-2.3.0.tgz", + "integrity": "sha512-vVBLQLlNpElt2sfsBG+XLDeVbQFS4RrniVU8Hi1/hX5ycSfx6mtW8MEEITr2g0Cvo36kuPWShFFDuy+DS7KFMA==", + "dev": true, + "dependencies": { + "anymatch": "^3.1.1", + "portfinder": "^1.0.17", + "tiny-lr": "^1.1.1" + } + }, + "node_modules/webpack-rtl-plugin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-rtl-plugin/-/webpack-rtl-plugin-2.0.0.tgz", + "integrity": "sha512-lROgFkiPjapg9tcZ8FiLWeP5pJoG00018aEjLTxSrVldPD1ON+LPlhKPHjb7eE8Bc0+KL23pxcAjWDGOv9+UAw==", + "dev": true, + "dependencies": { + "@romainberger/css-diff": "^1.0.3", + "async": "^2.0.0", + "cssnano": "4.1.10", + "rtlcss": "2.4.0", + "webpack-sources": "1.3.0" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/webpack-rtl-plugin/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/rtlcss": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.4.0.tgz", + "integrity": "sha512-hdjFhZ5FCI0ABOfyXOMOhBtwPWtANLCG7rOiOcRf+yi5eDdxmDjqBruWouEnwVdzfh/TWF6NNncIEsigOCFZOA==", + "dev": true, + "dependencies": { + "chalk": "^2.3.0", + "findup": "^0.1.5", + "mkdirp": "^0.5.1", + "postcss": "^6.0.14", + "strip-json-comments": "^2.0.0" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-rtl-plugin/node_modules/webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "node_modules/webpack/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/webpack/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/webpack/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "dependencies": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/webpack/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/webpack/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/webpack/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/webpack/node_modules/ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "dependencies": { + "figgy-pudding": "^3.5.1" + } + }, + "node_modules/webpack/node_modules/terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "dependencies": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/webpack/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "dependencies": { + "errno": "~0.1.7" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==", + "dev": true + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", + "integrity": "sha512-2olrDUuPM4NvRIgGPhvrp84f7/HmWR6RiQrgwFF2VctmnssFiogtYL3DcA8Vl2bsSmju79sVXe38TsII7JleUg==", + "dev": true, + "dependencies": { + "archiver-utils": "^1.3.0", + "compress-commons": "^1.2.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz", + "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==", + "dev": true + }, + "@babel/core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", + "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-module-transforms": "^7.20.2", + "@babel/helpers": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + } + }, + "@babel/generator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "dev": true, + "requires": { + "@babel/types": "^7.20.5", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", + "integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.2.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "requires": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + } + }, + "@babel/helpers": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", + "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "dev": true, + "requires": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.1" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", + "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz", + "integrity": "sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz", + "integrity": "sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.20.2.tgz", + "integrity": "sha512-KS/G8YI8uwMGKErLFOHS/ekhqdHhpEloxs43NecQHVgo2QuQSyJhGIY1fL8UGl9wy5ItVwwoUL4YxVqsplGq2g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", + "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", + "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.19.0" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", + "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.18.6" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", + "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", + "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-react-display-name": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.18.6", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-pure-annotations": "^7.18.6" + } + }, + "@babel/runtime": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", + "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/runtime-corejs3": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz", + "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==", + "dev": true, + "requires": { + "core-js-pure": "^3.25.1", + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@babel/traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", + "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.5", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.5", + "@babel/types": "^7.20.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", + "dev": true + }, + "@hapi/bourne": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", + "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==", + "dev": true + }, + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", + "dev": true + }, + "@hapi/joi": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", + "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "dev": true, + "requires": { + "@hapi/address": "2.x.x", + "@hapi/bourne": "1.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/topo": "3.x.x" + } + }, + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "dev": true, + "requires": { + "@hapi/hoek": "^8.3.0" + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.5.0.tgz", + "integrity": "sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-message-util": "^25.5.0", + "jest-util": "^25.5.0", + "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@jest/core": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.5.4.tgz", + "integrity": "sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/reporters": "^25.5.1", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^25.5.0", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-resolve-dependencies": "^25.5.4", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "jest-watcher": "^25.5.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "realpath-native": "^2.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@jest/environment": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.5.0.tgz", + "integrity": "sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0" + } + }, + "@jest/fake-timers": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.5.0.tgz", + "integrity": "sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "lolex": "^5.0.0" + } + }, + "@jest/globals": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-25.5.2.tgz", + "integrity": "sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/types": "^25.5.0", + "expect": "^25.5.0" + } + }, + "@jest/reporters": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.5.1.tgz", + "integrity": "sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^25.5.1", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "node-notifier": "^6.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^3.1.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^4.1.3" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@jest/source-map": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.5.0.tgz", + "integrity": "sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.5.0.tgz", + "integrity": "sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/types": "^25.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz", + "integrity": "sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==", + "dev": true, + "requires": { + "@jest/test-result": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4" + } + }, + "@jest/transform": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.5.1.tgz", + "integrity": "sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^25.5.0", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^3.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-regex-util": "^25.2.6", + "jest-util": "^25.5.0", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "realpath-native": "^2.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@romainberger/css-diff": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@romainberger/css-diff/-/css-diff-1.0.3.tgz", + "integrity": "sha512-zR2EvxtJvQXRxFtTnqazMsJADngyVIulzYQ+wVYWRC1Hw3e4gfEIbigX46wTsPUyjAI+lRXFrBSoCWcgZ6ZSlQ==", + "dev": true, + "requires": { + "lodash.merge": "^4.4.0", + "postcss": "^5.0.21" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@stylelint/postcss-css-in-js": { + "version": "0.37.3", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.3.tgz", + "integrity": "sha512-scLk3cSH1H9KggSniseb2KNAU5D9FWc3H7BxCSAIdtU9OWIyw0zkEZ9qEKHryRM+SExYXRKNb7tOOVNAsQ3iwg==", + "dev": true, + "requires": { + "@babel/core": "^7.17.9" + } + }, + "@stylelint/postcss-markdown": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", + "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", + "dev": true, + "requires": { + "remark": "^13.0.0", + "unist-util-find-all-after": "^3.0.2" + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "dev": true + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "dev": true + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "dev": true + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "dev": true + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "dev": true + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "dev": true + }, + "@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "dev": true, + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + } + }, + "@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "dev": true, + "requires": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "dev": true, + "requires": { + "@babel/types": "^7.12.6" + } + }, + "@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + } + }, + "@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + } + }, + "@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + } + }, + "@types/babel__core": { + "version": "7.1.20", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", + "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/cheerio": { + "version": "0.22.31", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", + "integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, + "@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "@types/node": { + "version": "18.11.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", + "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/q": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==", + "dev": true + }, + "@types/react": { + "version": "16.14.34", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.34.tgz", + "integrity": "sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "16.9.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.17.tgz", + "integrity": "sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g==", + "dev": true, + "requires": { + "@types/react": "^16" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/tapable": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", + "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz", + "integrity": "sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "@types/webpack": { + "version": "4.41.33", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.33.tgz", + "integrity": "sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, + "@types/webpack-sources": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", + "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + } + } + }, + "@types/yargs": { + "version": "15.0.14", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", + "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@wordpress/babel-plugin-import-jsx-pragma": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-2.7.0.tgz", + "integrity": "sha512-yR+rSyfHKfevW84vKBOERpjEslD/o00CaYMftywVYOjsOQ8GLS6xv/VgDcpQ8JomJ9eRRInLRpeGKTM3lOa4xQ==", + "dev": true, + "requires": {} + }, + "@wordpress/babel-preset-default": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-4.20.0.tgz", + "integrity": "sha512-VKPoC5We2GNxon5umOeZ7NIP4CfP7X5gqslSnNrLW4kD1XgmbVaCs2ISFF8+mObVVb6KAzbaUjI6OWljcUb5UA==", + "dev": true, + "requires": { + "@babel/core": "^7.12.9", + "@babel/plugin-transform-react-jsx": "^7.12.7", + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/preset-env": "^7.12.7", + "@babel/runtime": "^7.12.5", + "@wordpress/babel-plugin-import-jsx-pragma": "^2.7.0", + "@wordpress/browserslist-config": "^2.7.0", + "@wordpress/element": "^2.19.0", + "@wordpress/warning": "^1.3.0", + "core-js": "^3.6.4" + } + }, + "@wordpress/base-styles": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-3.1.0.tgz", + "integrity": "sha512-+HR6Cw0E95IHLixWmDCy54kMCmPxTxwAx7UTkJY/9YvOZyK8Nu3plWbX4c/6MhsASJ9RVFVhJPSJWleQ8bDEkQ==", + "dev": true + }, + "@wordpress/browserslist-config": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-2.7.0.tgz", + "integrity": "sha512-pB45JlfmHuEigNFZ1X+CTgIsOT3/TTb9iZxw1DHXge/7ytY8FNhtcNwTfF9IgnS6/xaFRZBqzw4DyH4sP1Lyxg==", + "dev": true + }, + "@wordpress/dependency-extraction-webpack-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.9.0.tgz", + "integrity": "sha512-Eo8ByPd3iZ6az4UmdLD2xYLp1/7os/H80l28Y5OlS4DozkD3vcWCBReynWoBax74u3oJ9wWN5b/8oSxGwIKXYQ==", + "dev": true, + "requires": { + "json2php": "^0.0.4", + "webpack-sources": "^1.3.0" + } + }, + "@wordpress/dom": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-2.15.0.tgz", + "integrity": "sha512-eoNfM7QnrZJfdJr1DMaIi1oWlaFJ0BtHBy/0IjGhDYeZIzKRhGzCkz4vhRMwxeTPCGbG0PZg4uwPvys4Vugp9Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.11.2", + "lodash": "^4.17.19" + } + }, + "@wordpress/element": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.20.3.tgz", + "integrity": "sha512-f4ZPTDf9CxiiOXiMxc4v1K7jcBMT4dsiehVOpkKzCDKboNXp4qVf8oe5PE23VGZNEjcOj5Mkg9hB57R0nqvMTw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.13.10", + "@types/react": "^16.9.0", + "@types/react-dom": "^16.9.0", + "@wordpress/escape-html": "^1.12.2", + "lodash": "^4.17.19", + "react": "^16.13.1", + "react-dom": "^16.13.1" + } + }, + "@wordpress/escape-html": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-1.12.2.tgz", + "integrity": "sha512-FabgSwznhdaUwe6hr1CsGpgxQbzqEoGevv73WIL1B9GvlZ6csRWodgHfWh4P6fYqpzxFL4WYB8wPJ1PdO32XFA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@wordpress/eslint-plugin": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-7.4.0.tgz", + "integrity": "sha512-HJpDYz2drtC9rY8MiYtYJ3cimioEIweGyb3P2DQTjUZ3sC4AGg+97PhXLHUdKfsFQ31JRxyLS9kKuGdDVBwWww==", + "dev": true, + "requires": { + "@wordpress/prettier-config": "^0.4.0", + "babel-eslint": "^10.1.0", + "cosmiconfig": "^7.0.0", + "eslint-config-prettier": "^6.10.1", + "eslint-plugin-jest": "^23.8.2", + "eslint-plugin-jsdoc": "^30.2.2", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.20.0", + "eslint-plugin-react-hooks": "^4.0.4", + "globals": "^12.0.0", + "prettier": "npm:wp-prettier@2.2.1-beta-1", + "requireindex": "^1.2.0" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "@wordpress/jest-console": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-3.10.0.tgz", + "integrity": "sha512-iS1GSO+o7+p2PhvScOquD+IK7WqmVxa2s9uTUQyNEo06f9EUv6KNw0B1iZ00DpbgLqDCiczfdCNapC816UXIIA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "jest-matcher-utils": "^25.3.0", + "lodash": "^4.17.19" + } + }, + "@wordpress/jest-preset-default": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-6.6.0.tgz", + "integrity": "sha512-9HbKUNRMUCooXAKt+6jj5SZjDMtWoR9yMb9bJ5eCd9wUfrfQ/x2nUJK/RXiv1aI85HHmzl5KfQquZF76lYEkcw==", + "dev": true, + "requires": { + "@jest/reporters": "^25.3.0", + "@wordpress/jest-console": "^3.10.0", + "babel-jest": "^25.3.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", + "enzyme-to-json": "^3.4.4" + } + }, + "@wordpress/npm-package-json-lint-config": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-3.1.0.tgz", + "integrity": "sha512-SYRWpzpQaSsBUiRO+ssqg6AHjgCF4j2npstGTGaKdVs/B720fLFzeyONuMmo1ZtMb9v6MyEWxVz5ON6dDgmVYg==", + "dev": true, + "requires": {} + }, + "@wordpress/postcss-plugins-preset": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-1.6.0.tgz", + "integrity": "sha512-WPToVlX99PiUSSxSbwAR2wJtIpbcnnRkB48sIIkDvw7rCpSWkh6OLuzfj0o5g+JCYuNL1OnQXFA8EtydNEZ9Sw==", + "dev": true, + "requires": { + "@wordpress/base-styles": "^3.3.0", + "@wordpress/postcss-themes": "^2.6.0", + "autoprefixer": "^9.8.6", + "postcss-custom-properties": "^10.0.0" + }, + "dependencies": { + "@wordpress/base-styles": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-3.6.0.tgz", + "integrity": "sha512-6/vXAmc9FSX7Y17UjKgUJoVU++Pv1U1G8uMx7iClRUaLetc7/jj2DD9PTyX/cdJjHr32e3yXuLVT9wfEbo6SEg==", + "dev": true + } + } + }, + "@wordpress/postcss-themes": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-themes/-/postcss-themes-2.6.0.tgz", + "integrity": "sha512-Q22s1KSVdtoK0Z0ND06V2QwTx/U4KvJhWFmoI8IzYW/LGlk8BkQJhHH157Y9vFliwpMlQpqfXW6/zOg2XtvHzQ==", + "dev": true, + "requires": { + "postcss": "^7.0.32" + } + }, + "@wordpress/prettier-config": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-0.4.0.tgz", + "integrity": "sha512-7c4VeugkCwDkaHSD7ffxoP0VC5c///gCTEAT032OhI5Rik2dPxE3EkNAB2NhotGE8M4dMAg4g5Wj2OWZIn8TFw==", + "dev": true + }, + "@wordpress/scripts": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-12.6.1.tgz", + "integrity": "sha512-pDLtACFrP5gUA414qrE49dUrR7yMy40+//1e/5Nx821lnmDb7GAGWGo1gX4lJ2gbfSjePwmRoZe6Mph87vSnLQ==", + "dev": true, + "requires": { + "@svgr/webpack": "^5.2.0", + "@wordpress/babel-preset-default": "^4.20.0", + "@wordpress/dependency-extraction-webpack-plugin": "^2.9.0", + "@wordpress/eslint-plugin": "^7.4.0", + "@wordpress/jest-preset-default": "^6.6.0", + "@wordpress/npm-package-json-lint-config": "^3.1.0", + "@wordpress/postcss-plugins-preset": "^1.6.0", + "@wordpress/prettier-config": "^0.4.0", + "babel-jest": "^25.3.0", + "babel-loader": "^8.1.0", + "chalk": "^4.0.0", + "check-node-version": "^3.1.1", + "clean-webpack-plugin": "^3.0.0", + "cross-spawn": "^5.1.0", + "css-loader": "^3.5.2", + "dir-glob": "^3.0.1", + "eslint": "^7.1.0", + "eslint-plugin-markdown": "^1.0.2", + "ignore-emit-webpack-plugin": "^2.0.6", + "jest": "^25.3.0", + "jest-puppeteer": "^4.4.0", + "markdownlint": "^0.18.0", + "markdownlint-cli": "^0.21.0", + "mini-css-extract-plugin": "^0.9.0", + "minimist": "^1.2.0", + "npm-package-json-lint": "^5.0.0", + "postcss-loader": "^3.0.0", + "prettier": "npm:wp-prettier@2.2.1-beta-1", + "puppeteer": "npm:puppeteer-core@3.0.0", + "read-pkg-up": "^1.0.1", + "resolve-bin": "^0.4.0", + "sass": "^1.26.11", + "sass-loader": "^8.0.2", + "source-map-loader": "^0.2.4", + "stylelint": "^13.6.0", + "stylelint-config-wordpress": "^17.0.0", + "terser-webpack-plugin": "^3.0.3", + "thread-loader": "^2.1.3", + "url-loader": "^3.0.0", + "webpack": "^4.42.0", + "webpack-bundle-analyzer": "^3.6.1", + "webpack-cli": "^3.3.11", + "webpack-livereload-plugin": "^2.3.0" + } + }, + "@wordpress/warning": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-1.4.2.tgz", + "integrity": "sha512-MjrkSp6Jyfx+92AE32A83P503noUtGb6//BYUH4GiWzzzSNhDHgbQ0UcOJwJaEYK166DxSNpMk/JXc4YENi1Cw==", + "dev": true + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + } + } + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": true + }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "airbnb-prop-types": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", + "integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==", + "dev": true, + "requires": { + "array.prototype.find": "^2.1.1", + "function.prototype.name": "^1.1.2", + "is-regex": "^1.1.0", + "object-is": "^1.1.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.13.1" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "requires": {} + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "applause": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/applause/-/applause-2.0.4.tgz", + "integrity": "sha512-wFhNjSoflbAEgelX3psyKSXV2iQFjuYW31DEhcCOD/bQ98VdfltLclK4p1mI6E58Qp4Q7+5RCbBdr+Nc9b5QhA==", + "dev": true, + "requires": { + "cson-parser": "^4.0.8", + "js-yaml": "^4.0.0", + "lodash": "^4.17.21", + "optional-require": "^1.0.2" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "optional": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "optional": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "archiver": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", + "integrity": "sha512-4q/CtGPNVyC5aT9eYHhFP7SAEjKYzQIDIJWXfexUIPNxitNs1y6hORdX+sYxERSZ6qPeNNBJ5UolFsJdWTU02g==", + "dev": true, + "requires": { + "archiver-utils": "^1.3.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "walkdir": "^0.0.11", + "zip-stream": "^1.1.0" + }, + "dependencies": { + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + } + } + }, + "archiver-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", + "integrity": "sha512-h+hTREBXcW5e1L9RihGXdH4PHHdGipG/jE2sMZrqIH6BmZAxeGU5IWjVsKhokdCSWX7km6Kkh406zZNEElHFPQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "graceful-fs": "^4.1.0", + "lazystream": "^1.0.0", + "lodash": "^4.8.0", + "normalize-path": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true + }, + "array.prototype.filter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.2.tgz", + "integrity": "sha512-us+UrmGOilqttSOgoWZTpOvHu68vZT2YCjc/H4vhu56vzZpaDFBhB+Se2UwqWzMKbDv7Myq5M5pcZLAtUvTQdQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "array.prototype.find": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.1.tgz", + "integrity": "sha512-I2ri5Z9uMpMvnsNrHre9l3PaX+z9D0/z6F7Yt2u15q7wt0I62g5kX6xUKR1SJiefgG+u2/gJUmM8B47XRvQR6w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true, + "optional": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "axe-core": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", + "integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==", + "dev": true + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "babel-jest": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.5.1.tgz", + "integrity": "sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==", + "dev": true, + "requires": { + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + } + } + }, + "babel-plugin-jest-hoist": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz", + "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + }, + "babel-preset-current-node-syntax": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz", + "integrity": "sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz", + "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^25.5.0", + "babel-preset-current-node-syntax": "^0.1.2" + } + }, + "bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bfj": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", + "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "check-types": "^8.0.3", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + }, + "dependencies": { + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true + } + } + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "dev": true + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001435", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001435.tgz", + "integrity": "sha512-kdCkUTjR+v4YAJelyiDTqiu82BDr4W4CP5sgTA0ZBmqn30XfS2ZghPLMowik9TPhS+psWJiUNxsqLyurDbmutA==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true + }, + "chart.js": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", + "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + } + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, + "check-node-version": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-3.3.0.tgz", + "integrity": "sha512-OAtp7prQf+8YYKn2UB/fK1Ppb9OT+apW56atoKYUvucYLPq69VozOY0B295okBwCKymk2cictrS3qsdcZwyfzw==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "map-values": "^1.0.1", + "minimist": "^1.2.0", + "object-filter": "^1.0.2", + "object.assign": "^4.0.4", + "run-parallel": "^1.1.4", + "semver": "^5.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "check-types": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", + "dev": true + }, + "cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "clean-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", + "dev": true, + "requires": { + "@types/webpack": "^4.4.31", + "del": "^4.1.1" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "dev": true, + "requires": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + } + }, + "clone-regexp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", + "dev": true, + "requires": { + "is-regexp": "^2.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "optional": true + }, + "coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true, + "optional": true + }, + "collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "comment-parser": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz", + "integrity": "sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "compress-commons": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", + "integrity": "sha512-SLTU8iWWmcORfUN+4351Z2aZXKJe1tr0jSilPMCZlLPzpdTXnkBW1LevW/MfuANBKJek8Xu9ggqrtVmQrChLtg==", + "dev": true, + "requires": { + "buffer-crc32": "^0.2.1", + "crc32-stream": "^2.0.0", + "normalize-path": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "optional": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true + }, + "copy-webpack-plugin": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.0.tgz", + "integrity": "sha512-1s/VbhIX73FBFBYF4D0KdeBLkjEnAlCQn0Ufo2a/IyJ41jHpQ9ZzM4JAfbE7yTOhbmwRFkARErJ/XIiLceja6Q==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "fast-glob": "^3.2.4", + "find-cache-dir": "^3.3.1", + "glob-parent": "^5.1.1", + "globby": "^11.0.1", + "loader-utils": "^2.0.0", + "normalize-path": "^3.0.0", + "p-limit": "^3.0.2", + "schema-utils": "^2.7.1", + "serialize-javascript": "^5.0.1", + "webpack-sources": "^1.4.3" + } + }, + "core-js": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", + "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "dev": true + }, + "core-js-compat": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==", + "dev": true, + "requires": { + "browserslist": "^4.21.4" + } + }, + "core-js-pure": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz", + "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "requires": { + "buffer": "^5.1.0" + } + }, + "crc32-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", + "integrity": "sha512-UjZSqFCbn+jZUHJIh6Y3vMF7EJLcJWNm4tKDf2peJRwlZKHvkkvOMTvAei6zjU9gO1xONVr3rRFw0gixm2eUng==", + "dev": true, + "requires": { + "crc": "^3.4.4", + "readable-stream": "^2.0.0" + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-env": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "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", + "randomfill": "^1.0.3" + } + }, + "cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "optional": true, + "requires": { + "coffeescript": "1.12.7" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==", + "dev": true + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true + } + } + }, + "cssnano-preset-default": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", + "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.3", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw==", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw==", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + }, + "dependencies": { + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + } + } + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "dev": true + }, + "cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "dev": true, + "requires": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + } + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", + "dev": true + }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true + } + } + }, + "decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true + }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "optional": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "optional": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + }, + "enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "dev": true, + "requires": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + } + }, + "enzyme-adapter-react-16": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.7.tgz", + "integrity": "sha512-LtjKgvlTc/H7adyQcj+aq0P0H07LDL480WQl1gU512IUyaDo/sbOaNDdZsJXYW2XaoPqrLLE9KbZS+X2z6BASw==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.14.1", + "enzyme-shallow-equal": "^1.0.5", + "has": "^1.0.3", + "object.assign": "^4.1.4", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "react-is": "^16.13.1", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "enzyme-adapter-utils": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.1.tgz", + "integrity": "sha512-JZgMPF1QOI7IzBj24EZoDpaeG/p8Os7WeBZWTJydpsH7JRStc7jYbHE4CmNQaLqazaGFyLM8ALWA3IIZvxW3PQ==", + "dev": true, + "requires": { + "airbnb-prop-types": "^2.16.0", + "function.prototype.name": "^1.1.5", + "has": "^1.0.3", + "object.assign": "^4.1.4", + "object.fromentries": "^2.0.5", + "prop-types": "^15.8.1", + "semver": "^5.7.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "enzyme-shallow-equal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.5.tgz", + "integrity": "sha512-i6cwm7hN630JXenxxJFBKzgLC3hMTafFQXflvzHgPmDhOBhxUWDe8AeRv1qp2/uWJ2Y8z5yLWMzmAfkTOiOCZg==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object-is": "^1.1.5" + } + }, + "enzyme-to-json": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.2.tgz", + "integrity": "sha512-Ynm6Z6R6iwQ0g2g1YToz6DWhxVnt8Dy1ijR2zynRKxTyBGA8rCDXU3rs2Qc4OKvUvc2Qoe1bcFK6bnPs20TrTg==", + "dev": true, + "requires": { + "@types/cheerio": "^0.22.22", + "lodash": "^4.17.21", + "react-is": "^16.12.0" + } + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "requires": { + "string-template": "~0.2.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", + "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-jest": { + "version": "23.20.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz", + "integrity": "sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^2.5.0" + } + }, + "eslint-plugin-jsdoc": { + "version": "30.7.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.13.tgz", + "integrity": "sha512-YM4WIsmurrp0rHX6XiXQppqKB8Ne5ATiZLJe2+/fkp9l9ExXFr43BbAbjZaVrpCT+tuPYOZ8k1MICARHnURUNQ==", + "dev": true, + "requires": { + "comment-parser": "^0.7.6", + "debug": "^4.3.1", + "jsdoctypeparser": "^9.0.0", + "lodash": "^4.17.20", + "regextras": "^0.7.1", + "semver": "^7.3.4", + "spdx-expression-parse": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", + "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.9", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.3", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.2", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + } + }, + "eslint-plugin-markdown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-1.0.2.tgz", + "integrity": "sha512-BfvXKsO0K+zvdarNc801jsE/NTLmig4oKhZ1U3aSUgTf2dB/US5+CrfGxMsCK2Ki1vS1R3HPok+uYpufFndhzw==", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "remark-parse": "^5.0.0", + "unified": "^6.1.2" + } + }, + "eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.31.11", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz", + "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exec-sh": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", + "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", + "dev": true + }, + "execa": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "execall": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", + "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", + "dev": true, + "requires": { + "clone-regexp": "^2.1.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "optional": true + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "expect": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz", + "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-styles": "^4.0.0", + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-regex-util": "^25.2.6" + } + }, + "expect-puppeteer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.4.0.tgz", + "integrity": "sha512-6Ey4Xy2xvmuQu7z7YQtMsaMV0EHJRpVxIDOd5GRrm04/I3nkTKIutELfECsLp6le+b3SSa3cXhPiw6PgqzxYWA==", + "dev": true + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "dev": true, + "requires": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + } + }, + "find-parent-dir": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz", + "integrity": "sha512-o4UcykWV/XN9wm+jMEtWLPlV8RXCZnMhQI6F6OdHeSez7iiJWePw8ijOlskJZMsaQoGR/b7dH6lO02HhaTN7+A==", + "dev": true + }, + "find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "dev": true, + "requires": { + "find-file-up": "^0.1.2" + } + }, + "find-process": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", + "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "findup": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", + "integrity": "sha512-Udxo3C9A6alt2GZ2MNsgnIvX7De0V3VGxeP/x98NSVgSlizcDHdmJza61LI7zJy4OEtSiJyE72s0/+tBl5/ZxA==", + "dev": true, + "requires": { + "colors": "~0.6.0-1", + "commander": "~2.1.0" + }, + "dependencies": { + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==", + "dev": true + }, + "commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha512-J2wnb6TKniXNOtoHS8TSrG9IOQluPrsmyAJ8oCUJOBmv+uLBCyPYAZkD2jFvw2DCzIXNnISIM01NIvr35TkBMQ==", + "dev": true + } + } + }, + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha512-z8Nrwhi6wzxNMIbxlrTzuUW6KWuKkogZ/7OdDVq+0+kxn77KUH1nipx8iU6suqkHqc4y6n7a9A8IpmxY/pTjWg==", + "dev": true, + "requires": { + "glob": "~5.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "flot": { + "version": "0.8.0-alpha", + "resolved": "https://registry.npmjs.org/flot/-/flot-0.8.0-alpha.tgz", + "integrity": "sha512-LMXH1jkhEcEOHdjF0sC0AuF1adcv8vx/VCdZEmuceWqQtw/ZJz23jgvsS+sS2QVGneV/lkVfz4H7yHjzfZoB/g==" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "dev": true + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "optional": true, + "requires": { + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true + }, + "getobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", + "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "dev": true, + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "dependencies": { + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true + } + } + }, + "global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "dependencies": { + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true + } + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true + } + } + }, + "globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", + "dev": true, + "optional": true + }, + "grunt": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.3.tgz", + "integrity": "sha512-mKwmo4X2d8/4c/BmcOETHek675uOqw0RuA/zy12jaspWqvTp4+ZeQF1W+OTpcbncnaBsfbQJ6l0l4j+Sn/GmaQ==", + "dev": true, + "requires": { + "dateformat": "~3.0.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~0.3.0", + "glob": "~7.1.6", + "grunt-cli": "~1.4.3", + "grunt-known-options": "~2.0.0", + "grunt-legacy-log": "~3.0.0", + "grunt-legacy-util": "~2.0.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "mkdirp": "~1.0.4", + "nopt": "~3.0.6", + "rimraf": "~3.0.2" + }, + "dependencies": { + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "grunt-checktextdomain": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt-checktextdomain/-/grunt-checktextdomain-1.0.1.tgz", + "integrity": "sha512-WX6EMOnvrhj8MDqmWJAlZRpM1rE/Mmqy9DCYYeWB1pFV+JotP6pNx2uO58+uHFOEFTcoridr4ECJ6aqGnLmgJw==", + "dev": true, + "requires": { + "chalk": "~0.2.1", + "text-table": "~0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-0.2.0.tgz", + "integrity": "sha512-YyQBeLj0juxUC9uUXRpQ1ZAzPT1dnsn5vVeJLHYFq4Ct1p0rymUSyvckKCXCH9I0bh3jWDIETA5nXIaZVKlDyA==", + "dev": true + }, + "chalk": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.2.1.tgz", + "integrity": "sha512-nmVapomwGksziCuynboy7I+dtW4ytIdqXPlrfY/ySx8l8EqFRGHyA04q6NMNpOri8XliGUGwXyfScVl48zFHbw==", + "dev": true, + "requires": { + "ansi-styles": "~0.2.0", + "has-color": "~0.1.0" + } + } + } + }, + "grunt-cli": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", + "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", + "dev": true, + "requires": { + "grunt-known-options": "~2.0.0", + "interpret": "~1.1.0", + "liftup": "~3.0.1", + "nopt": "~4.0.1", + "v8flags": "~3.2.0" + }, + "dependencies": { + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, + "grunt-composer": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt-composer/-/grunt-composer-0.4.5.tgz", + "integrity": "sha512-TqTxpedQiDlP9rrOSJpEFLT0Hr6njsm3306+jHK8C8EdoHXTkzFl2LiJE09dz/uyH34O7MXVG4K6WBYOYrxw3g==", + "dev": true, + "requires": { + "shelljs": "~0.2.6" + } + }, + "grunt-contrib-clean": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz", + "integrity": "sha512-tET+TYTd8vCtKeGwbLjoH8+SdI8ngVzGbPr7vlWkewG7mYYHIccd2Ldxq+PK3DyBp5Www3ugdkfsjoNKUl5MTg==", + "dev": true, + "requires": { + "async": "^1.5.2", + "rimraf": "^2.5.1" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "grunt-contrib-compress": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-compress/-/grunt-contrib-compress-1.6.0.tgz", + "integrity": "sha512-wIFuvk+/Ny4E+OgEfJYFZgoH7KcU/nnNFbYasB7gRvrcRyW6vmTp3Pj8a4rFSR3tbFMjrGvTUszdO6fgLajgZQ==", + "dev": true, + "requires": { + "archiver": "^1.3.0", + "chalk": "^1.1.1", + "iltorb": "^2.4.3", + "lodash": "^4.7.0", + "pretty-bytes": "^4.0.2", + "stream-buffers": "^2.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "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" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==", + "dev": true, + "requires": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "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" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "grunt-force-task": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-force-task/-/grunt-force-task-2.0.0.tgz", + "integrity": "sha512-GHh+jKzGFaTGIiAq4ln+42gJSWGiz3l3qAqiVHRgVtRUkWEuKqAnFedmuxlC2CBS9938DYb0iCihPJH8haSPyw==", + "dev": true + }, + "grunt-known-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", + "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", + "dev": true + }, + "grunt-legacy-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", + "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "dev": true, + "requires": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.19" + } + }, + "grunt-legacy-log-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", + "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "dev": true, + "requires": { + "chalk": "~4.1.0", + "lodash": "~4.17.19" + } + }, + "grunt-legacy-util": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", + "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "dev": true, + "requires": { + "async": "~3.2.0", + "exit": "~0.1.2", + "getobject": "~1.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.21", + "underscore.string": "~3.3.5", + "which": "~2.0.2" + }, + "dependencies": { + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "grunt-replace": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/grunt-replace/-/grunt-replace-2.0.2.tgz", + "integrity": "sha512-cv7ua3vBeUpCBkb+L4vZ8hwE8Sz6Sf/mb3031kufKVfKAestekHSsDO0BK63mhrRknsAHlnkHkieIxrjHS/fJA==", + "dev": true, + "requires": { + "applause": "^2.0.0", + "chalk": "^4.1.0", + "file-sync-cmp": "^0.1.0", + "lodash": "^4.17.21" + } + }, + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + } + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "optional": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", + "dev": true + }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==", + "dev": true + }, + "html-element-map": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz", + "integrity": "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==", + "dev": true, + "requires": { + "array.prototype.filter": "^1.0.0", + "call-bind": "^1.0.2" + } + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true + }, + "htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true + }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "requires": { + "agent-base": "5", + "debug": "4" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ignore-emit-webpack-plugin": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/ignore-emit-webpack-plugin/-/ignore-emit-webpack-plugin-2.0.6.tgz", + "integrity": "sha512-/zC18RWCC2wz4ZwnS4UoujGWzvSKy28DLjtE+jrGBOXej6YdmityhBDzE8E0NlktEqi4tgdNbydX8B6G4haHSQ==", + "dev": true + }, + "iltorb": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/iltorb/-/iltorb-2.4.5.tgz", + "integrity": "sha512-EMCMl3LnnNSZJS5QrxyZmMTaAC4+TJkM5woD+xbpm9RB+mFYCr7C05GFE3TEGCsVQSVHmjX+3sf5AiwsylNInQ==", + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.3", + "nan": "^2.14.0", + "npmlog": "^4.1.2", + "prebuild-install": "^5.3.3", + "which-pm-runs": "^1.0.0" + } + }, + "immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg==", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha512-0vdnLL2wSGnhlRmzHJAg5JHjt1l2vYhzJ7tNLGbeVg0fse56tpGaH0uzH+r9Slej+BSXXEHvBKDEnVSLLE9/+w==", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true + } + } + }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "irregular-plurals": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", + "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "optional": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "dev": true + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==", + "dev": true + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.4.tgz", + "integrity": "sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==", + "dev": true, + "requires": { + "@jest/core": "^25.5.4", + "import-local": "^3.0.2", + "jest-cli": "^25.5.4" + } + }, + "jest-changed-files": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.5.0.tgz", + "integrity": "sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "execa": "^3.2.0", + "throat": "^5.0.0" + } + }, + "jest-cli": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.5.4.tgz", + "integrity": "sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==", + "dev": true, + "requires": { + "@jest/core": "^25.5.4", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^25.5.4", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "prompts": "^2.0.1", + "realpath-native": "^2.0.0", + "yargs": "^15.3.1" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-config": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.5.4.tgz", + "integrity": "sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^25.5.4", + "@jest/types": "^25.5.0", + "babel-jest": "^25.5.1", + "chalk": "^3.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^25.5.0", + "jest-environment-node": "^25.5.0", + "jest-get-type": "^25.2.6", + "jest-jasmine2": "^25.5.4", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "micromatch": "^4.0.2", + "pretty-format": "^25.5.0", + "realpath-native": "^2.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-dev-server": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-4.4.0.tgz", + "integrity": "sha512-STEHJ3iPSC8HbrQ3TME0ozGX2KT28lbT4XopPxUm2WimsX3fcB3YOptRh12YphQisMhfqNSNTZUmWyT3HEXS2A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "cwd": "^0.10.0", + "find-process": "^1.4.3", + "prompts": "^2.3.0", + "spawnd": "^4.4.0", + "tree-kill": "^1.2.2", + "wait-on": "^3.3.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-docblock": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-25.3.0.tgz", + "integrity": "sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz", + "integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz", + "integrity": "sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "jsdom": "^15.2.1" + } + }, + "jest-environment-node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.5.0.tgz", + "integrity": "sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "semver": "^6.3.0" + } + }, + "jest-environment-puppeteer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-4.4.0.tgz", + "integrity": "sha512-iV8S8+6qkdTM6OBR/M9gKywEk8GDSOe05hspCs5D8qKSwtmlUfdtHfB4cakdc68lC6YfK3AUsLirpfgodCHjzQ==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "cwd": "^0.10.0", + "jest-dev-server": "^4.4.0", + "merge-deep": "^3.0.2" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true + }, + "jest-haste-map": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz", + "integrity": "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "@types/graceful-fs": "^4.1.2", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-serializer": "^25.5.0", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "jest-jasmine2": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz", + "integrity": "sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^25.5.0", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "co": "^4.6.0", + "expect": "^25.5.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^25.5.0", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0", + "throat": "^5.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-leak-detector": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz", + "integrity": "sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA==", + "dev": true, + "requires": { + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "jest-matcher-utils": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz", + "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "jest-diff": "^25.5.0", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-message-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", + "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^1.0.1" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-mock": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.5.0.tgz", + "integrity": "sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-puppeteer": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-4.4.0.tgz", + "integrity": "sha512-ZaiCTlPZ07B9HW0erAWNX6cyzBqbXMM7d2ugai4epBDKpKvRDpItlRQC6XjERoJELKZsPziFGS0OhhUvTvQAXA==", + "dev": true, + "requires": { + "expect-puppeteer": "^4.4.0", + "jest-environment-puppeteer": "^4.4.0" + } + }, + "jest-regex-util": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", + "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", + "dev": true + }, + "jest-resolve": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.5.1.tgz", + "integrity": "sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "browser-resolve": "^1.11.3", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.1", + "read-pkg-up": "^7.0.1", + "realpath-native": "^2.0.0", + "resolve": "^1.17.0", + "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "jest-resolve-dependencies": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz", + "integrity": "sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-snapshot": "^25.5.1" + } + }, + "jest-runner": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.5.4.tgz", + "integrity": "sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-docblock": "^25.3.0", + "jest-haste-map": "^25.5.1", + "jest-jasmine2": "^25.5.4", + "jest-leak-detector": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", + "jest-runtime": "^25.5.4", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-runtime": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.5.4.tgz", + "integrity": "sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/globals": "^25.5.2", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "realpath-native": "^2.0.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.3.1" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-serializer": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.5.0.tgz", + "integrity": "sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.5.1.tgz", + "integrity": "sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/prettier": "^1.19.0", + "chalk": "^3.0.0", + "expect": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-diff": "^25.5.0", + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", + "make-dir": "^3.0.0", + "natural-compare": "^1.4.0", + "pretty-format": "^25.5.0", + "semver": "^6.3.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", + "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "make-dir": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-watcher": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.5.0.tgz", + "integrity": "sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q==", + "dev": true, + "requires": { + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "jest-util": "^25.5.0", + "string-length": "^3.1.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "jquery": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + }, + "jquery-creditcardvalidator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jquery-creditcardvalidator/-/jquery-creditcardvalidator-1.0.0.tgz", + "integrity": "sha512-fuXWRPGMPO6UoLj1HwnENG7WwyLr43QNupS81VpC4NEDF8DAtB5pH4lyyZ7ApzgIxvn9LKtvsV/ilL963QzoFQ==" + }, + "jquery-validation": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.5.tgz", + "integrity": "sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==", + "requires": {} + }, + "jquery.payment": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jquery.payment/-/jquery.payment-3.0.0.tgz", + "integrity": "sha512-b02U7bf5HIhmfwsODomtLe119VeZ4c5hBvyKkdV3Ih4tTrD08sEPZuhgrX0Sv9iWIb08vNWn4RpaCyZ48mhOJA==", + "requires": { + "jquery": ">=1.7" + } + }, + "js-base64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "jsdoctypeparser": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", + "integrity": "sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==", + "dev": true + }, + "jsdom": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", + "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^7.1.0", + "acorn-globals": "^4.3.2", + "array-equal": "^1.0.0", + "cssom": "^0.4.1", + "cssstyle": "^2.0.0", + "data-urls": "^1.1.0", + "domexception": "^1.0.1", + "escodegen": "^1.11.1", + "html-encoding-sniffer": "^1.0.2", + "nwsapi": "^2.2.0", + "parse5": "5.1.0", + "pn": "^1.1.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "saxes": "^3.1.9", + "symbol-tree": "^3.2.2", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.1.2", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^7.0.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "dev": true + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "json2php": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/json2php/-/json2php-0.0.4.tgz", + "integrity": "sha512-hFzejhs28f70sGnutcsRS459MnAsjRVI85RgPAL1KQIZEpjiDitc27CZv4IgOtaR86vrqOVlu9vJNew2XyTH4g==", + "dev": true + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", + "dev": true + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "known-css-properties": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz", + "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", + "dev": true + }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "language-tags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.6.tgz", + "integrity": "sha512-HNkaCgM8wZgE/BZACeotAAgpL9FUjEnhgF0FVQMIgH//zqTPreLYMb3rWYkYAqPoF75Jwuycp1da7uz66cfFQg==", + "dev": true, + "requires": { + "language-subtag-registry": "^0.3.20" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "dev": true + }, + "lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "liftup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", + "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", + "dev": true, + "requires": { + "extend": "^3.0.2", + "findup-sync": "^4.0.0", + "fined": "^1.2.0", + "flagged-respawn": "^1.0.1", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.1", + "rechoir": "^0.7.0", + "resolve": "^1.19.0" + }, + "dependencies": { + "findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + } + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, + "load-grunt-tasks": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-3.5.2.tgz", + "integrity": "sha512-dwBbJ+Fmf1IrtASRdgT/KJNtczdlm+R3iLSi8KOGdCGl4V05uA055JHtafIXTyk5EJ1zDZbmEHndOQwU6uj8Jw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "multimatch": "^2.0.0", + "pkg-up": "^1.0.0", + "resolve-pkg": "^0.1.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "locutus": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.16.tgz", + "integrity": "sha512-pGfl6Hb/1mXLzrX5kl5lH7gz25ey0vwQssZp8Qo2CEF59di6KrAgdFm+0pW8ghLnvNzzJGj5tlWhhv2QbK3jeQ==", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.differencewith": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz", + "integrity": "sha512-/8JFjydAS+4bQuo3CpLMBv7WxGFyk7/etOAsrQUCu0a9QVDemxv0YQ0rFyeZvqlUD314SERfNlgnlqqHmaQ0Cg==", + "dev": true + }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true + }, + "map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true + }, + "map-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-values/-/map-values-1.0.1.tgz", + "integrity": "sha512-BbShUnr5OartXJe1GeccAWtfro11hhgNJg6G9/UtWKjVGvV5U4C09cg5nk8JUevhXODaXY+hQ3xxMUKSs62ONQ==", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true + }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + } + } + }, + "markdownlint": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.18.0.tgz", + "integrity": "sha512-nQAfK9Pbq0ZRoMC/abNGterEnV3kL8MZmi0WHhw8WJKoIbsm3cXGufGsxzCRvjW15cxe74KWcxRSKqwplS26Bw==", + "dev": true, + "requires": { + "markdown-it": "10.0.0" + } + }, + "markdownlint-cli": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.21.0.tgz", + "integrity": "sha512-gvnczz3W3Wgex851/cIQ/2y8GNhY+EVK8Ael8kRd8hoSQ0ps9xjhtwPwMyJPoiYbAoPxG6vSBFISiysaAbCEZg==", + "dev": true, + "requires": { + "commander": "~2.9.0", + "deep-extend": "~0.5.1", + "get-stdin": "~5.0.1", + "glob": "~7.1.2", + "ignore": "~5.1.4", + "js-yaml": "~3.13.1", + "jsonc-parser": "~2.2.0", + "lodash.differencewith": "~4.5.0", + "lodash.flatten": "~4.4.0", + "markdownlint": "~0.18.0", + "markdownlint-rule-helpers": "~0.6.0", + "minimatch": "~3.0.4", + "rc": "~1.2.7" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ignore": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", + "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "markdownlint-rule-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.6.0.tgz", + "integrity": "sha512-LiZVAbg9/cqkBHtLNNqHV3xuy4Y2L/KuGU6+ZXqCT9NnCdEkIoxeI5/96t+ExquBY0iHy2CVWxPH16nG1RKQVQ==", + "dev": true + }, + "mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "dependencies": { + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "requires": { + "@types/unist": "^2.0.2" + } + } + } + }, + "mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "dependencies": { + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + } + } + }, + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", + "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "dependencies": { + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + } + } + }, + "merge-deep": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + }, + "dependencies": { + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + } + } + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "optional": true + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", + "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "dev": true + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "moment": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", + "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==" + }, + "moment-timezone": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", + "integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha512-0mzK8ymiWdehTBiJh0vClAzGyQbdtyWqzSVx//EK4N/D+599RFlGfTAsKw2zMSABtDG9C6Ul2+t8f2Lbdjf5mA==", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + } + } + }, + "nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "optional": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dev": true, + "optional": true, + "requires": { + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true + } + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + } + } + }, + "node-notifier": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz", + "integrity": "sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==", + "dev": true, + "optional": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^2.1.1", + "semver": "^6.3.0", + "shellwords": "^0.1.1", + "which": "^1.3.1" + } + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==", + "dev": true, + "optional": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true + }, + "normalize-selector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", + "integrity": "sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-package-json-lint": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-5.4.2.tgz", + "integrity": "sha512-DH1MSvYvm+cuQFXcPehIIu/WiYzMYs7BOxlhOOFHaH2SNrA+P2uDtTEe5LOG90Ci7PTwgF/dCmSKM2HWTgWXNA==", + "dev": true, + "requires": { + "ajv": "^6.12.6", + "ajv-errors": "^1.0.1", + "chalk": "^4.1.2", + "cosmiconfig": "^7.0.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "ignore": "^5.1.9", + "is-plain-obj": "^3.0.0", + "jsonc-parser": "^3.0.0", + "log-symbols": "^4.1.0", + "meow": "^6.1.1", + "plur": "^4.0.0", + "semver": "^7.3.5", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "optional": true + }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + } + } + }, + "object-filter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz", + "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + } + } + }, + "object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "dev": true, + "requires": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, + "optional-require": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", + "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "dev": true, + "requires": { + "require-at": "^1.0.6" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true, + "optional": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pkg-up": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", + "integrity": "sha512-L+d849d9lz20hnRpUnWBRXOh+mAvygQpK7UuXiw+6QbPwL55RVgl+G+V936wCzs/6J7fj0pvgLY9OknZ+FqaNA==", + "dev": true, + "requires": { + "find-up": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "plur": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", + "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", + "dev": true, + "requires": { + "irregular-plurals": "^3.2.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "requires": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "dependencies": { + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", + "dev": true, + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-custom-properties": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-10.0.0.tgz", + "integrity": "sha512-55BPj5FudpCiPZzBaO+MOeqmwMDa+nV9/0QBJBfhZjYg6D9hE+rW9lpMBLTJoF4OTXnS5Po4yM1nMlgkPbCxFg==", + "dev": true, + "requires": { + "postcss": "^7.0.17", + "postcss-values-parser": "^4.0.0" + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-html": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", + "dev": true, + "requires": { + "htmlparser2": "^3.10.0" + }, + "dependencies": { + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "postcss-less": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "postcss-load-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true + } + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "postcss-rtl": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/postcss-rtl/-/postcss-rtl-1.7.3.tgz", + "integrity": "sha512-PMwlrQSeZKChNJQGtWz9Xfk3rY1W7P5Jp4sFRXVufczQIH6vRhTNSc5gnEwKHaWrU8SMoZMi2VY7ihOmwVvW7g==", + "dev": true, + "requires": { + "rtlcss": "2.5.0" + } + }, + "postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "dev": true, + "requires": { + "postcss": "^7.0.26" + } + }, + "postcss-sass": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz", + "integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==", + "dev": true, + "requires": { + "gonzales-pe": "^4.3.0", + "postcss": "^7.0.21" + } + }, + "postcss-scss": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", + "dev": true, + "requires": { + "postcss": "^7.0.6" + } + }, + "postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz", + "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true, + "requires": {} + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "postcss-values-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-4.0.0.tgz", + "integrity": "sha512-R9x2D87FcbhwXUmoCXJR85M1BLII5suXRuXibGYyBJ7lVDEpRIdKZh4+8q5S+/+A4m0IoG1U5tFw39asyhX/Hw==", + "dev": true, + "requires": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "postcss": "^7.0.5" + } + }, + "prebuild-install": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz", + "integrity": "sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==", + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "dev": true + }, + "prettier": { + "version": "npm:wp-prettier@2.2.1-beta-1", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", + "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-bytes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "integrity": "sha512-yJAF+AjbHKlxQ8eezMd/34Mnj/YTQ3i6kLzvVsH4l/BfIFtp444n0wVbnsn66JimZ9uBofv815aRp1zCppxlWw==", + "dev": true + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object.assign": "^4.1.0", + "reflect.ownkeys": "^0.2.0" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "puppeteer": { + "version": "npm:puppeteer-core@3.0.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-3.0.0.tgz", + "integrity": "sha512-oWjZFGMc0q2ak+8OxdmMffS79LIT0UEtmpV4h1/AARvESIqqKljf8mrfP+dQ2kas7XttsAZIxRBuWu7Y5JH8KQ==", + "dev": true, + "requires": { + "@types/mime-types": "^2.1.0", + "debug": "^4.1.0", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "mime-types": "^2.1.25", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dev": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "dev": true + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + } + } + }, + "react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "react-test-renderer": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", + "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "react-is": "^16.8.6", + "scheduler": "^0.19.1" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "realpath-native": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz", + "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==", + "dev": true + }, + "rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "requires": { + "resolve": "^1.9.0" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha512-qOLsBKHCpSOFKK1NUOCGC5VyeufB6lEsFe92AL2bhIJsacZS1qdoOZSbPk3MYKuT2cFlRDnulKXuuElIrMjGUg==", + "dev": true + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regextras": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz", + "integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==", + "dev": true + }, + "regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "remark": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", + "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", + "dev": true, + "requires": { + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.0", + "unified": "^9.1.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "remark-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "dev": true, + "requires": { + "mdast-util-from-markdown": "^0.8.0" + } + }, + "unified": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", + "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "dev": true, + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + } + }, + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "requires": { + "@types/unist": "^2.0.2" + } + }, + "vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + } + }, + "vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + } + } + } + }, + "remark-parse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", + "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "dev": true, + "requires": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, + "remark-stringify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "dev": true, + "requires": { + "mdast-util-to-markdown": "^0.6.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA==", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "require-at": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-bin": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/resolve-bin/-/resolve-bin-0.4.3.tgz", + "integrity": "sha512-9u8TMpc+SEHXxQXblXHz5yRvRZERkCZimFN9oz85QI3uhkh7nqfjm6OGTLg+8vucpXGcY4jLK6WkylPmt7GSvw==", + "dev": true, + "requires": { + "find-parent-dir": "~0.3.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "dev": true, + "requires": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "dependencies": { + "expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "dev": true, + "requires": { + "os-homedir": "^1.0.1" + } + } + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-pkg": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-0.1.0.tgz", + "integrity": "sha512-x11rPP22t6W9p+eSOhDeT6whjFGmsZQf76rDq5gtbgh9UdBxZeR1PuW6sYkCyN/IrD2hpJ0yYn0hE1toHAadEw==", + "dev": true, + "requires": { + "resolve-from": "^2.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==", + "dev": true + } + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==", + "dev": true, + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, + "rtlcss": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.5.0.tgz", + "integrity": "sha512-NCVdF45w70/3CQeqVvQ84bu2HN8agNn+CDjw+RxXaiWb7mPOmEvltdd1z4qzm9kin4Jnu9ShFBIx28yvWerZ2g==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "findup": "^0.1.5", + "mkdirp": "^0.5.1", + "postcss": "^6.0.23", + "strip-json-comments": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "sass": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz", + "integrity": "sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", + "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.6.1", + "semver": "^6.3.0" + }, + "dependencies": { + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "saxes": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", + "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", + "dev": true, + "requires": { + "xmlchars": "^2.1.1" + } + }, + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", + "dev": true, + "requires": { + "is-buffer": "^1.0.2" + } + }, + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", + "dev": true + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "shelljs": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.2.6.tgz", + "integrity": "sha512-LQiM15qPbSyzHDFfI4v7EVhjBXG5PUAKWVBnVMBXwdlQSHZtzKYeKGzDHBIqpenPrCsPWqBSOF5o7oSvSfX+CA==", + "dev": true + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "optional": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "optional": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "optional": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "dependencies": { + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "spawnd": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.4.0.tgz", + "integrity": "sha512-jLPOfB6QOEgMOQY15Z6+lwZEhH3F5ncXxIaZ7WHPIapwNNLyjrs61okj3VJ3K6tmP5TZ6cO0VAu9rEY4MD4YQg==", + "dev": true, + "requires": { + "exit": "^0.1.2", + "signal-exit": "^3.0.2", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.7" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "specificity": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "stack-utils": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", + "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + } + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "dev": true + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", + "dev": true + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-length": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", + "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^5.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "stylelint": { + "version": "13.13.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.13.1.tgz", + "integrity": "sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==", + "dev": true, + "requires": { + "@stylelint/postcss-css-in-js": "^0.37.2", + "@stylelint/postcss-markdown": "^0.36.2", + "autoprefixer": "^9.8.6", + "balanced-match": "^2.0.0", + "chalk": "^4.1.1", + "cosmiconfig": "^7.0.0", + "debug": "^4.3.1", + "execall": "^2.0.0", + "fast-glob": "^3.2.5", + "fastest-levenshtein": "^1.0.12", + "file-entry-cache": "^6.0.1", + "get-stdin": "^8.0.0", + "global-modules": "^2.0.0", + "globby": "^11.0.3", + "globjoin": "^0.1.4", + "html-tags": "^3.1.0", + "ignore": "^5.1.8", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "known-css-properties": "^0.21.0", + "lodash": "^4.17.21", + "log-symbols": "^4.1.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.4", + "normalize-selector": "^0.2.0", + "postcss": "^7.0.35", + "postcss-html": "^0.36.0", + "postcss-less": "^3.1.4", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^4.0.2", + "postcss-sass": "^0.4.4", + "postcss-scss": "^2.1.1", + "postcss-selector-parser": "^6.0.5", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^4.1.0", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "specificity": "^0.4.1", + "string-width": "^4.2.2", + "strip-ansi": "^6.0.0", + "style-search": "^0.1.0", + "sugarss": "^2.0.0", + "svg-tags": "^1.0.0", + "table": "^6.6.0", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^3.0.3" + }, + "dependencies": { + "balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + }, + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "ignore": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", + "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + } + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } + } + }, + "stylelint-config-recommended": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz", + "integrity": "sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==", + "dev": true, + "requires": {} + }, + "stylelint-config-recommended-scss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-4.3.0.tgz", + "integrity": "sha512-/noGjXlO8pJTr/Z3qGMoaRFK8n1BFfOqmAbX1RjTIcl4Yalr+LUb1zb9iQ7pRx1GsEBXOAm4g2z5/jou/pfMPg==", + "dev": true, + "requires": { + "stylelint-config-recommended": "^5.0.0" + }, + "dependencies": { + "stylelint-config-recommended": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-5.0.0.tgz", + "integrity": "sha512-c8aubuARSu5A3vEHLBeOSJt1udOdS+1iue7BmJDTSXoCBmfEQmmWX+59vYIj3NQdJBY6a/QRv1ozVFpaB9jaqA==", + "dev": true, + "requires": {} + } + } + }, + "stylelint-config-wordpress": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-wordpress/-/stylelint-config-wordpress-17.0.0.tgz", + "integrity": "sha512-qUU2kVMd2ezIV9AzRdgietIfnavRRENt4180A1OMoVXIowRjjhohZgBiyVPV5EtNKo3GTO63l8g/QGNG27/h9g==", + "dev": true, + "requires": { + "stylelint-config-recommended": "^3.0.0", + "stylelint-config-recommended-scss": "^4.2.0", + "stylelint-scss": "^3.17.2" + } + }, + "stylelint-scss": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.21.0.tgz", + "integrity": "sha512-CMI2wSHL+XVlNExpauy/+DbUcB/oUZLARDtMIXkpV/5yd8nthzylYd1cdHeDMJVBXeYHldsnebUX6MoV5zPW4A==", + "dev": true, + "requires": { + "lodash": "^4.17.15", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "sugarss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "dev": true + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + }, + "dependencies": { + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + } + } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + } + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.1.0.tgz", + "integrity": "sha512-cjdZte66fYkZ65rQ2oJfrdCAkkhJA7YLYk5eGOcGCSGlq0ieZupRdjedSQXYknMPo2IveQL+tPdrxUkERENCFA==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.2.1", + "p-limit": "^3.0.2", + "schema-utils": "^2.6.6", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.8.0", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "thread-loader": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-2.1.3.tgz", + "integrity": "sha512-wNrVKH2Lcf8ZrWxDF/khdlLlsTMczdcwPA9VEK4c2exlEPynYWxi9op3nPTo5lAnDIkE0rQEB3VBP+4Zncc9Hg==", + "dev": true, + "requires": { + "loader-runner": "^2.3.1", + "loader-utils": "^1.1.0", + "neo-async": "^2.6.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", + "dev": true + }, + "tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "requires": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", + "dev": true + }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", + "dev": true + }, + "trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true + }, + "trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", + "dev": true + }, + "trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true + }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true, + "peer": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "dev": true, + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + } + } + }, + "uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha512-46TFiBOzX7xq/PcSWfFwkyjpemdRnMe31UQF+os0y+1W3k95f6R4SEt02Hj4p3X0Mir9gfrkmOtshFidS0VPUg==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true + }, + "underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "dev": true, + "requires": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + } + } + }, + "unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dev": true, + "requires": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "unified": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", + "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "dev": true, + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^2.0.0", + "x-is-string": "^0.1.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + } + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ==", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unist-util-find-all-after": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz", + "integrity": "sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==", + "dev": true, + "requires": { + "unist-util-is": "^4.0.0" + } + }, + "unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true + }, + "unist-util-remove-position": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "dev": true, + "requires": { + "unist-util-visit": "^1.1.0" + } + }, + "unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "dev": true + }, + "unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "requires": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "requires": { + "unist-util-is": "^3.0.0" + }, + "dependencies": { + "unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + } + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "optional": true + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "dependencies": { + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "dev": true + } + } + }, + "url-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-3.0.0.tgz", + "integrity": "sha512-a84JJbIA5xTFTWyjjcPdnsu+41o/SNE8SpXMdUvXs6Q+LuhCD9E2+0VCiuDWqgo3GGXVlFHzArDmBpj9PgWn4A==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "mime": "^2.4.4", + "schema-utils": "^2.5.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "uuid-random": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uuid-random/-/uuid-random-1.3.0.tgz", + "integrity": "sha512-FSIlv8RFRPOjcHeDYStV7u6aJRfp+THrcWkbAJpw51JCyQLDxsFz+4dHgTYP8hSpZeSMXBpb/1qrK4bodXpSRA==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "v8-to-istanbul": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz", + "integrity": "sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + } + } + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + } + } + }, + "vfile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", + "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "dev": true, + "requires": { + "is-buffer": "^1.1.4", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + }, + "vfile-location": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "dev": true + }, + "vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "requires": { + "unist-util-stringify-position": "^1.1.1" + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", + "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "dev": true, + "requires": { + "domexception": "^1.0.1", + "webidl-conversions": "^4.0.2", + "xml-name-validator": "^3.0.0" + } + }, + "wait-on": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", + "integrity": "sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ==", + "dev": true, + "requires": { + "@hapi/joi": "^15.0.3", + "core-js": "^2.6.5", + "minimist": "^1.2.0", + "request": "^2.88.0", + "rx": "^4.1.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + } + } + }, + "wait-port": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz", + "integrity": "sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "walkdir": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", + "integrity": "sha512-lMFYXGpf7eg+RInVL021ZbJJT4hqsvsBvq5sZBp874jfhs3IWlA7OPoG0ojQrYcXHuUSi+Nqp6qGN+pPGaMgPQ==", + "dev": true + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + }, + "watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "optional": true + } + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "webpack": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "webpack-bundle-analyzer": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", + "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.19", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" + }, + "dependencies": { + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "ws": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "webpack-cli": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + } + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "webpack-fix-style-only-entries": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/webpack-fix-style-only-entries/-/webpack-fix-style-only-entries-0.5.1.tgz", + "integrity": "sha512-G4TBoc5JvIVNR0GXG+t314V4AqpqLJuApX7aDNTZl8yhXojAXCwJXKQeF4SF65UpP/bjrvptmHLPmdA+aGUZMw==", + "dev": true + }, + "webpack-livereload-plugin": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/webpack-livereload-plugin/-/webpack-livereload-plugin-2.3.0.tgz", + "integrity": "sha512-vVBLQLlNpElt2sfsBG+XLDeVbQFS4RrniVU8Hi1/hX5ycSfx6mtW8MEEITr2g0Cvo36kuPWShFFDuy+DS7KFMA==", + "dev": true, + "requires": { + "anymatch": "^3.1.1", + "portfinder": "^1.0.17", + "tiny-lr": "^1.1.1" + } + }, + "webpack-rtl-plugin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-rtl-plugin/-/webpack-rtl-plugin-2.0.0.tgz", + "integrity": "sha512-lROgFkiPjapg9tcZ8FiLWeP5pJoG00018aEjLTxSrVldPD1ON+LPlhKPHjb7eE8Bc0+KL23pxcAjWDGOv9+UAw==", + "dev": true, + "requires": { + "@romainberger/css-diff": "^1.0.3", + "async": "^2.0.0", + "cssnano": "4.1.10", + "rtlcss": "2.4.0", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "rtlcss": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.4.0.tgz", + "integrity": "sha512-hdjFhZ5FCI0ABOfyXOMOhBtwPWtANLCG7rOiOcRf+yi5eDdxmDjqBruWouEnwVdzfh/TWF6NNncIEsigOCFZOA==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "findup": "^0.1.5", + "mkdirp": "^0.5.1", + "postcss": "^6.0.14", + "strip-json-comments": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, + "which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "requires": {} + }, + "x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "zip-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", + "integrity": "sha512-2olrDUuPM4NvRIgGPhvrp84f7/HmWR6RiQrgwFF2VctmnssFiogtYL3DcA8Vl2bsSmju79sVXe38TsII7JleUg==", + "dev": true, + "requires": { + "archiver-utils": "^1.3.0", + "compress-commons": "^1.2.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0" + } + }, + "zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000000..d457babd8be --- /dev/null +++ b/package.json @@ -0,0 +1,82 @@ +{ + "name": "easy-digital-downloads", + "version": "3.1.5", + "description": "The easiest way to sell digital products with WordPress.", + "private": true, + "author": "Easy Digital Downloads", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "wordpress-plugin" + ], + "homepage": "https://easydigitaldownloads.com", + "repository": { + "type": "git", + "url": "https://github.com/easydigitaldownloads/easy-digital-downloads/" + }, + "bugs": { + "url": "https://github.com/easydigitaldownloads/easy-digital-downloads/issues" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">=6.9.0" + }, + "main": "webpack.config.js", + "dependencies": { + "chart.js": "2.9.3", + "flot": "0.8.0-alpha", + "jquery-creditcardvalidator": "1.0.0", + "jquery-validation": "^1.19.3", + "jquery.payment": "3.0.0", + "moment": "2.29.3", + "moment-timezone": "0.5.23" + }, + "devDependencies": { + "@wordpress/base-styles": "3.1.0", + "@wordpress/dom": "2.15.0", + "@wordpress/postcss-plugins-preset": "^1.6.0", + "@wordpress/scripts": "12.6.1", + "copy-webpack-plugin": "6.2.0", + "cross-env": "7.0.2", + "grunt": "^1.5.2", + "grunt-checktextdomain": "^1.0.1", + "grunt-cli": "^1.3.2", + "grunt-composer": "^0.4.5", + "grunt-contrib-clean": "^1.1.0", + "grunt-contrib-compress": "^1.4.3", + "grunt-contrib-copy": "^1.0.0", + "grunt-force-task": "^2.0.0", + "grunt-replace": "^2.0.1", + "load-grunt-tasks": "^3.5.2", + "locutus": "^2.0.11", + "mini-css-extract-plugin": "0.9.0", + "postcss-rtl": "^1.7.3", + "uglify-es": "3.3.9", + "uglifyjs-webpack-plugin": "^1.1.2", + "uuid-random": "1.3.0", + "webpack": "4.44.2", + "webpack-cli": "3.3.12", + "webpack-fix-style-only-entries": "0.5.1", + "webpack-rtl-plugin": "^2.0.0" + }, + "scripts": { + "build": "wp-scripts build --webpack-no-externals && npm run stripe && npm run blocks && grunt prep && npm run translate && grunt copy && grunt replace:pro && npm run translate-lite", + "dev": "cross-env NODE_ENV=production wp-scripts start --webpack-no-externals", + "check-engines": "wp-scripts check-engines", + "check-licenses": "wp-scripts check-licenses --production", + "lint:css": "wp-scripts lint-style 'assets/css/admin/**/*.scss' --fix", + "lint:js": "wp-scripts lint-js ./assets/js/ ", + "lint:js--fix": "wp-scripts lint-js ./assets/js --fix", + "lint:pkg-json": "wp-scripts lint-pkg-json", + "package-plugin": "npm run build && grunt compress", + "test:unit": "wp-scripts test-unit-js --config tests/jest/jest.config.js", + "blocks": "bash bin/update-blocks.sh && grunt replace:blocks", + "stripe": "bash bin/update-stripe.sh && grunt replace:stripe", + "translate": "grunt clean && wp i18n make-pot . languages/easy-digital-downloads.pot --exclude=node_modules,assets,tests --ignore-domain", + "translate-lite": "wp i18n make-pot build/easy-digital-downloads build/easy-digital-downloads/languages/easy-digital-downloads.pot --exclude=build/easy-digital-downloads/assets --ignore-domain", + "update": "composer update --no-dev --ignore-platform-reqs", + "lite": "grunt clean && grunt copy:lite && grunt replace:pro && npm run translate-lite && grunt compress:lite", + "pro": "grunt clean && grunt copy:pro && grunt compress:pro", + "local": "grunt clean && grunt copy && grunt replace:pro && npm run translate-lite && grunt compress" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100755 index 00000000000..cb514bc1c9c --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,46 @@ + + + + ./tests/ + ./tests/phpunit/class-edd-unittestcase.php + + + + + ./includes + easy-digital-downloads.php + uninstall.php + + ./includes/admin/payments/view-order-details.php + ./includes/admin/discounts/edit-discount.php + ./includes/admin/discounts/add-discount.php + ./includes/admin/reporting/class-export-customers.php + ./includes/admin/reporting/class-export-download-history.php + ./includes/admin/reporting/class-export-payments.php + ./includes/admin/tools/class-edd-tools-recount-all-stats.php + ./includes/admin/tools/class-edd-tools-recount-customer-stats.php + ./includes/admin/tools/class-edd-tools-recount-download-stats.php + ./includes/admin/tools/class-edd-tools-recount-single-customer-stats.php + ./includes/admin/tools/class-edd-tools-recount-store-earnings.php + ./includes/admin/tools/class-edd-tools-reset-stats.php + ./includes/admin/import/class-batch-import-downloads.php + ./includes/admin/import/class-batch-import-payments.php + ./includes/class-edd-cli.php + ./includes/admin/reporting/export + ./includes/libraries/ + + + + + + edd_ajax + + + diff --git a/readme.md b/readme.md new file mode 100755 index 00000000000..b448d4e85ca --- /dev/null +++ b/readme.md @@ -0,0 +1,34 @@ +# [Easy Digital Downloads](https://easydigitaldownloads.com) # + +![Plugin Version](https://img.shields.io/wordpress/plugin/v/easy-digital-downloads.svg?maxAge=172800) ![Total Downloads](https://img.shields.io/wordpress/plugin/dt/easy-digital-downloads.svg?maxAge=172800) ![Plugin Rating](https://img.shields.io/wordpress/plugin/r/easy-digital-downloads.svg?maxAge=172800) ![WordPress Compatibility](https://img.shields.io/wordpress/v/easy-digital-downloads.svg?maxAge=172800) [![Build status](https://badge.buildkite.com/e318e1649f3a28f4029272231b926126d5664837d575cd507a.svg?branch=main)](https://buildkite.com/sandhills-development-llc/easy-digital-downloads?branch=main) [![License](https://img.shields.io/badge/license-GPL--2.0%2B-red.svg)](https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/license.txt) + +### Welcome to our GitHub Repository + +Selling digital downloads is something that not a single one of the large WordPress ecommerce plugins has ever gotten really right. This plugin aims to fix that. Instead of focusing on providing every single feature under the sun, Easy Digital Downloads tries to provide only the ones that you really need. It aims to make selling digital downloads through WordPress easy, and complete. + +More information can be found at [easydigitaldownloads.com](https://easydigitaldownloads.com/). + +## Installation ## + +For detailed setup instructions, visit the official [Documentation](https://easydigitaldownloads.com/documentation/) page. + +1. You can clone the GitHub repository: `https://github.com/easydigitaldownloads/easy-digital-downloads.git` +2. Or download it directly as a ZIP file: `https://github.com/easydigitaldownloads/easy-digital-downloads/archive/master.zip` + +This will download the latest developer copy of Easy Digital Downloads. + +## Bugs ## +If you find an issue, let us know [here](https://github.com/easydigitaldownloads/easy-digital-downloads/issues?state=open)! + +## Support ## +This is a developer's portal for Easy Digital Downloads and should _not_ be used for support. Please visit the [support page](https://easydigitaldownloads.com/support) if you need to submit a support request. + +## Contributions ## +Anyone is welcome to contribute to Easy Digital Downloads. Please read the [guidelines for contributing](https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/CONTRIBUTING.md) to this repository. + +There are various ways you can contribute: + +1. Raise an [Issue](https://github.com/easydigitaldownloads/easy-digital-downloads/issues) on GitHub +2. Send us a Pull Request with your bug fixes and/or new features +3. Translate Easy Digital Downloads into [different languages](https://translate.wordpress.org/projects/wp-plugins/easy-digital-downloads/) +4. Provide feedback and suggestions on [enhancements](https://github.com/easydigitaldownloads/easy-digital-downloads/issues?direction=desc&labels=Enhancement&page=1&sort=created&state=open) diff --git a/readme.txt b/readme.txt index c1098de4d05..ad48da26409 100755 --- a/readme.txt +++ b/readme.txt @@ -1,1156 +1,265 @@ -=== Easy Digital Downloads === -Author URI: http://pippinsplugins.com -Plugin URI: http://easydigitaldownloads.com -Contributors: mordauk, sksmatt, sunnyratilal, spencerfinnell -Donate link: http://pippinsplugins.com/support-the-site -Tags: download, downloads, e-store, eshop, digital downloads, e-downloads, ecommerce, e commerce, e-commerce, selling, wp-ecommerce, wp ecommerce, mordauk, Pippin Williamson, pippinsplugins -Requires at least: 3.2 -Tested up to: 3.4.2 -Stable Tag: 1.2.2 +=== Easy Digital Downloads - Simple eCommerce for Selling Digital Files === +Author URI: https://easydigitaldownloads.com +Plugin URI: https://easydigitaldownloads.com +Contributors: easydigitaldownloads, am, cklosows, littlerchicken, achchu93, smub, mordauk, sunnyratilal, chriscct7, section214, sumobi, sdavis2702, mindctrl, sksmatt, SpencerFinnell, johnstonphilip, brashrebel, drewapicture, johnjamesjacoby, nosegraze, lisacee, zkawesome +Tags: ecommerce, payments, sell, digital store, stripe +Requires at least: 5.4 +Tested up to: 6.2 +Requires PHP: 7.1 +Stable Tag: 3.1.5 +License: GNU Version 2 or Any Later Version + +Sell your digital products with the #1 eCommerce plugin written for digital creators by digital creators. +== Description == -Sell digital downloads through WordPress with this complete digital downloads management plugin += eCommerce without limits = +We believe that _your passion_ for creating shouldn't be restricted by your choice of ecommerce software. **Easy Digital Downloads** gives you unlimited products with no hidden listing fees, unlimited products, unlimited transactions, and provides unlimited possibilities. -== Description == +Too often, digital creators must choose between powerful and cheap. Not with Easy Digital Downloads. It's easy to start with limitless possibilities, which is why some of the most popular digital ecommerce businesses trust Easy Digital Downloads with their store. + += Sell effortlessly = + +Whether it is selling [software like WordPress plugins or themes](https://easydigitaldownloads.com/blog/selling-software/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description), [documents](https://easydigitaldownloads.com/blog/selling-documents/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description) and [spreadsheets](https://easydigitaldownloads.com/blog/sell-excel-or-google-spreadsheets/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description), [photography](https://easydigitaldownloads.com/blog/selling-photographs/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description), [ebooks](https://easydigitaldownloads.com/blog/selling-ebooks/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description), [music](https://easydigitaldownloads.com/blog/selling-audio-and-music/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description), [graphics or digital artwork](https://easydigitaldownloads.com/blog/selling-graphics-and-digital-artwork/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description), [videos](https://easydigitaldownloads.com/blog/selling-videos/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description), or any other type of media file, Easy Digital Downloads provides a complete ecommerce platform that just works, so you can get back to creating. + +> Easy Digital Downloads is an invaluable tool that I use throughout my business and for my clients. I love its simplicity yet powerful features and library of useful extensions. EDD stands out as one of the only affordable options for scaling a business centered around providing digital ecommerce sites to its clients. + +> Mark Hunter - Mark of Approval + +Within minutes of starting the Setup Wizard, you'll have access to the base features you'll need to start selling: + +- **Complete shopping cart** - Customers can browse your products, add items to their cart, and checkout when ready. Or they can bypass the cart using Buy Now buttons from your product page. +- **Flexible payment methods** - Your customers can pay by card, PayPal, PayPal Credit, Apple Pay, Google Pay, and Link by Stripe. +- **Highly customizable and clean checkout** - Buyers can easily complete their purchase, directly on your site's checkout page with the included WordPress Block. +- **Complete customer management** - View and manage detailed records of all customers and their purchase history. +- **Detailed eCommerce reports** - Keep track of your earnings, refunds, sales, average order value, and more. +- **Secure file protection** - Prevent unauthorized visitors from accessing your product files without paying. +- **Discount codes** - Boost conversions by offering potential customers a coupon or discount code which can be applied at checkout. +- **Works with any theme** - Let your chosen WordPress theme handle the design. Easy Digital Downloads will fit right in. +- [And much more](https://easydigitaldownloads.com/edd-features/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_content=much-more&utm_term=description)! + += Localized for your language = + +Easy Digital Downloads has been translated into more than two dozen languages and new translations are being added all the time, letting you have a natural, native experience, regardless of where you live. If your language is not available, we welcome translation contributions! See our [FAQ](https://easydigitaldownloads.com/docs/easy-digital-downloads-in-your-own-language/?utm_source=docs&utm_medium=description_tab&utm_content=languages&utm_campaign=readme). + +> EDD gives me a solid platform out of the box, with the flexibility of being able to customize anything I want or need to. The best of both worlds! + +> Brian Hogg – SellingPlugins.com + += Support for many payment gateways, including Stripe and PayPal = + +The internet has made it possible for anyone to sell their products to a world-wide audience. No matter where you live, Easy Digital Downloads can be your e-Commerce solution. We offer integrations for the most common merchant processors. + +Offer multiple payment methods, and start selling today: + +* Credit Cards/Bank Cards - Via Stripe +* Apple Pay +* Google Pay +* Link by Stripe +* PayPal +* PayPal Credit + +Payment gateways supported through free or paid extensions: + +* Stripe Pro (reduced fees and preapproved payments functionality) +* PayPal Commerce Pro (unbranded card payments and additional payment methods) + +> What can I say? Stripe is my go-to payment processor, and without this [integration] I don't know what I would do. Connect EDD to your Stripe account in seconds, and you're done! Having a simple test mode toggle is also a valuable development tool. -Selling digital downloads is something that not a single one of the large WordPress ecommerce plugins has ever gotten really right. This plugin aims to fix that. Instead of focusing on providing every single feature under the sun, Easy Digital Downloads tries to provide only the ones that you really need. It aims to make selling digital downloads through WordPress easy, and complete. +> Matt Whiteley - Whiteley Designs -**Follow this plugin on [Git Hub](https://github.com/pippinsplugins/Easy-Digital-Downloads)** +Easy Digital Downloads is a verified member of the [Stripe partner program](https://stripe.com/partners/directory/easy-digital-downloads). This program recognizes software that meets Stripe's strict quality requirements and provides users of Easy Digital Downloads and Stripe with the confidence they need to trust thier business is secure. -Features of the plugin include: += Take your store further with a Pass = -* Cart system for purchasing multiple downloads at once -* Complete promotional code system -* Many payment gateways. PayPal and Manual are included by default with Stripe, PayPal Pro, PayPal Express, and others available as [add-ons](http://easydigitaldownloads.com/extensions/) -* Complete payment history -* User purchase history and ability to redownload files -* Multiple files per downloadable product -* Variable prices for multiple price options per product -* Customizable purchase receipts -* Earnings and sales charts -* Detailed purchase and file download logs -* Extensible with many [add-ons](http://easydigitaldownloads.com/extensions/) -* Developer friendly with dozens of actions and filters +With our Passes you can get access to [additional features](https://easydigitaldownloads.com/downloads/?utm_source=extensions&utm_medium=description_tab&utm_content=extensions&utm_campaign=readme) and super-charge your ecommerce business. Some of our most popular features in Pro are: -More information at [Easy Digital Downloads.com](http://easydigitaldownloads.com/). +* [Stripe Pro](https://easydigitaldownloads.com/downloads/stripe-gateway/?utm_source=extension&utm_medium=description_tab&utm_content=stripe-pro&utm_campaign=readme) removes the additional 2% processing fee as well as adds the ability to accept pre-approved payments (extension must be installed and activated). +* [PayPal Commerce Pro](https://easydigitaldownloads.com/downloads/paypal-commerce-pro/?utm_source=extension&utm_medium=description_tab&utm_content=paypal-commerce-pro&utm_campaign=readme) allows customers to pay by debit or credit card as well as alternative payments such as Sofort, iDEAL, Bancontact, and Giropay (extension must be installed and activated). +* [Software Licensing](https://easydigitaldownloads.com/downloads/software-licensing/?utm_source=extension&utm_medium=description_tab&utm_content=software-licensing&utm_campaign=readme) provides a complete solution for selling software license keys and distributing software updates to customers. Trusted by some of the most successful WordPress Theme and Plugin developers. +* [Recurring Payments](https://easydigitaldownloads.com/downloads/recurring-payments/?utm_source=extension&utm_medium=description_tab&utm_content=recurring-payments&utm_campaign=readme) allows store owners to sell recurring subscriptions for digital products using payment gateways like Stripe and PayPal. +* [Frontend Submissions](https://easydigitaldownloads.com/downloads/frontend-submissions/?utm_source=extension&utm_medium=description_tab&utm_content=frontend-submissions&utm_campaign=readme) transforms your store into a full-featured multi-vendor marketplace. +* [Commissions](https://easydigitaldownloads.com/downloads/commissions/?utm_source=extension&utm_medium=description_tab&utm_content=commissions&utm_campaign=readme) enables store owners to easily track earnings for product vendors. +* [Free Downloads](https://easydigitaldownloads.com/downloads/free-downloads/?utm_source=extension&utm_medium=description_tab&utm_content=free-downloads&utm_campaign=readme) allows you to give free products away as lead magnets. +* [Reviews](https://easydigitaldownloads.com/downloads/product-reviews/?utm_source=extension&utm_medium=description_tab&utm_content=reviews&utm_campaign=readme) allows customers to leave reviews and feedback on the products they have purchased. +* [Recommended Products](https://easydigitaldownloads.com/downloads/recommended-products/?utm_source=extension&utm_medium=description_tab&utm_content=recommended-products&utm_campaign=readme) increases revenue by showing customers recommendations on additional products to purchase based on real sales data. +* [Content Restriction](https://easydigitaldownloads.com/downloads/content-restriction/?utm_source=extension&utm_medium=description_tab&utm_content=content-restriction&utm_campaign=readme) allows site owners to restrict access to page content to paying customers. +* [Mailchimp](https://easydigitaldownloads.com/downloads/mailchimp/?utm_source=extension&utm_medium=description_tab&utm_content=mailchimp&utm_campaign=readme) connects your store to your Mailchimp account so you can easily subscribe customers to email lists based on which products they purchase. -[youtube http://www.youtube.com/watch?v=SjOeSZ08_IA] +These are just a few of the features available to help you optimize your digital store and increase revenue. See our [complete list of extensions](https://easydigitaldownloads.com/downloads/?utm_source=extensions&utm_medium=description_tab&utm_content=extensions&utm_campaign=readme) for more. -**Sell and Track Software License Keys** += Built with developers in mind = -[Software Licensing](https://easydigitaldownloads.com/extension/software-licensing/) for Easy Digital Downloads provides a complete license key generation, activation, and checking system. It allows you to provide license keys along with your digital products that can then be used to properly license and activate the buyer’s copy of the software. +Extensible, adaptable, and open source -- Easy Digital Downloads is created with designers and developers in mind. Consult our detailed [developer documentation](https://easydigitaldownloads.com/categories/docs/developer-docs/?utm_source=wporg&utm_medium=readme&utm_content=documentation&utm_campaign=dev-docs) to learn how you can extend and customize your Easy Digital Downloads powered eCommerce store. -Includes a complete integration pack for delivering automatic upgrades for premium WordPress plugins and themes. +The Easy Digital Downloads API makes it possible for developers to make customizations such as: -**Add an Affiliate System to Your Store** +- Modifying the checkout form +- Overriding default plugin behavior +- Generating custom e-Commerce reports +- Extending native features +- Creating new product page templates +- Integrating with other plugins and web applications -Easy Digital Downloads has an [integration pack for the awesome Affiliates Pro plugin](http://easydigitaldownloads.com/extension/affiliates-pro-integration-pack/), which gives you everything you need to build a complete affiliate system and dramatically boost your traffic and sales. +...and much much more! -**Build Up Your Email Subscribers** +> As a developer, Easy Digital Downloads is very pleasant to work with since you can absolutely extend anything available within EDD. -With add-ons for [Mail Chimp](http://easydigitaldownloads.com/extension/mail-chimp/), [Campaign Monitor](http://easydigitaldownloads.com/extension/campaign-monitor/), and [AWeber](http://easydigitaldownloads.com/extension/aweber/), Easy Digital Downloads can easily grow your email subscription lists while making you money at the same time. +> Yudhistira Mauris - WP House -**Languages** += Get help = -Easy Digital Downloads as been translated into the following languages: +Easy Digital Downloads is backed by world-class technical support from our globally distributed full-time support team. We also have an [extensive documentation site available](https://easydigitaldownloads.com/docs/?utm_medium=readme&utm_source=wporg&utm_campaign=lite-plugin&utm_content=docs&utm_term=description). If you're looking for faster support via email, we encourage you to [purchase an Easy Digital Downloads pass](https://easydigitaldownloads.com/pricing/?utm_medium=readme&utm_source=wporg&utm_campaign=lite-plugin&utm_content=upgrade&utm_term=description). -1. English -2. German -3. Spanish -4. French -5. Italian -6. Dutch -7. European Portuguese -8. Turkish -9. Arabic +> EDD has been a long standing, rock-solid e-commerce solution for WordPress. The team lives and breathes WordPress, understands the platform, and is embedded in the community. -Would you like to help translate the plugin into more langauges? [Contact Pippin](http://easydigitaldownloads.com/contact-developer/). +> Joe Casabona - Creator Courses + += From our blog = + +Check out some of our popular posts for actionable advice for running your business and selling digital files on the web. + +- [The 12 Most Popular Digital Products You Can Sell Online](https://easydigitaldownloads.com/blog/the-12-most-popular-digital-products-you-can-sell-online/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description) +- [How to Add Lead Magnets in WordPress to Grow Your Email List](https://easydigitaldownloads.com/blog/how-to-add-lead-magnets-in-wordpress-to-grow-your-email-list/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_content=lead-magnets&utm_term=description) +- [How to Successfully Launch Your Digital Product](https://easydigitaldownloads.com/blog/how-to-successfully-launch-your-digital-product/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_content=launch&utm_term=description) +- [How to Sell Canva Templates with WordPress](https://easydigitaldownloads.com/blog/how-to-sell-canva-templates-with-wordpress/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_content=canva&utm_term=description) +- [The Astonishing Benefits of Selling Digital Products](https://easydigitaldownloads.com/blog/top-10-benefits-selling-digital-products/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_content=benefits&utm_term=description) + +**[Subscribe to our newsletter](https://easydigitaldownloads.com/subscribe/?utm_medium=readme&utm_source=wporg&utm_campaign=lite-plugin&utm_content=subscribe&utm_term=description)** to get posts like these in your inbox as soon as they're published. + += Looking to change e-Commerce solutions? = + +Easy Digital Downloads is the perfect digital e-Commerce alternative to Etsy, Gumroad, WooCommerce, Shopfiy, BigCommerce, or Wix. + +You can use Easy Digital Downloads to sell your digital products with less fees than Gumroad or Etsy, and create unlimited products, and never pay a listing fee. + +If Easy Digital Downloads doesn't quite fit your needs, we suggest you try WP Simple Pay. + +[WP Simple Pay](https://wpsimplepay.com/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_content=something-else&utm_term=description) is the #1 Stripe Payments Plugin for WordPress. + += Branding guideline = +Easy Digital Downloads is a product by Sandhills Development, LLC. When writing about the digital eCommerce plugin by Easy Digital Downloads, please make sure to always reference us by full name before you use our short name of EDD. + +Easy Digital Downloads (correct) +EDD (correct) +easy digital downloads (incorrect) +Edd (incorrect, this is our mascot's name!) +edd (incorrect) == Installation == 1. Activate the plugin -2. Go to Downloads > Settings and configure the options -3. Create Downloadable products from the Downloads page -4. Insert purchase buttons for any download via the "Insert Download" button next the Upload Media buttons -5. For detailed setup instructions, vist the official [Documentation](http://easydigitaldownloads.com/documentation/) page. +2. Complete the Onboarding Wizard +3. Start selling your first product! +4. For more detailed setup instructions, vist the official [Documentation](https://easydigitaldownloads.com/docs/quickstart-guide/?utm_source=docs&utm_medium=installation_tab&utm_content=documentation&utm_campaign=readme) page. == Frequently Asked Questions == -= How do I Show My Shopping Cart? = += Where can I find complete documentation? = -There are three ways you can show the downloads shopping cart: +Full searchable docs for Easy Digital Downloads and all our extensions can be found at [https://easydigitaldownloads.com/docs](https://easydigitaldownloads.com/docs/?utm_source=docs&utm_medium=faq_tab&utm_content=documentation&utm_campaign=readme) -1. Use the short code and simply place [download_cart] on a page or within a text widget. += Where can I ask for help with my digital e-Commerce store? = -2. Use the included widget. Go to Appearance > Widgets and place the "Downloads Cart" widget into any widget area available. +You can submit a support ticket via the [support forum](https://wordpress.org/support/plugin/easy-digital-downloads/) at anytime. -3. Use the template tag and place the following the template file of your choosing: += Which file types can I sell? = -`echo edd_shopping_cart();` +Any file type can be sold using Easy Digital Downloads. You can sell .PDF, .DOC, .MP3, .MOV, .EPUB, .PSD, .MP4, .JPG, or any other extension which exists. On the product creation form, there is a file upload field which will accept any file type. Whatever file is uploaded to that field is the file customers will receive instantly when they complete a purchase. -= My Payments Marked as Pending = += How can I increase conversions? = -There are several reasons this happens. Please follow the suggestions [here](https://easydigitaldownloads.com/documentation/faqs/). +Due to its popularity, many ecommerce tools have built direct integrations with Easy Digital Downloads, such as [OptinMonster](https://optinmonster.com). With the direct EDD integration, you can optimize your sales funnel using OptinMonster's display rules based on the customer's EDD cart contents and conditions. -= Getting a 404 error? = += Is an SSL certificate required to sell digital products? = -To get rid of the 404 error when viewing a download, you need to resave your permalink structure. Go to Settings > Permalinks and click "Save Changes". +Easy Digital Downloads can function without an SSL certificate just fine, making it easy to set up in a testing or development environment. We still strongly recommend you have an SSL certificate for your live store, both for security and for the peace of mind of your customers. [Documentation here](https://easydigitaldownloads.com/docs/do-i-need-an-ssl-certificate/?994-how-to-set-up-ssl?utm_source=docs&utm_medium=faq_tab&utm_content=documentation&utm_campaign=readme) -= How do I Show the User's Purchase History? = += What themes work with Easy Digital Downloads? = -Place the [purchase_history] short code on any page. +While nearly any theme will work with Easy Digital Downloads, we've worked with industry-leading theme developers to curate a [list of themes that are specifically written for Easy Digital Downloads](https://easydigitaldownloads.com/themes/?utm_source=docs&utm_medium=faq_tab&utm_term=import_addon&utm_content=faqs&utm_campaign=readme). -If you want to just show a list of the files the user has purchased, use the [download_history] short code instead. += Is there a sample import file I can use to setup a demo store? = -= Can I Setup an Affiliate System? = +Yes! Simply go to Tools > Import and install the WordPress Importer, then navigate to wp-content/plugins/easy-digital-downloads/assets/ and select the sample-products-import.xml file. This will create several sample products and plugin pages for you. -Yes! EDD has an add-on that provides a complete affiliate system that you can use to award commissions to your affiliate marketers. += Can I import all the products from my existing store into EDD? = -[Checkout Affiliates Pro + EDD Integration Pack](http://easydigitaldownloads.com/extension/affiliates-pro-integration-pack/) +Yes! We do have a [basic import solution](https://easydigitaldownloads.com/docs/importing-exporting-products/?utm_source=docs&utm_medium=faq_tab&utm_term=import_addon&utm_content=faqs&utm_campaign=readme) that imports a CSV file, so if you can get your data into the appropriate format then we can import it. -= Can Users Purchase Products without Using PayPal? = += How do I migrate from Gumroad? = -Yes, through the addition of one or more of the add-on payment gateways, you can accept payments in many different ways. The add-on gateways currently available: +Check out our guide on [migrating to Easy Digital Downloads from Gumroad](https://easydigitaldownloads.com/docs/migrating-to-easy-digital-downloads-from-gumroad/?utm_source=docs&utm_medium=faq_tab&utm_term=import_addon&utm_content=faqs&utm_campaign=readme). -* [Stripe](http://easydigitaldownloads.com/extension/stripe-payment-gateway/) -* [Recurly](http://easydigitaldownloads.com/extension/recurly-com-checkout/) -* [Authorize.net](http://easydigitaldownloads.com/extension/authorize-net-gateway/) -* [Moneybookers / Skrill](http://easydigitaldownloads.com/extension/moneybookers-skrill-payment-gateway/) -* [2Checkout](http://easydigitaldownloads.com/extension/2checkout-gateway/) -* [PayPal Pro / Express](http://easydigitaldownloads.com/extension/paypal-pro-express/) -* [Mijireh Checkout](http://easydigitaldownloads.com/extension/mijireh-checkout/) -* [MercadoPago](http://easydigitaldownloads.com/extension/mercadopago/) -* More coming soon += How do I show the user's purchase history? = -== Screenshots == +You can display your customer's purchase history with the EDD Order History Block or if you prefer, you can show users a list of their available downloads with the EDD User Downloads block. Check out our documentation for more information about [Easy Digital Download's Blocks](https://easydigitaldownloads.com/docs/easy-digital-downloads-blocks/?utm_source=docs&utm_medium=faq_tab&utm_content=faqs&utm_campaign=readme). -1. Download products overview -2. Download configuration -3. Download configuration details -4. Download configuration with variable prices -5. Payment history -6. Discount codes -7. Earnings and sales reports -8. Add to cart / purchase button -9. Checkout screen += How do I display my digital products? = +Easy Digital Downloads comes with a [customizable WordPress Block](https://easydigitaldownloads.com/docs/easy-digital-downloads-blocks/?utm_source=docs&utm_medium=faq_tab&utm_content=faqs&utm_campaign=readme), giving you the ability to display your products your way. -== Changelog == - -= 1.2.2 = - -* Fixed problem with sorting downloads by price and added compatibility for Post Types Order plugin -* Improved file organization -* Added new Arabic language files -* Added a Sales Summary dashboard widget -* Updated download configuration with brand new UI and simplified options -* Removed button text / style from Download Configuration -* Added new default button color/style options to Settings > Styles -* Added new default add-to-cart button text option in Settings > Misc -* Added a new query filter to block direct access to attachments of downloads -* Fixed bug with undefined index error with the metabox nonce -* Added a check to prevent users from deleting all price/download files in download configuration -* Added WPML config file for more WPML compatibility -* Dramatically improved admin javascript functions to be more extendable -* Updated French language files -* Added cache flush for user purchase history when payments are created -* Fixed a bug with percentage based discount codes -* Updated edd_email_template_tags with $payment_id - -= 1.2.1.1 = - -* Fixed a bug with the earnings per day graph -* Added a new filter for edd_receipt_attachments -* Improved extendability of payment form -* Fixed a minor CSS conflict with purchase buttons -* Fixed a security exploit that made it possible for malicious users to purchase variable priced items for free - -= 1.2.1 = - -* Added new filters to the taxonomy args for download categories and tags -* Fixed incorrect help text for the start/end date of discount codes -* Fixed a couple of typos -* Added a new "Product Notes" meta box to the Edit Download page -* Updated German translation -* Added some missing text domains for strings -* Fixed a problem with manually completing purchases -* Fixed some problems with international number formatting - -= 1.2 = - -* Added reset styles to default fieldsets to account for themes without fieldset styling -* Added new Date and Time to the View Details purchase popup -* Fixed a bug with radio button toggling in Download Configuration -* Added new CSS for improved styling of checkout error messages -* Added new filter to edd_get_checkout_uri -* Updated [purchase_collection] to support custom link text -* Improved the edd_delete_purchase() function -* Added a missing closing anchor tag -* Added an error notice on download pages if no checkout page is set -* Added labels to the checkboxes in the Download Configuration meta box to make fields easier to click on -* Added new filter to the EDD languages directory -* Fixed a bug in the email template preview -* Updated Italian language files -* Updated German language files -* Replaced hard-coded instance of "Downloads" with plural label function -* Updated French translation -* Added huge new templating system to [download_history] and [purchase_history] short codes -* Added huge new templating system for [downloads] short code -* Improved the edd_append_purchase_link() function -* Added new edd_payments_page_date_format filter to allow date format in Payment History to be changed -* Removed "Deleted" from the payment hsitory filter options -* Made improvements to script loading -* Fixed a problem with the auto-generated short code on the All Downloads page -* Added a new edd_get_payments_args filter -* Added the ability to sort payments by their total price -* Minor improvements to the add-ons page -* Added a new meta key for payment total so that payments can be sorted by amount -* Fixed a problem with new downloads not being able to add download files -* Improvements to add/remove to/from cart functions with ajax -* Updated default language files -* Fixed an incorrect variable name in register-settings.php -* Added a new option to export all customer emails from Payment History -* Fixed a problem with checkout ajax when permalinks are not enabled -* Created new global checkout fields and validation methods for use by payment gateways -* Added new "Purchase History" widget -* Added new classes/div to the purchase history widget -* Improved data sanitization in many files -* Improved code formatting in many files -* Fixed a few small typos in labels -* Added support for absolute file paths for download files -* Added a lot of new helper functions for developers -* Changed the Earnings Per Day graph to show the latest 30 days, instead of the current month -* Improved the way files are downloaded, especially for large files -* Fixed a discrepancy with the sales per month graph -* Added dozens of new content types to the download processing function -* Fixed several undefined index errors -* Improved the UI of Download Configuration meta box -* Added new "Total Earnings" stat to the bottom of Payment History -* Added CSS styles specific to Twenty Twelve -* Improved number formatting for prices -* Added new edd_download_post_type_args filter - -= 1.1.8 = - -* Fixed a major bug with discount codes not being accepted -* Fixed a bug in the PDF reports -* Added a new "Earnings per day" graph on the Reports page -* Improved URL > path conversion for locally stored download files -* Improved organization of email templating functions - -= 1.1.7 = - -* Fixed major bug with processing file downloads -* Removed price check from PayPal purchase verification temporarily -* Added filters to all discount validation functions for developers -* Improved data sanitization to discount creation / edit forms -* Removed the "type" column from the discounts page -* Added option to set minimum purchase amount required before discount can be applied - -= 1.1.6 = - -* Added the name of the buyer to the admin purchase notifications -* Added a new setting for "Complete Purchase" button text -* Escaped values of settings field callbacks -* Fixed a bug with button colors when inserting a short code -* Fixed a spelling error in Downloads > Settings > Payment Gateways -* Removed ini_set error display -* Fixed a major bug with discount codes that caused the "uses" value to get erased when updated a code -* Fixed a bug with download sales/earnings stats and variable priced downloads -* Removed an old and unused add_meta_box() call -* Fixed an incorrect check for missing directory and creation of /uploads/edd -* Escaped attributes and improved code formating in template-functions.php -* Added new actions to top and bottom of payment history page -* Escaped attributes and improved code formating in download-functions.php -* Updated Dutch language files -* Added pre_ and post_ actions to the add to cart function -* Added a new edd_download_price filter to the edd_price() function -* Fixed a misnamed class on the empty cart element -* Added class names to all table rows and cells in the checkout template -* Updated French translation files -* Removed call by reference in edd_scan_folders() - -= 1.1.5.2 = - -* Fixed a call-by-reference error -* Fixed a problem with zero-byte downloads - -= 1.1.5.1 = - -* Fixed a bug with file downloads - -= 1.1.5 = - -* Updated default language files -* Changed "Purchase Page" label to "Checkout Page" in settings -* Fixed a problem with serving download files -* Fixed a bug that caused images to break when uploaded to download products -* Made significant security improvements for protecting files against unauthorized downloads -* Updated discounts so taht users can only use a discount code once -* Download titles are now decoded for html entities in payment history -* Updated payment history to fix an error notice when a user isn't found -* Added a new option for showing download links on the success page after completing a payment -* Fixed a couple of undefined index errors -* Added item prices to the cart widget -* Added support for the Iranian Rial currency. Make sure your gateway supports it before using it -* Updated the edd_remove_item_url() to use the current page URL instead of the home URL -* Added new edd_get_current_page_url() function -* Made the edd_payment post type not public -* Updated French language files - -= 1.1.4.1 = - -* Fixed a bug with the source file download processing -* Added support for .mobi files -* Removed deprecated get_magic_quotes_runtime() -* Fixed an error notice warning -* Added "order" and "orderby" parameters to the [downloads] short code -* Fixed some errors with aposthrophe encoding -* Removed a conditional check for the jQuery library as it was causing problems with jQuery not loading -* Add a "No Download Found" message to the PDF reports for when there are no products -* Fixed a rendering issue with purchase receipts in Outlook - -= 1.1.4.0 = - -* Fixed a bug with the purchase receipt templates -* Updated default language files with a lot of new strings -* Added new edd_cart_contents filter -* Replaced Thickbox with Colorbox for email template previews -* Fixed a bug with + signs in email addresses -* Fixed a bug with prices not saving when set to 0 -* Added a new PDF Report generation feature for Sales and Earnings, thanks to SunnyRatilal -* Added a new [edd_price] short code -* Fixed a miss spelled FOR attribute on a checkout label -* Added Quick Edit ability to the Download Price option -* Fixed a bug with the discount code field on the checkout page -* Fixed an encoded bug with the purchase receipts -* Updated the charset from ISO-8859-1 to utf-8 for the purchase receipts -* Fixed a bug with the way the currency sign was displayed in the meta box price field -* Fixed a bug where flat rate discounts could result in negative checkout values -* Update the date in purchase receipts to reflect the date_format setting in WordPress -* Added a check to existing jQuery libraries before enqueing -* Added is_array() check to the price options name function to fix a potential error -* Improved the formatting of country names -* Added an php_ini check for safe mode -* Fixed a missing currency sign in the email {price} template tag - -= 1.1.3.2 = - -* Fixed a minor bug with the PayPal IPN listener -* Fixed a minor bug with the function that checks for a valid discount -* Added two new action hooks to the reports page - -= 1.1.3.1 = - -* Fixed a bug that caused complete CC fields to show when only one gateway was enabled - -= 1.1.3 = - -* Fixed a bug with free downloads that happened when no payment gateway was selected -* Separated First and Last name fields in the payment's CSV export -* Fixed a bug that prevented large files from being able to be downloaded -* Improved the countries drop down field in the default CC form for payment gateways -* Fixed an error that showed up when purchasing a download without any downloadable files -* Added a new filter to the PayPal redirect arguments array -* Fixed a bug with the PayPal Standard gateway that was present when allow_url_fopen wasn't enabled -* Removed the edd_payment post type from the WP Nav Menus -* Added a check to the download processing function to ensure the purchase has been marked as complete -* Fixed a padding bug on the checkout form - -= 1.1.2 = - -* Fixed a bug with the ajax function that adds items to the cart - it did not show the price option name until page was refreshed -* Fixed a bug in the purchase receipt that caused it to include all source file links, not just the ones set to the price option purchase -* Added a new "class" parameter to the [purchas_link} short code -* Moved the discount code fieldset inside of the user info fieldset on the checkout form -* Added a legend to the user info fieldset -* Improved the markup of the default CC fields -* Added new edd_is_checkout() conditional function -* Updated Spanish language files -* Added new payment export system, thanks to MadeByMike - -= 1.1.1 = - -* Added a couple of new filters to the file download processing function -* Fixed a couple of undefined index errors -* Fixed a bug with the "All" filter in the Payment History page -* Fixed an amount comparision error in the PayPal IPN processer -* Added Japanese language files - -= 1.1.0 = - -* Added new French translation files, thanks for Boddhi -* Updated default language files -* Fixed the width of the "Email" column in the payment history page -* Added payment "status" filters to the payment history page -* Added an option to filter the payment history page by user/buyer -* Added a "Price" column to the Downloads page -* Fixed a bug with duplicate "Settings Updated" notices -* Added a missing text domain to the Settings Updated notice -* Fixed a bug with the add-ons cache that caused them to never refresh -* Added new {receipt_id} template tag for purchase receipts -* Improved CSS for the checkout page -* Improved CSS for the payment method icons -* Added a new "upload" callback for settings field types -* Added a new hook, edd_process_verified_download, to the download processing function -* Minor improvements to the email templating system -* Minor improvements to the View Order Details pop up -* Updated edd_sert_payment() to apply the date of the payment to the post_date field - -= 1.0.9 = - -* Updated the purchase/download history short codes to only show files for the price options the user has purchased -* Fixed a bug with the file upload meta box fields -* Added the ability to register custom payment method icons -* Added unique IDs to P tags on the checkout form -* Added an option to disable the PayPal IPN verification -* Added a new feature that allows source files to be restricted to specific price options -* Updated the "View Purchase Details" modal to include the price option purchased, if any -* Added labels above the file name and file URL fields to help users using browsers without placeholder support -* Made improvements to the checkout registration form layout -* Added an option in Settings > Misc to define the expiration length for download links - default is 24 hours -* Updated the [purchase_link] short code in the Download Configuration meta box to reflect the chosen button color -* Updated the "Short Code" column in the list table to include the correct button color option -* Added a new filter, edd_download_file_url_args, for changing the arguments passed to the function that generages download URLs -* Fixed a bug with the EDD_READ_FILE_MODE constant -* Added a new filter to allow developers to change the redirect URL for the edd_login form -* Improved some file / function organization - -= 1.0.8.5 = - -* Added {payment_method} to the list of email template tags for showing the method of payment used for a purchase -* Removed the menu_position attribute from the "download" post type to help prevent menu conflicts -* Fixed a bug with the page options in settings -* Updated the edd_read_file() function to convert local URLs to absolute file paths -* Fixed a bug with the [downloads] short code -* Enhanced the function for checking if a user has purchased a download to add support for checking for specific price options -* Fixed a bug with the function that checks if a user has purchased a specific download -* Fixed a potential bug with the "settings updated" notice that could have caused duplicate messages to be shown - -= 1.0.8.4 = - -* Fixed a bug with download sale/earning stats going negative when reversing purchases -* Removed some blank form action attributes that caused the HTML to invalidate -* Added "Settings Updated" notification when saving plugin settings -* Made some improvements to the default purchase receipt email template -* Renamed the "Manual Payment" gateway to "Test" -* Added options for linking the download titles in the [downloads] short code -* Removed the "You have already purchased this" message from the purchase link short code / template -* Added a "price" parameter to the [downloads] short code -* Improved the CSS on the variable price option forms -* Add a parameter to the [downloads] short code for showing the complete content -* Fixed a bug with free downloads -* Moved the function that triggers the purchase receipt to its own function/hook so that it can be modified more easily -* Added a few new action hooks -* Updated Spanish language files - -= 1.0.8.3 = - -* Added a default purchase receipt email that is used if no custom email has been defined -* Fixed a bug with the discount codes and their usage counts not getting recorded correctly -* Fixed a bug with the install script -* Fixed a problem with apostrophe encoding in the purchase summary sent to PayPal -* Added pagination to the download/sale log on download Edit screens -* Added new "edd_default_downloads_name" filter for changing the default singular and plural "download" labels used globally throughout the plugin -* Adding new span.edd-cart-item-separator to the cart widget and short code -* Added more support for the [downloads] short code, used to display a list or grid of digital products -* Moved load_plugin_textdomain to an "init" hook in order to work better with translation plugins -* Fixed a couple of undefined index errors -* Added option to send purchase receipt when manually marked a payment as complete -* Added new "edd_success_page_redirect" filter to the function that redirects a buyer to the success page -* Changed the default charset in the PayPal standard gateway to that of the website -* Added "Payment Method" to the "View Order Details" popup -* Made ajax enabled by default -* Reorganized the edd_complete_purchase() function to be more extensible -* Added new constant EDD_READ_FILE_MODE for defining how download files are delivered -* Added auto creation for .htaccess files in the uploads directory for EDD to help protect unauthorized file downloads -* Added Turkish language files -* Added detection for php.ini variables important to PayPal payment verification -* Added a new short code for showing a list of active discounts: [download_discounts] - -= 1.0.8.2 = - -* Added a number_format() check to the PayPal standard gateway -* Added the Turkish Lira to supported currencies -* Dramatically improved the default PayPal gateway, which should help prevent payments not getting verified -* Added edd_get_ip() and updated the user IP detection. It previously failed if the server was running SSL -* Added missing class name to the download history table -* Fixed a misnamed class in the purchase history table -* Updated purchase and download history to now show download links if redownload is disabled -* Added a new conditional called edd_no_redownload() that theme devs can use to check if redownloading of files is permitted -* Fixed problem with improper encoding of apostrphes in purchase receipt emails -* Added new edd_hook_callback() function for settings field type callbacks -* Updated default language files with new strings for translation - -= 1.0.8.1 = - -* Updated es_ES translation files -* A lots of code documentation improvements -* Completely rewrote the purchase processing functions to fix a couple of bugs and make the entire thing easier to debug and improve -* Fixed a problem with user emails not being recorded for guest purchases -* Improved the performance of the add-ons page with transients -* Reorganized some functions into more appropriate files -* Fixed translation domains on the login forms -* Added a new option for marking a payment as "refunded". The refund process must be done through the payment gateway still. When payments are marked as "refunded", the sales and earnings stats will be adjusted accordingly. -* Added an alert message to the "Delete Payment" link -* Updated French language files -* Added get_post_class() to the payments history page so that payment rows can be styled based on their status, post type, etc. -* Updated admin CSS to add custom background color to refunded payments -* Added new filter called "edd_payment_statuses", which can be used to register custom statuses - -= 1.0.8 = - -* Added the [purchase_history] shortcode for showing a detailed list of user's purchases -* Improved the names of the widgets -* Fixed a CSS bug with the Add Ons page -* Added the edd_get_checkout_uri() function for use by themes -* Fixed a couple of bugs with the login/register checkout forms -* Dramatically improved code documentation -* Fixed an incorrectly named parameter in the edd_after_download_content hook - -= 1.0.7.2 = - -* Added a new EDD Categories / Tags widget -* Removed duplicated code from payments history page -* Fixed a major bug that made it impossible to safely update orders -* Added user's IP address to payment meta -* Added localization to the default page titles created during install -* Removed old stripe.js code that is no longer used -* Added an enhancement to the cart widget that causes the "Purchase" button to reset when removing an item from the cart - -= 1.0.7.1 = - -* Added a second instance do_action('edd_purchase_form_user_info') to the checkout registration form -* Updated the edd_purchase_link() function to automatically detect chosen link styles and colors -* Fixed a bug with the discount code form on checkout. It now only shows if there is at least one active discount. Props to Sksmatt -* Fixed a bug with the Media Uploader when adding media to the content of a Download. Props to Sksmatt -* Added a wrapper div.edd-cart-ajax-alert around the message that shows when an item has been added to the cart -* Fixed a small error notice present in the checkout form short code. Props to Sksmatt -* Fixed a small bug wit the edd_remove_item_url() present when on a 404 error page. Props to Sksmatt - -= 1.0.7 = - -* Added new edd_has_variable_prices() function -* Improved the edd_price() function to take into account products with variable prices -* Added an $id parameter to the edd_cart_item filter -* Updated French language files -* Added missing "required" classes to the checkout login form -* Added the ability to update the email address associated with payments -* Added a new [edd_login] short code for showing a basic login form -* Added new Dutch language translation files - - -= 1.0.6 = - -* NOTE: if you have modified the checkout_cart.php template via your theme, please consider updating it with the new version as many things have changed. -* Fixed a bug with the empty cart message not being displayed on the checkout page -* When purchasing a product with variable prices, the selected price option name is now shown on the checkout page -* Fixed a bug with the in-checkout registration /login form -* Improved the layout of the in-checkout register / login forms -* Fixed a bug in the "Edit Payment" page caused by the variable price system -* Fixed a bug with plugin pages being duplicate on reactivation of EDD -* Variable price descriptions can now contain HTMl -* Added new a new filter that allows for the jQuery validation rules to be modified for the checkout page -* Payments in the Payment History page can now be sorted by ID, Status, and Date. -* Fix a bug that allowed for the same download to be added to the cart twice. -* Added missing element classes to the cart widget, checkout cart, and more -* Added the edd_price() function for use in themes -* Updated the edd_payment_meta filter with a second parameter for $payment_data -* Updated the "Insert Download" icon in the "Insert Media" section to match the main post type icon -* Added filters that allow for post type and taxonomy labels to be modified via the theme -* Added filters that allow for the post type "supports" attributes to be modified -* Added extra mimetypes to the function that processes file downloads -* Dramatically improved the CSS of the checkout page. - -= 1.0.5 = - -* New variable pricing option for downloads -* Added new {price} template tag for emails -* Fixed an improperly named filter for "edd_payment_meta" -* Updated some advanced query URLs to be more efficient -* Updated the German language files -* Updated default.po/mo -* Added a check for whether the current theme supports post thumbnails -* Fixed a few undefined index errors -* Updated Spanish language files -* Added support for free downloads -* Fixed some bugs with the email formatting -* Fixed a small bug with the ajax add to cart system -* Improved the download metabox layout -* Updated the French language files -* Added a new icon to the Downloads post type - - -= 1.0.4.1 = - -* New download post type icon -* Fixed missing add-ons.php file - -= 1.0.4 = - -* Added a new "Add Ons" page for viewing all available add-ons for the plugin -* Added two new filters for currencies that allow developers to add their own currencies -* Improved meta box field loading that allows add-ons to add / remove fields -* Added language files for Spanish -* Improvements to the "empty cart" message. It can now be customized via a filter - -= 1.0.3 = - -* Added first and last name fields to the checkout registration form. -* Improved country list formatting. -* Improved the price input field to make it more clear and help prevent improper price formats. -* Added backwards compatibility for WP versions < 3.3. The rich editors in the settings pages could not be rendered in < 3.3. -* Added option to include an "Agree to terms" to the checkout. -* Added an option for the checkout cart template to be customized via the theme. -* Fixed a potential bug with file downloads. -* Added .epub files to accepted mime types. -* Fixed a bug with a missing email field when using add-on gateways. - -= 1.0.2 = - -* Added an option to delete payments -* Added featured thumbnails to checkout cart -* Moved payment action links to beneath the payment email to better match WordPress core -* Improved checkout CSS to help prevent conflicts -* "Already purchased" message now shows option to checkout when purchasing again. -* Forced file downloads and hidden file URLs -* Fixed a bug with duplicate purchase receipts -* Updated language files -* Fixed a bug with the discount code system += Can I set up an affiliate program? = -= 1.0.1.4 = - -* Fixed a bug with the "Add New" button for download source files. -* Added the Italian language files, thanks to Marco. - -= 1.0.1.3 = +Yes! EDD integrates directly with several affiliate platforms that provide complete affiliate systems you can use to award commissions to your affiliate marketers. This means when affiliates refer customers to you, and those customers buy your products, those affiliates earn a commission from you. -* Fixed a bug with the checkout login / register forms +[Check out AffiliateWP](https://affiliatewp.com?utm_source=edd_readme&utm_medium=faq_tab&utm_content=home&utm_campaign=readme) -= 1.0.1.2 = += Can I give my customers downloadable PDF Invoices? = -* Fixed a bug with the manual payment gateway. -* Fixed a bug where sales / earnings counts were increased before a purchase was confirmed. -* Fixed a bug with the checkout registration / login forms. -* Added a German translation, thanks to David Decker. -* Added a partial European Portuguese translation, thanks to Takssista. +Yes, with a Personal Pass, you can provide beautiful and downloadable invoices to your customers with the [Invoices](https://easydigitaldownloads.com/downloads/edd-invoices/?utm_source=edd-invoices&utm_medium=faq_tab&utm_content=pdf-invoices&utm_campaign=readme) extension. -= 1.0.1.1 = += Are recurring payments (subscriptions) supported? = -* Minor updates including inclusion of INR as an available currency. -* Updates to the default.po file for missing strings. +Yes, with an Extended Pass you get access to [Recurring Payments](https://easydigitaldownloads.com/downloads/recurring-payments/?utm_source=docs&utm_medium=faq_tab&utm_term=recurring_addon&utm_content=faqs&utm_campaign=readme). [Full documentation here](https://easydigitaldownloads.com/docs/recurring-payments-setup-overview/?utm_source=docs&utm_medium=faq_tab&utm_term=recurring_addon&utm_content=faqs&utm_campaign=readme). The Recurring Payments extension allows you to create subscriptions so that customers continue paying you over time. This is great for selling memberships, courses, all access passes, software licenses, and other products which require an ongoing payment. -= 1.0 = - -* First offical release! +== Screenshots == +1. Detailed and actional reports +2. Customer Management +3. Order History +4. Order Details +5. Product Grid - Vendd +6. Checkout - Vendd +7. Product Grid - Default Theme +8. Checkout - Default Theme +== Changelog == += 3.1.5 = +* Fix: India Regions were not being properly loaded. +* Fix: Fixed compatibility with PHP 7.1 when adding a new discount. +* Dev: Tax settings tab was overriding the base class method for getting the ID. + += 3.1.4 = +* Improvement: The checkout cart markup is now aware if the cart is using the shortcode or the block. +* Improvement: A filter has been added to the structured data for products to allow extensions to add additional offers. +* Improvement: The email marker (tagging) experience has been updated to be more accessible and easier to use. +* Improvement: EDD's admin JavaScript has been updated to prevent deprecation notices. +* Improvement: It is now possible to update the sequential start number as long as it does not conflict with existing orders. +* Improvement: The current file name is now stored with the file download log. +* Improvement: The emails on the order details screen has been updated to always include the customer's primary address, even if it wasn't used on an order. +* Fix: When the "last month" had fewer days than "month to date", reporting could incorrectly cross months. +* Fix: The `edd_link_helper` function could sometimes have an empty UTM medium attribute. +* Fix: Checkbox inputs for privacy/terms are now marked required with the HTML5 attribute. +* Fix: The Recapture onbording process could inconsistently fail to install or connect. +* Fix - Stripe: Recurring products with free trials were immediately creating an invoice with Payment Elements. +* Fix - Stripe: When using Payment Elements, clicking the purchase button did not show the loading animation. +* Fix - Stripe: Stripe data will be included in the Site Health debug information with EDD 3.1.4. +* Fix - Stripe: The list of countries which do not allow application fees has been updated. +* i18n: All countries and states are now translation ready. +* i18n: We have started the process of updating strings to add comments for translators where recommended. +* Dev: Hooks have been added to the license update/delete methods. +* Dev: EDD's pointer registration has been updated so that extensions can hook into it. +* Dev: EDD now has a status badge utility class to allow order status badges to be more universally used. +* Dev: The `edd_set_upgrade_complete` function is now in a file that's loaded outside of the admin, so it can be used in cron upgrades. +* Dev: The EDD debugging information under Site Health can now be filtered so that extensions can add key data. +* Dev: Extensions which use the EDD extension registry will attempt to validate the pro pass license before the individual extension license. +* Dev: The EDD debug log description has been updated to link to a support doc explaining the `edd_debug_log` function. +* Dev: The `edd_get_utc_date_string` helper function has been created to simplify getting a formatted UTC date. +* Dev: The EDD settings have been refactored for readability. + +View the full changelog at [https://easydigitaldownloads.com/changelogs/plugin/easy-digital-downloads/](https://easydigitaldownloads.com/changelogs/plugin/easy-digital-downloads/?utm_medium=readme&utm_source=wporg&utm_campaign=edd-plugin&utm_term=description) == Upgrade Notice == - -= 1.2.2 = - -* Fixed problem with sorting downloads by price and added compatibility for Post Types Order plugin -* Improved file organization -* Added new Arabic language files -* Added a Sales Summary dashboard widget -* Updated download configuration with brand new UI and simplified options -* Removed button text / style from Download Configuration -* Added new default button color/style options to Settings > Styles -* Added new default add-to-cart button text option in Settings > Misc -* Added a new query filter to block direct access to attachments of downloads -* Fixed bug with undefined index error with the metabox nonce -* Added a check to prevent users from deleting all price/download files in download configuration -* Added WPML config file for more WPML compatibility -* Dramatically improved admin javascript functions to be more extendable -* Updated French language files -* Added cache flush for user purchase history when payments are created -* Fixed a bug with percentage based discount codes -* Updated edd_email_template_tags with $payment_id - -= 1.2.1.1 = - -* Fixed a bug with the earnings per day graph -* Added a new filter for edd_receipt_attachments -* Improved extendability of payment form -* Fixed a minor CSS conflict with purchase buttons -* Fixed a security exploit that made it possible for malicious users to purchase variable priced items for free - -= 1.2.1 = - -* Added new filters to the taxonomy args for download categories and tags -* Fixed incorrect help text for the start/end date of discount codes -* Fixed a couple of typos -* Added a new "Product Notes" meta box to the Edit Download page -* Updated German translation -* Added some missing text domains for strings -* Fixed a problem with manually completing purchases -* Fixed some problems with international number formatting - -= 1.2 = - -* Added reset styles to default fieldsets to account for themes without fieldset styling -* Added new Date and Time to the View Details purchase popup -* Fixed a bug with radio button toggling in Download Configuration -* Added new CSS for improved styling of checkout error messages -* Added new filter to edd_get_checkout_uri -* Updated [purchase_collection] to support custom link text -* Improved the edd_delete_purchase() function -* Added a missing closing anchor tag -* Added an error notice on download pages if no checkout page is set -* Added labels to the checkboxes in the Download Configuration meta box to make fields easier to click on -* Added new filter to the EDD languages directory -* Fixed a bug in the email template preview -* Updated Italian language files -* Updated German language files -* Replaced hard-coded instance of "Downloads" with plural label function -* Updated French translation -* Added huge new templating system to [download_history] and [purchase_history] short codes -* Added huge new templating system for [downloads] short code -* Improved the edd_append_purchase_link() function -* Added new edd_payments_page_date_format filter to allow date format in Payment History to be changed -* Removed "Deleted" from the payment hsitory filter options -* Made improvements to script loading -* Fixed a problem with the auto-generated short code on the All Downloads page -* Added a new edd_get_payments_args filter -* Added the ability to sort payments by their total price -* Minor improvements to the add-ons page -* Added a new meta key for payment total so that payments can be sorted by amount -* Fixed a problem with new downloads not being able to add download files -* Improvements to add/remove to/from cart functions with ajax -* Updated default language files -* Fixed an incorrect variable name in register-settings.php -* Added a new option to export all customer emails from Payment History -* Fixed a problem with checkout ajax when permalinks are not enabled -* Created new global checkout fields and validation methods for use by payment gateways -* Added new "Purchase History" widget -* Added new classes/div to the purchase history widget -* Improved data sanitization in many files -* Improved code formatting in many files -* Fixed a few small typos in labels -* Added support for absolute file paths for download files -* Added a lot of new helper functions for developers -* Changed the Earnings Per Day graph to show the latest 30 days, instead of the current month -* Improved the way files are downloaded, especially for large files -* Fixed a discrepancy with the sales per month graph -* Added dozens of new content types to the download processing function -* Fixed several undefined index errors -* Improved the UI of Download Configuration meta box -* Added new "Total Earnings" stat to the bottom of Payment History -* Added CSS styles specific to Twenty Twelve -* Improved number formatting for prices -* Added new edd_download_post_type_args filter - -= 1.1.8 = - -* Fixed a major bug with discount codes not being accepted -* Fixed a bug in the PDF reports -* Added a new "Earnings per day" graph on the Reports page -* Improved URL > path conversion for locally stored download files -* Improved organization of email templating functions - -= 1.1.7 = - -* Fixed major bug with processing file downloads -* Removed price check from PayPal purchase verification temporarily -* Added filters to all discount validation functions for developers -* Improved data sanitization to discount creation / edit forms -* Removed the "type" column from the discounts page -* Added option to set minimum purchase amount required before discount can be applied - -= 1.1.6 = - -* Added the name of the buyer to the admin purchase notifications -* Added a new setting for "Complete Purchase" button text -* Escaped values of settings field callbacks -* Fixed a bug with button colors when inserting a short code -* Fixed a spelling error in Downloads > Settings > Payment Gateways -* Removed ini_set error display -* Fixed a major bug with discount codes that caused the "uses" value to get erased when updated a code -* Fixed a bug with download sales/earnings stats and variable priced downloads -* Removed an old and unused add_meta_box() call -* Fixed an incorrect check for missing directory and creation of /uploads/edd -* Escaped attributes and improved code formating in template-functions.php -* Added new actions to top and bottom of payment history page -* Escaped attributes and improved code formating in download-functions.php -* Updated Dutch language files -* Added pre_ and post_ actions to the add to cart function -* Added a new edd_download_price filter to the edd_price() function -* Fixed a misnamed class on the empty cart element -* Added class names to all table rows and cells in the checkout template -* Updated French translation files -* Removed call by reference in edd_scan_folders() - -= 1.1.5.2 = - -* Fixed a call-by-reference error -* Fixed a problem with zero-byte downloads - -= 1.1.5.1 = - -* Fixed a bug with file downloads - -= 1.1.5 = - -* Updated default language files -* Changed "Purchase Page" label to "Checkout Page" in settings -* Fixed a problem with serving download files -* Fixed a bug that caused images to break when uploaded to download products -* Made significant security improvements for protecting files against unauthorized downloads -* Updated discounts so taht users can only use a discount code once -* Download titles are now decoded for html entities in payment history -* Updated payment history to fix an error notice when a user isn't found -* Added a new option for showing download links on the success page after completing a payment -* Fixed a couple of undefined index errors -* Added item prices to the cart widget -* Added support for the Iranian Rial currency. Make sure your gateway supports it before using it -* Updated the edd_remove_item_url() to use the current page URL instead of the home URL -* Added new edd_get_current_page_url() function -* Made the edd_payment post type not public -* Updated French language files - -= 1.1.4.1 = - -* Fixed a bug with the source file download processing -* Added support for .mobi files -* Removed deprecated get_magic_quotes_runtime() -* Fixed an error notice warning -* Added "order" and "orderby" parameters to the [downloads] short code -* Fixed some errors with aposthrophe encoding -* Removed a conditional check for the jQuery library as it was causing problems with jQuery not loading -* Add a "No Download Found" message to the PDF reports for when there are no products -* Fixed a rendering issue with purchase receipts in Outlook - -= 1.1.4.0 = - -* Fixed a bug with the purchase receipt templates -* Updated default language files with a lot of new strings -* Added new edd_cart_contents filter -* Replaced Thickbox with Colorbox for email template previews -* Fixed a bug with + signs in email addresses -* Fixed a bug with prices not saving when set to 0 -* Added a new PDF Report generation feature for Sales and Earnings, thanks to SunnyRatilal -* Added a new [edd_price] short code -* Fixed a miss spelled FOR attribute on a checkout label -* Added Quick Edit ability to the Download Price option -* Fixed a bug with the discount code field on the checkout page -* Fixed an encoded bug with the purchase receipts -* Updated the charset from ISO-8859-1 to utf-8 for the purchase receipts -* Fixed a bug with the way the currency sign was displayed in the meta box price field -* Fixed a bug where flat rate discounts could result in negative checkout values -* Update the date in purchase receipts to reflect the date_format setting in WordPress -* Added a check to existing jQuery libraries before enqueing -* Added is_array() check to the price options name function to fix a potential error -* Improved the formatting of country names -* Added an php_ini check for safe mode -* Fixed a missing currency sign in the email {price} template tag - -= 1.1.3.2 = - -* Fixed a minor bug with the PayPal IPN listener -* Fixed a minor bug with the function that checks for a valid discount -* Added two new action hooks to the reports page - -= 1.1.3.1 = - -* Fixed a bug that caused complete CC fields to show when only one gateway was enabled - -= 1.1.3 = - -* Fixed a bug with free downloads that happened when no payment gateway was selected -* Separated First and Last name fields in the payment's CSV export -* Fixed a bug that prevented large files from being able to be downloaded -* Improved the countries drop down field in the default CC form for payment gateways -* Fixed an error that showed up when purchasing a download without any downloadable files -* Added a new filter to the PayPal redirect arguments array -* Fixed a bug with the PayPal Standard gateway that was present when allow_url_fopen wasn't enabled -* Removed the edd_payment post type from the WP Nav Menus -* Added a check to the download processing function to ensure the purchase has been marked as complete -* Fixed a padding bug on the checkout form - -= 1.1.2 = - -* Fixed a bug with the ajax function that adds items to the cart - it did not show the price option name until page was refreshed -* Fixed a bug in the purchase receipt that caused it to include all source file links, not just the ones set to the price option purchase -* Added a new "class" parameter to the [purchas_link} short code -* Moved the discount code fieldset inside of the user info fieldset on the checkout form -* Added a legend to the user info fieldset -* Improved the markup of the default CC fields -* Added new edd_is_checkout() conditional function -* Updated Spanish language files -* Added new payment export system, thanks to MadeByMike - -= 1.1.1 = - -* Added a couple of new filters to the file download processing function -* Fixed a couple of undefined index errors -* Fixed a bug with the "All" filter in the Payment History page -* Fixed an amount comparision error in the PayPal IPN processer -* Added Japanese language files - - -= 1.1.0 = - -* Updated French translation files, thanks for Boddhi -* Updated default language files -* Fixed the width of the "Email" column in the payment history page -* Added payment "status" filters to the payment history page -* Added an option to filter the payment history page by user/buyer -* Added a "Price" column to the Downloads page -* Fixed a bug with duplicate "Settings Updated" notices -* Added a missing text domain to the Settings Updated notice -* Fixed a bug with the add-ons cache that caused them to never refresh -* Added new {receipt_id} template tag for purchase receipts -* Improved CSS for the checkout page -* Improved CSS for the payment method icons -* Added a new "upload" callback for settings field types -* Added a new hook, edd_process_verified_download, to the download processing function -* Minor improvements to the email templating system -* Minor improvements to the View Order Details pop up -* Updated edd_sert_payment() to apply the date of the payment to the post_date field - -= 1.0.9 = - -* Updated the purchase/download history short codes to only show files for the price options the user has purchased -* Fixed a bug with the file upload meta box fields -* Added the ability to register custom payment method icons -* Added unique IDs to P tags on the checkout form -* Added an option to disable the PayPal IPN verification -* Added a new feature that allows source files to be restricted to specific price options -* Updated the "View Purchase Details" modal to include the price option purchased, if any -* Added labels above the file name and file URL fields to help users using browsers without placeholder support -* Made improvements to the checkout registration form layout -* Added an option in Settings > Misc to define the expiration length for download links - default is 24 hours -* Updated the [purchase_link] short code in the Download Configuration meta box to reflect the chosen button color -* Updated the "Short Code" column in the list table to include the correct button color option -* Added a new filter, edd_download_file_url_args, for changing the arguments passed to the function that generages download URLs -* Fixed a bug with the EDD_READ_FILE_MODE constant -* Added a new filter to allow developers to change the redirect URL for the edd_login form -* Improved some file / function organization - -= 1.0.8.5 = - -* Added {payment_method} to the list of email template tags for showing the method of payment used for a purchase -* Removed the menu_position attribute from the "download" post type to help prevent menu conflicts -* Fixed a bug with the page options in settings -* Updated the edd_read_file() function to convert local URLs to absolute file paths -* Fixed a bug with the [downloads] short code -* Enhanced the function for checking if a user has purchased a download to add support for checking for specific price options -* Fixed a bug with the function that checks if a user has purchased a specific download -* Fixed a potential bug with the "settings updated" notice that could have caused duplicate messages to be shown - -= 1.0.8.4 = - -* Fixed a bug with download sale/earning stats going negative when reversing purchases -* Removed some blank form action attributes that caused the HTML to invalidate -* Added "Settings Updated" notification when saving plugin settings -* Made some improvements to the default purchase receipt email template -* Renamed the "Manual Payment" gateway to "Test" -* Added options for linking the download titles in the [downloads] short code -* Removed the "You have already purchased this" message from the purchase link short code / template -* Added a "price" parameter to the [downloads] short code -* Improved the CSS on the variable price option forms -* Add a parameter to the [downloads] short code for showing the complete content -* Fixed a bug with free downloads -* Moved the function that triggers the purchase receipt to its own function/hook so that it can be modified more easily -* Added a few new action hooks -* Updated Spanish language files - -= 1.0.8.3 = - -* Added a default purchase receipt email that is used if no custom email has been defined -* Fixed a bug with the discount codes and their usage counts not getting recorded correctly -* Fixed a bug with the install script -* Fixed a problem with apostrophe encoding in the purchase summary sent to PayPal -* Added pagination to the download/sale log on download Edit screens -* Added new "edd_default_downloads_name" filter for changing the default singular and plural "download" labels used globally throughout the plugin -* Adding new span.edd-cart-item-separator to the cart widget and short code -* Added more support for the [downloads] short code, used to display a list or grid of digital products -* Moved load_plugin_textdomain to an "init" hook in order to work better with translation plugins -* Fixed a couple of undefined index errors -* Added option to send purchase receipt when manually marked a payment as complete -* Added new "edd_success_page_redirect" filter to the function that redirects a buyer to the success page -* Changed the default charset in the PayPal standard gateway to that of the website -* Added "Payment Method" to the "View Order Details" popup -* Made ajax enabled by default -* Reorganized the edd_complete_purchase() function to be more extensible -* Added new constant EDD_READ_FILE_MODE for defining how download files are delivered -* Added auto creation for .htaccess files in the uploads directory for EDD to help protect unauthorized file downloads -* Added Turkish language files -* Added detection for php.ini variables important to PayPal payment verification -* Added a new short code for showing a list of active discounts: [download_discounts] - -= 1.0.8.2 = - -* Added a number_format() check to the PayPal standard gateway -* Added the Turkish Lira to supported currencies -* Dramatically improved the default PayPal gateway, which should help prevent payments not getting verified -* Added edd_get_ip() and updated the user IP detection. It previously failed if the server was running SSL -* Added missing class name to the download history table -* Fixed a misnamed class in the purchase history table -* Updated purchase and download history to now show download links if redownload is disabled -* Added a new conditional called edd_no_redownload() that theme devs can use to check if redownloading of files is permitted -* Fixed problem with improper encoding of apostrphes in purchase receipt emails -* Added new edd_hook_callback() function for settings field type callbacks -* Updated default language files with new strings for translation - -= 1.0.8.1 = - -* Updated es_ES translation files -* A lots of code documentation improvements -* Completely rewrote the purchase processing functions to fix a couple of bugs and make the entire thing easier to debug and improve -* Fixed a problem with user emails not being recorded for guest purchases -* Improved the performance of the add-ons page with transients -* Reorganized some functions into more appropriate files -* Fixed translation domains on the login forms -* Added a new option for marking a payment as "refunded". The refund process must be done through the payment gateway still. When payments are marked as "refunded", the sales and earnings stats will be adjusted accordingly. -* Added an alert message to the "Delete Payment" link -* Updated French language files -* Added get_post_class() to the payments history page so that payment rows can be styled based on their status, post type, etc. -* Updated admin CSS to add custom background color to refunded payments -* Added new filter called "edd_payment_statuses", which can be used to register custom statuses - -= 1.0.8 = - -* Added the [purchase_history] shortcode for showing a detailed list of user's purchases -* Improved the names of the widgets -* Fixed a CSS bug with the Add Ons page -* Added the edd_get_checkout_uri() function for use by themes -* Fixed a couple of bugs with the login/register checkout forms -* Dramatically improved code documentation -* Fixed an incorrectly named parameter in the edd_after_download_content hook - -= 1.0.7.2 = - -* Added a new EDD Categories / Tags widget -* Removed duplicated code from payments history page -* Fixed a major bug that made it impossible to safely update orders -* Added user's IP address to payment meta -* Added localization to the default page titles created during install -* Removed old stripe.js code that is no longer used -* Added an enhancement to the cart widget that causes the "Purchase" button to reset when removing an item from the cart - -= 1.0.7.1 = - -* Added a second instance do_action('edd_purchase_form_user_info') to the checkout registration form -* Updated the edd_purchase_link() function to automatically detect chosen link styles and colors -* Fixed a bug with the discount code form on checkout. It now only shows if there is at least one active discount. Props to Sksmatt -* Fixed a bug with the Media Uploader when adding media to the content of a Download. Props to Sksmatt -* Added a wrapper div.edd-cart-ajax-alert around the message that shows when an item has been added to the cart -* Fixed a small error notice present in the checkout form short code. Props to Sksmatt -* Fixed a small bug wit the edd_remove_item_url() present when on a 404 error page. Props to Sksmatt - -= 1.0.7 = - -* Added new edd_has_variable_prices() function -* Improved the edd_price() function to take into account products with variable prices -* Added an $id parameter to the edd_cart_item filter -* Updated French language files -* Added missing "required" classes to the checkout login form -* Added the ability to update the email address associated with payments -* Added a new [edd_login] short code for showing a basic login form -* Added new Dutch language translation files - -= 1.0.6 = - -* NOTE: if you have modified the checkout_cart.php template via your theme, please consider updating it with the new version as many things have changed. -* Fixed a bug with the empty cart message not being displayed on the checkout page -* When purchasing a product with variable prices, the selected price option name is now shown on the checkout page -* Fixed a bug with the in-checkout registration /login form -* Improved the layout of the in-checkout register / login forms -* Fixed a bug in the "Edit Payment" page caused by the variable price system -* Fixed a bug with plugin pages being duplicate on reactivation of EDD -* Variable price descriptions can now contain HTMl -* Added new a new filter that allows for the jQuery validation rules to be modified for the checkout page -* Payments in the Payment History page can now be sorted by ID, Status, and Date. -* Fix a bug that allowed for the same download to be added to the cart twice. -* Added missing element classes to the cart widget, checkout cart, and more -* Added the edd_price() function for use in themes -* Updated the edd_payment_meta filter with a second parameter for $payment_data -* Updated the "Insert Download" icon in the "Insert Media" section to match the main post type icon -* Added filters that allow for post type and taxonomy labels to be modified via the theme -* Added filters that allow for the post type "supports" attributes to be modified -* Added extra mimetypes to the function that processes file downloads -* Dramatically improved the CSS of the checkout page. - -= 1.0.5 = - -* New variable pricing option for downloads -* Added new {price} template tag for emails -* Fixed an improperly named filter for "edd_payment_meta" -* Updated some advanced query URLs to be more efficient -* Updated the German language files -* Updated default.po/mo -* Added a check for whether the current theme supports post thumbnails -* Fixed a few undefined index errors -* Updated Spanish language files -* Added support for free downloads -* Fixed some bugs with the email formatting -* Fixed a small bug with the ajax add to cart system -* Improved the download metabox layout -* Updated the French language files -* Added a new icon to the Downloads post type - -= 1.0.4.1 = - -* New download post type icon -* Fixed missing add-ons.php file - -= 1.0.4 = - -* Added a new "Add Ons" page for viewing all available add-ons for the plugin -* Added two new filters for currencies that allow developers to add their own currencies -* Improved meta box field loading that allows add-ons to add / remove fields -* Added language files for Spanish -* Improvements to the "empty cart" message. It can now be customized via a filter - -= 1.0.3 = - -* Added first and last name fields to the checkout registration form. -* Improved country list formatting. -* Improved the price input field to make it more clear and help prevent improper price formats. -* Added backwards compatibility for WP versions < 3.3. The rich editors in the settings pages could not be rendered in < 3.3. -* Added option to include an "Agree to terms" to the checkout. -* Added an option for the checkout cart template to be customized via the theme. -* Fixed a potential bug with file downloads. -* Added .epub files to accepted mime types. -* Fixed a bug with a missing email field when using add-on gateways. - -= 1.0.2 = - -* Added an option to delete payments -* Added featured thumbnails to checkout cart -* Moved payment action links to beneath the payment email to better match WordPress core -* Improved checkout CSS to help prevent conflicts -* "Already purchased" message now shows option to checkout when purchasing again. -* Forced file downloads and hidden file URLs -* Fixed a bug with duplicate purchase receipts -* Updated language files -* Fixed a bug with the discount code system - -= 1.0.1.4 = - -* Fixed a bug with the "Add New" button for download source files. -* Added the Italian language files, thanks to Marco. - -= 1.0.1.3 = - -* Fixed a bug with the checkout login / register forms - -= 1.0.1.2 = - -Fixed a bug with the manual payment gateway. -Fixed a bug where sales / earnings counts were increased before a purchase was confirmed. -Fixed a bug with the checkout registration / login forms. -Added a German translation, thanks to David Decker. -Added a partial European Portuguese translation, thanks to Takssista. - -= 1.0.1.1 = - -* Minor updates including inclusion of INR as an available currency. -* Updates to the default.po file for missing strings. - -= 1.0 = - -* First offical release! \ No newline at end of file +IMPORTANT: Upgrading from Easy Digital Downloads 2.x to 3.0+ is a major release that includes many improvements and changes. You will be asked to perform database maintenance once installed. Please ensure you make a backup of your site prior to upgrading. Your site should remain functional during this maintenance, but as with all updates, it is best to make a backup of your site prior to updating. diff --git a/src/Admin/Downloads/Meta.php b/src/Admin/Downloads/Meta.php new file mode 100644 index 00000000000..e35a191f0c2 --- /dev/null +++ b/src/Admin/Downloads/Meta.php @@ -0,0 +1,82 @@ + 'variable_prices_value', + 'edd_metabox_save_edd_download_files' => 'download_files_value', + 'edd_save_download' => array( 'bundled_conditions', 10, 2 ), + ); + } + + /** + * Checks the variable prices array to weed out empty prices. + * + * @since 3.1.0.5 + * @param array $prices + * @return array + */ + public function variable_prices_value( $prices ) { + if ( empty( $prices ) ) { + return false; + } + foreach ( $prices as $id => $price ) { + if ( empty( $price['amount'] ) && empty( $price['name'] ) ) { + unset( $prices[ $id ] ); + continue; + } + } + + return $prices; + } + + + /** + * Checks the download files array to weed out empty file options. + * + * @since 3.1.0.5 + * @param array $files + * @return array + */ + public function download_files_value( $files ) { + if ( empty( $files ) ) { + return false; + } + foreach ( $files as $id => $file ) { + if ( empty( $file['name'] ) && empty( $file['file'] ) ) { + unset( $files[ $id ] ); + continue; + } + } + + return $files; + } + + /** + * Once the download is saved, if it's not a bundle, delete the product conditions meta. + * + * @since 3.1.0.5 + * @param int $post_id + * @param WP_Post $post + * @return void + */ + public function bundled_conditions( $post_id, $post ) { + if ( ! get_post_meta( $post_id, '_edd_product_type', true ) ) { + delete_post_meta( $post_id, '_edd_bundled_products_conditions' ); + } + } +} diff --git a/src/Admin/Extensions/Card.php b/src/Admin/Extensions/Card.php new file mode 100644 index 00000000000..3952a0d716e --- /dev/null +++ b/src/Admin/Extensions/Card.php @@ -0,0 +1,373 @@ +product = $product; + $this->inactive_parameters = $args['inactive_parameters']; + $this->active_parameters = $args['active_parameters']; + $this->required_pass_id = $args['required_pass_id']; + $this->is_plugin_active = $args['is_plugin_active']; + $this->is_plugin_installed = $args['is_plugin_installed']; + $this->version = $args['version']; + + if ( ! empty( $this->product->style ) && 'installer' === $this->product->style ) { + $this->do_card_extension_installer(); + } else { + $this->do_card_product_education(); + } + } + + /** + * Outputs the card with the product education style markup. + * + * @since 3.1.1 + * @return void + */ + private function do_card_product_education() { + ?> +
    + do_title(); ?> +
    + do_image(); + $this->do_description(); + $this->do_features(); + $this->do_actions(); + ?> +
    +
    + get_filter_terms(); + ?> +
    + data-filter="get_filter_terms() ); ?>" + + > +
    + do_icon(); + echo '
    '; + $this->do_title( true ); + $this->do_description(); + echo '
    '; + $this->do_settings_link( $this->product ); + ?> +
    +
    + do_version(); + $this->do_installer_action(); + ?> +
    +
    + product->heading ) ? $this->product->heading : $this->product->title; + $url = false; + if ( $link && ! empty( $this->product->slug ) ) { + $url = edd_link_helper( + 'https://easydigitaldownloads.com/downloads/' . esc_attr( $this->product->slug ), + array( + 'utm_content' => esc_attr( $this->product->slug ), + 'utm_medium' => 'extensions-page', + ), + false + ); + } + ?> +

    + %s', + esc_url( $url ), + esc_html( $title ) + ); + } else { + echo esc_html( $title ); + } + ?> +

    + product->image ) ) { + return; + } + ?> +
    + +
    + product->icon ) ) { + return; + } + ?> +
    + +
    + product->description ) ) { + return; + } + ?> +
    product->description ) ); ?>
    + product->features ) || ! is_array( $this->product->features ) ) { + return; + } + ?> +
    +
      + product->features as $feature ) : ?> +
    • + +
    +
    + +
    + is_plugin_active && ! empty( $this->inactive_parameters['button_text'] ) ) { + ?> +
    + button( $this->inactive_parameters ); ?> +
    + +
    + link( $this->active_parameters ); ?> +
    +
    + version ) { + return; + } + ?> +
    + version ) ); + ?> +
    + active_parameters; + if ( ! $this->is_plugin_active && ! empty( $this->inactive_parameters['button_text'] ) ) { + $args = $this->inactive_parameters; + } + ?> +
    + select_installer_action( $args ); ?> +
    + is_plugin_active && ! empty( $this->inactive_parameters['button_text'] ) ) { + $this->button( $this->inactive_parameters ); + return; + } + + $this->link( $this->active_parameters ); + } + + /** + * Gets the CSS classes for the single extension card. + * + * @since 2.11.4 + * @return array The array of CSS classes. + */ + private function get_card_classes() { + $base_class = 'edd-extension-manager__card'; + $card_classes = array( + $base_class, + ); + if ( $this->is_plugin_installed ) { + $card_classes[] = 'edd-plugin__installed'; + if ( $this->is_plugin_active ) { + $card_classes[] = 'edd-plugin__active'; + } else { + $card_classes[] = 'edd-plugin__inactive'; + } + } + $variation = 'stacked'; + if ( ! empty( $this->product->style ) ) { + $variation = $this->product->style; + } + if ( 'detailed-2col' === $variation && ( empty( $this->product->features ) || ! is_array( $this->product->features ) ) ) { + $variation = 'detailed'; + } + $card_classes[] = "{$base_class}--{$variation}"; + + return $card_classes; + } + + /** + * Gets the data-filter terms for a card. + * + * @since 3.1.1 + * @return string + */ + private function get_filter_terms() { + $terms = array(); + if ( ! empty( $this->product->terms ) ) { + $terms = array_keys( (array) $this->product->terms ); + } + if ( ! empty( $this->product->tab ) ) { + $terms[] = $this->product->tab; + } + + return implode( ',', array_map( 'strtolower', array_filter( $terms ) ) ); + } +} diff --git a/src/Admin/Extensions/DownloadURL.php b/src/Admin/Extensions/DownloadURL.php new file mode 100644 index 00000000000..7673f5a87ce --- /dev/null +++ b/src/Admin/Extensions/DownloadURL.php @@ -0,0 +1,64 @@ +plugin = sanitize_text_field( $_POST['plugin'] ); + } + $this->license_key = $license_key; + } + + /** + * Gets the download URL. + * + * @since 3.1.1 + * @return bool|string + */ + public function get_url() { + if ( ! $this->plugin ) { + return false; + } + if ( false === strpos( $this->plugin, 'https://downloads.wordpress.org/plugin' ) ) { + return false; + } + if ( ! in_array( $this->plugin, $this->get_allowed_urls(), true ) ) { + return false; + } + + return $this->plugin; + } + + /** + * Gets an array of allowed download URLs. + * + * @since 3.1.2 + * @return array + */ + private function get_allowed_urls() { + return array( + 'https://downloads.wordpress.org/plugin/edd-auto-register.zip', + 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip', + 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip', + 'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.zip', + ); + } +} diff --git a/src/Admin/Extensions/Extension.php b/src/Admin/Extensions/Extension.php new file mode 100644 index 00000000000..235d07ceb30 --- /dev/null +++ b/src/Admin/Extensions/Extension.php @@ -0,0 +1,403 @@ +manager = new \EDD\Admin\Extensions\Extension_Manager( static::PASS_LEVEL ); + $this->pass_manager = new Pass_Manager(); + } + + /** + * Whether the extension is activated. + * + * @since 2.11.4 + * @return bool + */ + abstract protected function is_activated(); + + /** + * Output the settings field (installation helper). + * + * @return void + */ + public function settings_field() { + if ( $this->is_activated() ) { + return; + } + $this->do_single_extension_card(); + } + + /** + * Outputs a single extension card. + * + * @since 2.11.4 + * @param false|int $item_id Optional: the individual extension product ID. + * @return void + */ + public function do_single_extension_card( $item_id = false ) { + if ( empty( $item_id ) && empty( $this->item_id ) ) { + return; + } + $product_data = $this->get_product_data( $item_id ); + if ( ! $product_data || empty( $product_data->title ) ) { + return; + } + + $configuration = $this->get_configuration( $product_data ); + if ( ! empty( $configuration ) ) { + $product_data = $product_data->mergeConfig( $configuration ); + } + $this->manager->do_extension_card( + $product_data, + $this->get_inactive_parameters( $product_data, $item_id ), + $this->get_active_parameters( $product_data, $item_id ) + ); + } + + /** + * Gets the parameters for an inactive plugin. + * + * @since 3.1.1 + * @param ProductData $product_data The extension data returned from the Products API. + * @param int $item_id The individual extension product ID. + * @return array + */ + protected function get_inactive_parameters( $product_data, $item_id ) { + return $this->get_button_parameters( $product_data, $item_id ); + } + + /** + * Gets the parameters for an active plugin. + * + * @since 3.1.1 + * @param ProductData $product_data The extension data returned from the Products API. + * @param int $item_id The individual extension product ID. + * @return array + */ + protected function get_active_parameters( $product_data, $item_id ) { + return $this->get_link_parameters( $product_data ); + } + + /** + * Gets the product data for a specific extension. + * + * @param false|int $item_id + * @return bool|ProductData|array False if there is no data; product data object if there is, or possibly an array of arrays. + */ + public function get_product_data( $item_id = false ) { + $api = new ExtensionsAPI(); + $body = $this->get_api_body(); + $api_item_id = $item_id ?: $this->item_id; + $product_data = $api->get_product_data( $body, $api_item_id ); + if ( ! $product_data ) { + return false; + } + + if ( $api_item_id ) { + return $product_data; + } + + if ( $item_id && ! empty( $product_data[ $item_id ] ) ) { + return $product_data[ $item_id ]; + } + + return $product_data; + } + + /** + * Gets the custom configuration for the extension. + * + * @since 2.11.4 + * @param ProductData $product_data Optionally allows the product data to be parsed in the configuration. + * @return array + */ + protected function get_configuration( ProductData $product_data ) { + return array(); + } + + /** + * Formats a custom description array by running wpautop and converting it to a string. + * + * @since 2.11.4 + * @param array $description The custom product description. + * @return string + */ + protected function format_description( array $description ) { + return implode( '', array_map( 'wpautop', $description ) ); + } + + /** + * Whether the current screen is an EDD setings screen. + * + * @since 2.11.4 + * @return bool + */ + protected function is_edd_settings_screen() { + return edd_is_admin_page( 'settings', $this->settings_tab ); + } + + /** + * Whether the current screen is a download new/edit screen. + * + * @since 2.11.4 + * @return bool + */ + protected function is_download_edit_screen() { + return edd_is_admin_page( 'download', 'edit' ) || edd_is_admin_page( 'download', 'new' ); + } + + /** + * Whether the section for an individual product can be registered/shown. + * + * @since 2.11.4 + * @return bool + */ + protected function can_show_product_section() { + if ( ! $this->is_edd_settings_screen() ) { + return false; + } + if ( $this->is_activated() ) { + return false; + } + if ( ! $this->get_product_data() ) { + return false; + } + + return true; + } + + /** + * Gets the array for the body of the API request. + * Classes may need to override this (for example, to query a specific tag). + * Note that the first array key/value pair are used to create the option name. + * + * @return array + */ + protected function get_api_body() { + return array(); + } + + /** + * Gets the type for the button data-type attribute. + * This is intended to sync with the Products API request. + * Default is product. + * + * Really a shim for array_key_first. + * + * @param array $array + * @return string + */ + private function get_type( array $array ) { + $type = 'product'; + if ( empty( $array ) ) { + return $type; + } + if ( function_exists( 'array_key_first' ) ) { + return array_key_first( $array ); + } + foreach ( $array as $key => $unused ) { + return $key; + } + + return $type; + } + + /** + * Gets the button parameters. + * Classes should not need to replace this method. + * + * @param ProductData $product_data The extension data returned from the Products API. + * @param int|false $item_id Optional: the item ID. + * @return array + */ + protected function get_button_parameters( ProductData $product_data, $item_id = false ) { + if ( empty( $item_id ) ) { + $item_id = $this->item_id; + } + $body = $this->get_api_body(); + $type = $this->get_type( $body ); + $id = ! empty( $body[ $type ] ) ? $body[ $type ] : $this->item_id; + $button = array( + 'type' => $type, + 'id' => $id, + 'product' => $item_id, + ); + // If the extension is not installed, the button will prompt to install and activate it. + if ( empty( $product_data->basename ) || ! $this->current_user_can() || ! $this->manager->is_plugin_installed( $product_data->basename ) ) { + $required_pass_id = ! empty( $product_data->pass_id ) ? $product_data->pass_id : static::PASS_LEVEL; + if ( $this->manager->pass_can_download( $required_pass_id ) ) { + $button = array( + /* translators: The extension name. */ + 'button_text' => sprintf( __( 'Log In to Your Account to Download %s', 'easy-digital-downloads' ), $product_data->title ), + 'href' => $this->get_upgrade_url( $product_data, $item_id, true ), + 'new_tab' => true, + 'type' => $type, + ); + } else { + $button = array( + /* translators: The extension name. */ + 'button_text' => sprintf( __( 'Upgrade Today to Access %s!', 'easy-digital-downloads' ), $product_data->title ), + 'href' => $this->get_upgrade_url( $product_data, $item_id ), + 'new_tab' => true, + 'type' => $type, + ); + } + + return $button; + } + + if ( ! empty( $product_data->basename ) && $this->current_user_can() ) { + $button['plugin'] = $product_data->basename; + // If the extension is installed, but not activated, the button will prompt to activate it. + if ( ! $this->manager->is_plugin_active( $product_data->basename ) ) { + $button['action'] = 'activate'; + /* translators: The extension name. */ + $button['button_text'] = sprintf( __( 'Activate %s', 'easy-digital-downloads' ), $product_data->title ); + } elseif ( ! empty( $product_data->style ) && 'installer' === $product_data->style ) { + $button['action'] = 'deactivate'; + /* translators: The extension name. */ + $button['button_text'] = sprintf( __( 'Deactivate %s', 'easy-digital-downloads' ), $product_data->title ); + } + } + + return $button; + } + + /** + * Gets the upgrade URL for the button. + * + * @since 2.11.4 + * @param ProductData $product_data The product data object. + * @param int $item_id The item/product ID. + * @param bool $has_access Whether the user already has access to the extension (based on pass level). + * @return string + */ + protected function get_upgrade_url( ProductData $product_data, $item_id, $has_access = false ) { + if ( $has_access ) { + $url = 'https://easydigitaldownloads.com/your-account/your-downloads/'; + } else { + $url = 'https://easydigitaldownloads.com/lite-upgrade'; + } + + $utm_parameters = array( + 'utm_medium' => $this->settings_section, + 'utm_content' => $product_data->slug, + ); + + return edd_link_helper( + $url, + $utm_parameters + ); + } + + /** + * Gets the array of parameters for the link to configure the extension. + * + * @since 2.11.4 + * @param ProductData $product_data The product data object. + * @return array + */ + protected function get_link_parameters( ProductData $product_data ) { + $configuration = $this->get_configuration( $product_data ); + $tab = ! empty( $configuration['tab'] ) ? $configuration['tab'] : $product_data->tab; + $section = ! empty( $configuration['section'] ) ? $configuration['section'] : $product_data->section; + if ( ! empty( $tab ) && ! empty( $section ) && ! empty( $product_data->basename ) && $this->current_user_can() ) { + return array( + /* translators: The extension name. */ + 'button_text' => sprintf( __( 'Configure %s', 'easy-digital-downloads' ), $product_data->title ), + 'href' => edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => urlencode( $tab ), + 'section' => urlencode( $section ), + ) + ), + ); + } + + return array( + /* translators: the plural Downloads label. */ + 'button_text' => sprintf( __( 'View %s', 'easy-digital-downloads' ), edd_get_label_plural() ), + 'href' => add_query_arg( + array( + 'post_type' => 'download', + ), + admin_url( 'edit.php' ) + ), + ); + } + + /** + * Optionally hides the submit button on screens where it's not needed. + * + * @since 2.11.4 + * @return void + */ + public function hide_submit_button() { + if ( ! $this->can_show_product_section() ) { + return; + } + ?> + + +
    +
    +
    +
    +
    +

    get_heading_text() ); ?>

    + refresh(); ?> +
    +
    +
    + + +
    +
    +

    + get_intro_text() ); ?>
    + show_missing_key_question(); ?> +

    + do_cards(); ?> +
    + get_button_parameters( $product_data, $item_id ); + } + + /** + * Outputs the cards. + * + * @since 3.1.1 + * @return void + */ + protected function do_cards() { + ?> +
    + get_product_data() as $item_id => $extension ) { + $this->do_single_extension_card( $item_id ); + } + ?> +
    + can_show_pass_refresh() ) { + return; + } + ?> + + pass_manager->highest_pass_id ) ) { + /* translators: the active pass name */ + return sprintf( __( 'Add functionality to your Easy Digital Downloads powered store with your %s.', 'easy-digital-downloads' ), $this->pass_manager->get_pass_name() ); + } + + return __( 'Add functionality to your Easy Digital Downloads powered store.', 'easy-digital-downloads' ); + } + + /** + * If a pass hasn't been saved, show the text offering to add it. + * + * @since 3.1.1 + * @return void + */ + protected function show_missing_key_question() { + if ( ! empty( $this->pass_manager->highest_pass_id ) && edd_is_pro() ) { + return; + } + $url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + ) + ); + printf( + wp_kses_post( + /* translators: 1. opening anchor tag; 2. closing anchor tag. */ + __( 'Missing access to an extension? %1$sAdd your license key now%2$s.', 'easy-digital-downloads' ) + ), + '', + '' + ); + } + + /** + * Updates the card configuration. + * + * @since 3.1.1 + * @param ProductData $product_data The extension data returned from the Products API. + * @return array + */ + protected function get_configuration( ProductData $product_data ) { + + return array( + 'style' => 'installer', + 'pass_id' => $this->pass_manager->can_access_categories( $product_data->categories ), + ); + } + + /** + * Update the button parameters. + * + * @since 3.1.1 + * @param ProductData $product_data The extension data returned from the Products API. + * @param bool|int $item_id + * @return array + */ + protected function get_button_parameters( ProductData $product_data, $item_id = false ) { + $button = parent::get_button_parameters( $product_data, $item_id ); + + // Lite always shows "Upgrade Now". + $button['button_text'] = __( 'Upgrade Now', 'easy-digital-downloads' ); + $button['button_class'] = 'button-primary edd-promo-notice__trigger'; + $button['type'] = 'extension'; + + return $button; + } + + /** + * Overrides the body array sent to the Products API. + * Download category 1592 is "extensions". + * + * @since 3.1.1 + * @return array + */ + protected function get_api_body() { + return array( 'category' => 1592 ); + } + + /** + * Whether the current extension is activated. + * Not used here for now but required since it's an abstract method. + * + * @since 3.1.1 + * @return bool + */ + protected function is_activated() { + return false; + } + + /** + * Checks the current user's capability level. + * + * @since 3.1.1 + * @param string $capability + * @return bool + */ + protected function current_user_can( $capability = 'activate_plugins' ) { + return false; + } + + /** + * Gets the upgrade URL for the button. + * + * @since 3.1.1 + * @param ProductData $product_data The product data object. + * @param int $item_id The item/product ID. + * @param bool $has_access Whether the user already has access to the extension (based on pass level). + * @return string + */ + protected function get_upgrade_url( ProductData $product_data, $item_id, $has_access = false ) { + if ( $has_access ) { + $url = 'https://easydigitaldownloads.com/your-account/your-downloads/'; + } else { + $url = 'https://easydigitaldownloads.com/lite-upgrade'; + } + + $utm_parameters = array( + 'utm_medium' => 'extensions-page', + 'utm_content' => $product_data->slug, + ); + + return edd_link_helper( + $url, + $utm_parameters + ); + } +} diff --git a/src/Admin/Extensions/Extension_Manager.php b/src/Admin/Extensions/Extension_Manager.php new file mode 100644 index 00000000000..98d8b6c0d89 --- /dev/null +++ b/src/Admin/Extensions/Extension_Manager.php @@ -0,0 +1,518 @@ +required_pass_id = $required_pass_id; + } + $this->pass_manager = new Pass_Manager(); + } + + /** + * Gets the subscribed events for this class. + * + * @since 3.1.1 + * @return array + */ + public static function get_subscribed_events() { + return array( + 'wp_ajax_edd_activate_extension' => 'activate', + 'wp_ajax_edd_install_extension' => 'install', + 'wp_ajax_edd_deactivate_extension' => 'deactivate', + 'admin_enqueue_scripts' => 'register_assets', + 'edd_after_ajax_activate_extension' => 'post_extension_activation', + ); + } + + /** + * Registers the extension manager script and style. + * + * @since 2.11.4 + * @return void + */ + public function register_assets() { + if ( wp_script_is( 'edd-extension-manager', 'registered' ) ) { + return; + } + $minify = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + wp_register_style( 'edd-extension-manager', EDD_PLUGIN_URL . 'assets/css/edd-admin-extension-manager.min.css', array(), EDD_VERSION ); + wp_register_script( 'edd-extension-manager', EDD_PLUGIN_URL . 'assets/js/edd-admin-extension-manager.js', array( 'jquery' ), EDD_VERSION, true ); + wp_localize_script( + 'edd-extension-manager', + 'EDDExtensionManager', + array( + 'activating' => __( 'Activating', 'easy-digital-downloads' ), + 'installing' => __( 'Installing', 'easy-digital-downloads' ), + 'plugin_install_failed' => __( 'Could not install the plugin. Please download and install it manually via Plugins > Add New.', 'easy-digital-downloads' ), + 'extension_install_failed' => sprintf( + /* translators: 1. opening anchor tag, do not translate; 2. closing anchor tag, do not translate */ + __( 'Could not install the extension. Please %1$sdownload it from your account%2$s and install it manually.', 'easy-digital-downloads' ), + '', + '' + ), + 'extension_manager_nonce' => wp_create_nonce( 'edd_extensionmanager' ), + 'results' => __( 'extensions found', 'easy-digital-downloads' ), + 'deactivating' => __( 'Deactivating', 'easy-digital-downloads' ), + 'debug' => edd_doing_script_debug(), + 'filter' => filter_input( INPUT_GET, 'filter', FILTER_SANITIZE_STRING ), + ) + ); + } + + /** + * Enqueues the extension manager script/style. + * + * @since 2.11.4 + * @return void + */ + public function enqueue() { + wp_enqueue_style( 'edd-extension-manager' ); + wp_enqueue_script( 'edd-extension-manager' ); + } + + /** + * Outputs a standard extension card. + * + * @since 2.11.4 + * @param ProductData $product The product data object. + * @param array $inactive_parameters The array of information to build the button for an inactive/not installed plugin. + * @param array $active_parameters The array of information needed to build the link to configure an active plugin. + * @param array $configuration The optional array of data to override the product data retrieved from the API. + * @return void + */ + public function do_extension_card( ProductData $product, $inactive_parameters, $active_parameters, $configuration = array() ) { + $this->enqueue(); + + $parameters = array( + 'inactive_parameters' => $inactive_parameters, + 'active_parameters' => $active_parameters, + 'required_pass_id' => ! empty( $product->pass_id ) ? $product->pass_id : $this->required_pass_id, + 'is_plugin_installed' => false, + 'is_plugin_active' => false, + 'version' => ! empty( $product->version ) ? $product->version : false, + ); + if ( ! empty( $product->basename ) ) { + $parameters['is_plugin_installed'] = $this->is_plugin_installed( $product->basename ); + $parameters['is_plugin_active'] = $this->is_plugin_active( $product->basename ); + $parameters['version'] = $this->get_plugin_version( $product->basename ); + } + $card_class = edd_get_namespace( 'Admin\\Extensions\\Card' ); + $card = new $card_class( + $product, + $parameters + ); + } + + /** + * Installs and maybe activates a plugin or extension. + * + * @since 2.11.4 + */ + public function install() { + // Run a security check. + check_ajax_referer( 'edd_extensionmanager', 'nonce', true ); + + $generic_error = esc_html__( 'There was an error while performing your request.', 'easy-digital-downloads' ); + $type = ! empty( $_POST['type'] ) ? sanitize_text_field( $_POST['type'] ) : ''; + $required_pass = ! empty( $_POST['pass'] ) ? sanitize_text_field( $_POST['pass'] ) : ''; + $result = array( + 'message' => $generic_error, + 'is_activated' => false, + 'type' => $type, + ); + if ( ! $type ) { + wp_send_json_error( $result ); + } + + // Check if new installations are allowed. + if ( ! $this->can_install( $type, $required_pass ) ) { + $result['message'] = __( 'Plugin installation is not available for you on this site.', 'easy-digital-downloads' ); + if ( 'extension' === $type ) { + $result['highest_pass'] = $this->pass_manager->highest_pass_id; + } + wp_send_json_error( $result ); + } + + $result['message'] = 'plugin' === $type + ? __( 'Could not install the plugin. Please download and install it manually via Plugins > Add New.', 'easy-digital-downloads' ) + : sprintf( + /* translators: 1. opening anchor tag, do not translate; 2. closing anchor tag, do not translate */ + __( 'Could not install the extension. Please %1$sdownload it from your account%2$s and install it manually.', 'easy-digital-downloads' ), + '', + '' + ); + + $download_url_classname = \edd_get_namespace( 'Admin\\Extensions\\DownloadURL' ); + $download_url_class = new $download_url_classname( $this->pass_manager->highest_license_key ); + $plugin = $download_url_class->get_url(); + if ( empty( $plugin ) ) { + wp_send_json_error( $result ); + } + + // Set the current screen to avoid undefined notices. + set_current_screen( 'download_page_edd-settings' ); + + // Prepare variables. + $url = esc_url_raw( + edd_get_admin_url( + array( + 'page' => 'edd-addons', + ) + ) + ); + + ob_start(); + $creds = request_filesystem_credentials( $url, '', false, false, null ); + + // Hide the filesystem credentials form. + ob_end_clean(); + + // Check for file system permissions. + if ( ! $creds ) { + wp_send_json_error( $result ); + } + + if ( ! WP_Filesystem( $creds ) ) { + wp_send_json_error( $result ); + } + + /* + * We do not need any extra credentials if we have gotten this far, so let's install the plugin. + */ + + // Do not allow WordPress to search/download translations, as this will break JS output. + remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); + + // Create the plugin upgrader with our custom skin. + $installer = new \EDD\Admin\Installers\PluginSilentUpgrader( new \EDD\Admin\Installers\Install_Skin() ); + + // Error check. + if ( ! method_exists( $installer, 'install' ) || empty( $plugin ) ) { + wp_send_json_error( $result ); + } + + $installer->install( $plugin ); // phpcs:ignore + + // Flush the cache and return the newly installed plugin basename. + wp_cache_flush(); + + $plugin_basename = $installer->plugin_info(); + + // Check for permissions. + if ( ! current_user_can( 'activate_plugins' ) ) { + $result['message'] = 'plugin' === $type ? esc_html__( 'Plugin installed.', 'easy-digital-downloads' ) : esc_html__( 'Extension installed.', 'easy-digital-downloads' ); + + wp_send_json_error( $result ); + } + + $this->activate( $plugin_basename ); + } + + /** + * Activates an existing extension. + * + * @since 2.11.4 + * @param string $plugin_basename Optional: the plugin basename. + */ + public function activate( $plugin_basename = '' ) { + $result = array( + 'message' => __( 'There was an error while performing your request.', 'easy-digital-downloads' ), + 'is_activated' => false, + ); + + // Check for permissions. + if ( ! check_ajax_referer( 'edd_extensionmanager', 'nonce', false ) || ! current_user_can( 'activate_plugins' ) ) { + $result['message'] = __( 'Plugin activation is not available for you on this site.', 'easy-digital-downloads' ); + wp_send_json_error( $result ); + } + + $already_installed = false; + if ( empty( $plugin_basename ) ) { + $plugin_basename = ! empty( $_POST['plugin'] ) ? sanitize_text_field( $_POST['plugin'] ) : ''; + $already_installed = true; + } + + $plugin_basename = sanitize_text_field( wp_unslash( $plugin_basename ) ); + $type = ! empty( $_POST['type'] ) ? sanitize_text_field( $_POST['type'] ) : ''; + if ( 'plugin' !== $type ) { + $type = 'extension'; + } + $result = array( + /* translators: "extension" or "plugin" as defined by $type */ + 'message' => sprintf( __( 'Could not activate the %s.', 'easy-digital-downloads' ), esc_html( $type ) ), + 'is_activated' => false, + ); + if ( empty( $plugin_basename ) || empty( $type ) ) { + wp_send_json_error( $result ); + } + + $result['basename'] = $plugin_basename; + + // Set the GET variable for multi-plugin activation. + $_GET['activate-multi'] = true; + + // Activate the plugin silently. + $activated = activate_plugin( $plugin_basename ); + + if ( is_wp_error( $activated ) ) { + wp_send_json_error( $result ); + } + + do_action( 'edd_after_ajax_activate_extension', sanitize_text_field( $plugin_basename ) ); + + // At this point we have successfully activated. + if ( $already_installed ) { + $message = 'plugin' === $type ? esc_html__( 'Plugin activated.', 'easy-digital-downloads' ) : esc_html__( 'Extension activated.', 'easy-digital-downloads' ); + } else { + $message = 'plugin' === $type ? esc_html__( 'Plugin installed & activated.', 'easy-digital-downloads' ) : esc_html__( 'Extension installed & activated.', 'easy-digital-downloads' ); + } + + $success = array( + 'is_activated' => true, + 'message' => $message, + ); + if ( edd_is_pro() && class_exists( '\\EDD\\Pro\\Admin\\Extensions\\Buttons' ) ) { + $buttons = new \EDD\Pro\Admin\Extensions\Buttons(); + $success['status'] = __( 'Activated', 'easy-digital-downloads' ); + $success['button'] = $buttons->get_activate_deactivate_button( + array( + 'type' => $type, + 'id' => filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT ), + 'product' => filter_input( INPUT_POST, 'product', FILTER_SANITIZE_NUMBER_INT ), + 'plugin' => $plugin_basename, + 'action' => 'deactivate', + ) + ); + } + + wp_send_json_success( $success ); + } + + /** + * Deactivates a plugin. + * + * @since 3.1.1 + * @return void + */ + public function deactivate() { + $result = array( + 'message' => __( 'There was an error while performing your request.', 'easy-digital-downloads' ), + 'is_deactivated' => false, + ); + + // Check for permissions. + if ( ! check_ajax_referer( 'edd_extensionmanager', 'nonce', false ) || ! current_user_can( 'deactivate_plugins' ) ) { + $result['message'] = __( 'Plugin deactivation is not available for you on this site.', 'easy-digital-downloads' ); + wp_send_json_error( $result ); + } + + $plugin = ! empty( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : false; + if ( empty( $plugin ) ) { + wp_send_json_error( $result ); + } + + // At this point, we are allowed to deactivate the extension. + $type = ! empty( $_POST['type'] ) ? sanitize_text_field( $_POST['type'] ) : ''; + if ( 'plugin' !== $type ) { + $type = 'extension'; + } + deactivate_plugins( $plugin ); + + $this->update_wp_activation_data( $plugin ); + + $success = array( + 'message' => 'plugin' === $type ? esc_html__( 'Plugin deactivated.', 'easy-digital-downloads' ) : esc_html__( 'Extension deactivated.', 'easy-digital-downloads' ), + 'is_deactivated' => true, + ); + if ( edd_is_pro() && class_exists( '\\EDD\\Pro\\Admin\\Extensions\\Buttons' ) ) { + $buttons = new \EDD\Pro\Admin\Extensions\Buttons(); + $success['button'] = $buttons->get_activate_deactivate_button( + array( + 'type' => $type, + 'id' => filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT ), + 'product' => filter_input( INPUT_POST, 'product', FILTER_SANITIZE_NUMBER_INT ), + 'plugin' => $plugin, + 'action' => 'activate', + ) + ); + } + + wp_send_json_success( $success ); + } + + /** + * Determine if the plugin/extension installations are allowed. + * + * @since 2.11.4 + * + * @param string $type Should be `plugin` or `extension`. + * + * @return bool + */ + public function can_install( $type, $required_pass_id = false ) { + + if ( ! current_user_can( 'install_plugins' ) || ( is_multisite() && ! is_super_admin() ) ) { + return false; + } + + // Determine whether file modifications are allowed. + if ( ! wp_is_file_mod_allowed( 'edd_can_install' ) ) { + return false; + } + + // All plugin checks are done. + if ( 'plugin' === $type ) { + return true; + } + + return $this->pass_can_download( $required_pass_id ); + } + + /** + * Checks if a user's pass can download an extension. + * + * @since 2.11.4 + * @return bool Returns true if the current site has an active pass and it is greater than or equal to the extension's minimum pass. + */ + public function pass_can_download( $required_pass_id = false ) { + $highest_pass_id = $this->pass_manager->highest_pass_id; + if ( ! $required_pass_id ) { + $required_pass_id = $this->required_pass_id; + } + + return ! empty( $highest_pass_id ) && ! empty( $required_pass_id ) && $this->pass_manager->pass_compare( $highest_pass_id, $required_pass_id, '>=' ); + } + + /** + * Get all installed plugins. + * + * @since 2.11.4 + * @return array + */ + public function get_plugins() { + if ( $this->all_plugins ) { + return $this->all_plugins; + } + + $this->all_plugins = get_plugins(); + + return $this->all_plugins; + } + + /** + * Check if a plugin is installed. + * + * @since 2.11.4 + * @param string $plugin The path to the main plugin file, eg 'my-plugin/my-plugin.php'. + * @return boolean + */ + public function is_plugin_installed( $plugin ) { + return array_key_exists( $plugin, $this->get_plugins() ); + } + + /** + * Whether a given plugin is active or not. + * + * @since 2.11.4 + * @param string|ProductData $basename_or_data The path to the main plugin file, eg 'my-plugin/my-plugin.php', or the product data object. + * @return boolean + */ + public function is_plugin_active( $basename_or_data ) { + $basename = ! empty( $basename_or_data->basename ) ? $basename_or_data->basename : $basename_or_data; + + return ! empty( $basename ) && is_plugin_active( $basename ); + } + + /** + * Gets the plugin version. + * + * @since 3.1.1 + * @param string $basename + * @return false|string + */ + protected function get_plugin_version( $basename ) { + if ( empty( $basename ) ) { + return false; + } + + $plugins = $this->get_plugins(); + + return array_key_exists( $basename, $plugins ) + ? $plugins[ $basename ]['Version'] + : false; + } + + /** + * When a plugin is deactivated, update the related WP options. + * + * @since 3.1.1 + * @param string $plugin + * @return void + */ + private function update_wp_activation_data( $plugin ) { + $deactivated = array( + $plugin => time(), + ); + + if ( ! is_network_admin() ) { + update_option( 'recently_activated', $deactivated + (array) get_option( 'recently_activated' ) ); + } else { + update_site_option( 'recently_activated', $deactivated + (array) get_site_option( 'recently_activated' ) ); + } + } + + /** + * When the extension manager activates a plugin, possibly modify behavior. + * + * @since 3.1.0.4 + * + * @param string $plugin_basename The plugin basename being activated via the extension manager. + */ + public function post_extension_activation( $plugin_basename ) { + if ( empty( $plugin_basename ) ) { + return; + } + + switch ( $plugin_basename ) { + case 'wp-mail-smtp/wp_mail_smtp.php': + update_option( 'wp_mail_smtp_activation_prevent_redirect', true ); + break; + case 'all-in-one-seo-pack/all_in_one_seo_pack.php': + update_option( 'aioseo_activation_redirect', true ); + break; + case 'google-analytics-for-wordpress/googleanalytics.php': + delete_transient( '_monsterinsights_activation_redirect' ); + break; + } + } +} diff --git a/src/Admin/Extensions/ExtensionsAPI.php b/src/Admin/Extensions/ExtensionsAPI.php new file mode 100644 index 00000000000..47cf20d7ba6 --- /dev/null +++ b/src/Admin/Extensions/ExtensionsAPI.php @@ -0,0 +1,286 @@ +get_api_body( $item_id ); + } + $key = $this->array_key_first( $body ); + // The option name is created from the first key/value pair of the API "body". + $option_name = sanitize_key( "edd_extension_{$key}_{$body[ $key ]}_data" ); + $option = get_site_option( $option_name ); + $is_stale = $this->option_has_expired( $option ); + + // The ProductData class. + $product_data = new ProductData(); + + // If the data is "fresh" and what we want exists, return it. + if ( $option && ! $is_stale ) { + if ( $item_id && ! empty( $option[ $item_id ] ) ) { + return $product_data->fromArray( $option[ $item_id ] ); + } elseif ( ! empty( $option['timeout'] ) ) { + unset( $option['timeout'] ); + + return $option; + } + } + + // Get all of the product data. + $all_product_data = $this->get_all_product_data(); + + // If no product data was retrieved, let the option sit for an hour. + if ( empty( $all_product_data ) ) { + $data = array( + 'timeout' => strtotime( '+1 hour', time() ), + ); + if ( $option && $is_stale ) { + $data = array_merge( $option, $data ); + } + update_site_option( $option_name, $data ); + + if ( $item_id && ! empty( $option[ $item_id ] ) ) { + return $product_data->fromArray( $option[ $item_id ] ); + } + unset( $option['timeout'] ); + + return $option; + } + + $value = array( + 'timeout' => strtotime( '+1 week', time() ), + ); + if ( $item_id && ! empty( $all_product_data->$item_id ) ) { + $item = $all_product_data->$item_id; + $value[ $item_id ] = $this->get_item_data( $item ); + } elseif ( in_array( $key, array( 'category', 'tag' ), true ) ) { + $term_id = $body[ $key ]; + if ( 1592 === $body[ $key ] ) { + $value = $value + $this->get_pass_extensions_data( $all_product_data ); + } else { + foreach ( $all_product_data as $item_id => $item ) { + if ( 'category' === $key && ( empty( $item->categories ) || ! in_array( $term_id, $item->categories, true ) ) ) { + continue; + } elseif ( 'tag' === $key && ( empty( $item->tags ) || ! in_array( $term_id, $item->tags, true ) ) ) { + continue; + } + $value[ $item_id ] = $this->get_item_data( $item ); + } + } + } + + if ( is_multisite() && get_option( $option_name ) ) { + delete_option( $option_name ); + } + update_site_option( $option_name, $value ); + unset( $value['timeout'] ); + + return $item_id && ! empty( $value[ $item_id ] ) ? $product_data->fromArray( $value[ $item_id ] ) : $value; + } + + /** + * Gets the extensions data for displaying on the extensions page. + * The extensions are grouped by pass. + * + * @since 3.1.1 + * @param object $all_product_data + * @return array + */ + private function get_pass_extensions_data( $all_product_data ) { + $personal_pass = array(); + $extended_pass = array(); + $pro_pass = array(); + $all_access_pass = array(); + $pass_manager = new Pass_Manager(); + foreach ( $all_product_data as $item_id => $item ) { + if ( ! empty( $item->categories ) ) { + if ( ! in_array( 1592, $item->categories, true ) ) { + continue; + } + if ( in_array( $pass_manager->categories[ $pass_manager::PERSONAL_PASS_ID ], $item->categories, true ) ) { + $personal_pass[ $item_id ] = $this->get_item_data( $item ); + } elseif ( in_array( $pass_manager->categories[ $pass_manager::EXTENDED_PASS_ID ], $item->categories, true ) ) { + $extended_pass[ $item_id ] = $this->get_item_data( $item ); + } elseif ( in_array( $pass_manager->categories[ $pass_manager::PROFESSIONAL_PASS_ID ], $item->categories, true ) ) { + $pro_pass[ $item_id ] = $this->get_item_data( $item ); + } else { + $all_access_pass[ $item_id ] = $this->get_item_data( $item ); + } + } + } + + return $personal_pass + $extended_pass + $pro_pass + $all_access_pass; + } + + /** + * Gets all of the product data, either from an option or an API request. + * If the option exists and has data, it will be an object. + * + * @since 2.11.4 + * @return object|false + */ + private function get_all_product_data() { + // Possibly all product data is in an option. If it is, return it. + $all_product_data = get_site_option( 'edd_all_extension_data' ); + if ( $all_product_data && ! $this->option_has_expired( $all_product_data ) ) { + return ! empty( $all_product_data['products'] ) ? $all_product_data['products'] : false; + } + + // Otherwise, query the API. + $url = add_query_arg( + array( + 'edd_action' => 'extension_data', + ), + $this->get_products_url() + ); + $request = wp_remote_get( + esc_url_raw( $url ), + array( + 'timeout' => 15, + 'sslverify' => true, + ) + ); + + // If there was an API error, set option and return false. + if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) { + update_site_option( + 'edd_all_extension_data', + array( + 'timeout' => strtotime( '+1 hour', time() ), + ) + ); + + return false; + } + + // Fresh data has been retrieved, so update the option with a four hour timeout. + $all_product_data = json_decode( wp_remote_retrieve_body( $request ) ); + $data = array( + 'timeout' => strtotime( '+4 hours', time() ), + 'products' => $all_product_data, + ); + + if ( is_multisite() && get_option( 'edd_all_extension_data' ) ) { + delete_option( 'edd_all_extension_data' ); + } + update_site_option( 'edd_all_extension_data', $data ); + + return $all_product_data; + } + + /** + * Gets the product data as needed for the extension manager. + * + * @since 2.11.4 + * @param object $item + * @return array + */ + private function get_item_data( $item ) { + + return array( + 'title' => ! empty( $item->title ) ? $item->title : '', + 'slug' => ! empty( $item->slug ) ? $item->slug : '', + 'image' => ! empty( $item->image ) ? $item->image : '', + 'description' => ! empty( $item->excerpt ) ? $item->excerpt : '', + 'basename' => ! empty( $item->custom_meta->basename ) ? $item->custom_meta->basename : '', + 'tab' => ! empty( $item->custom_meta->settings_tab ) ? $item->custom_meta->settings_tab : '', + 'section' => ! empty( $item->custom_meta->settings_section ) ? $item->custom_meta->settings_section : '', + 'icon' => $this->get_icon( $item ), + 'categories' => ! empty( $item->categories ) ? $item->categories : array(), + 'terms' => ! empty( $item->terms ) ? $item->terms : array(), + 'version' => ! empty( $item->version ) ? $item->version : false, + ); + } + + /** + * Gets the product icon. + * + * @since 3.1.1 + * @param object $item + * @return string + */ + private function get_icon( $item ) { + $icon = ! empty( $item->custom_meta->icon ) ? $item->custom_meta->icon : ''; + + if ( $icon ) { + return $icon; + } + + $icon_size = '2x'; + + return ! empty( $item->icons->{$icon_size} ) ? $item->icons->{$icon_size} : $icon; + } + /** + * Gets the base url for the products remote request. + * + * @since 2.11.4 + * @return string + */ + private function get_products_url() { + if ( defined( 'EDD_PRODUCTS_URL' ) ) { + return EDD_PRODUCTS_URL; + } + + return 'https://easydigitaldownloads.com/'; + } + + /** + * Gets the default array for the body of the API request. + * A class may override this by setting an array to query a tag or category. + * Note that the first array key/value pair are used to create the option name. + * + * @since 2.11.4 + * @param int $item_id The product ID. + * @return array + */ + private function get_api_body( $item_id ) { + return array( + 'product' => $item_id, + ); + } + + /** + * Gets the first key of an array. + * (Shims array_key_first for PHP < 7.3) + * + * @since 2.11.4 + * @param array $array + * @return string|null + */ + private function array_key_first( array $array ) { + if ( function_exists( 'array_key_first' ) ) { + return array_key_first( $array ); + } + foreach ( $array as $key => $unused ) { + return $key; + } + + return null; + } + + /** + * Checks whether a given option has "expired". + * + * @since 2.11.4 + * @param array|false $option + * @return bool + */ + private function option_has_expired( $option ) { + return empty( $option['timeout'] ) || time() > $option['timeout']; + } +} diff --git a/src/Admin/Extensions/Legacy.php b/src/Admin/Extensions/Legacy.php new file mode 100644 index 00000000000..94da829fd27 --- /dev/null +++ b/src/Admin/Extensions/Legacy.php @@ -0,0 +1,83 @@ + 'manage_legacy_extensions', + ); + } + + /** + * Deactivates the legacy extension. + * + * @since 3.1.1 + * @return void + */ + public function manage_legacy_extensions() { + foreach ( $this->get_extensions() as $extension ) { + add_action( "plugin_action_links_{$extension['basename']}", array( $this, 'update_plugin_links' ), 10, 2 ); + if ( ! is_plugin_active( $extension['basename'] ) ) { + continue; + } + if ( $this->should_deactivate( $extension['basename'] ) ) { + deactivate_plugins( $extension['basename'] ); + } + if ( ! empty( $extension['option'] ) ) { + delete_option( $extension['option'] ); + } + } + } + + /** + * Removes the activation link from the plugins table. + * + * @since 3.1.1 + * @param array $links + * @param string $plugin_file + * @return array + */ + public function update_plugin_links( $links, $plugin_file ) { + $links['activate'] = __( 'Inactive — part of EDD', 'easy-digital-downloads' ); + + return $links; + } + + /** + * Gets the array of extensions which have been merged into EDD. + * + * @sice 3.1.1 + * @return array + */ + protected function get_extensions() { + return array( + 'edd-manual-purchases' => array( + 'basename' => 'edd-manual-purchases/edd-manual-purchases.php', + 'option' => 'edd_manual_purchases_license_active', + ), + ); + } + + /** + * Whether the plugin should be deactivated. + * + * @since 3.1.1 + * @param string $basename + * @return bool + */ + protected function should_deactivate( $basename ) { + return true; + } +} diff --git a/src/Admin/Extensions/Menu.php b/src/Admin/Extensions/Menu.php new file mode 100644 index 00000000000..74812ff4aa5 --- /dev/null +++ b/src/Admin/Extensions/Menu.php @@ -0,0 +1,42 @@ + $value ) { + $product_data->$key = $value; + } + + return $product_data; + } + + /** + * Merge an array of data into an object. + * + * @since 2.11.4 + * @param array $configuration The custom configuration data. + * @return ProductData + */ + public function mergeConfig( array $configuration ) { + foreach ( $configuration as $key => $value ) { + $this->{$key} = $value; + } + + return $this; + } +} diff --git a/src/Admin/Extensions/Traits/Buttons.php b/src/Admin/Extensions/Traits/Buttons.php new file mode 100644 index 00000000000..9fd1d9f6ad0 --- /dev/null +++ b/src/Admin/Extensions/Traits/Buttons.php @@ -0,0 +1,85 @@ +link()). + * + * @since 2.11.4 + * @param array $args The array of parameters for the button. + * @return void + */ + public function button( $args ) { + if ( ! empty( $args['href'] ) ) { + $this->link( $args ); + return; + } + $defaults = array( + 'button_class' => 'button-primary', + 'plugin' => '', + 'action' => '', + 'button_text' => '', + 'type' => 'plugin', + 'id' => '', + 'product' => '', + 'pass' => $this->required_pass_id, + ); + $args = wp_parse_args( $args, $defaults ); + if ( empty( $args['button_text'] ) ) { + return; + } + ?> + + 'button-primary', + 'button_text' => '', + ); + $args = wp_parse_args( $args, $defaults ); + if ( empty( $args['button_text'] ) || empty( $args['href'] ) ) { + return; + } + ?> + + > + + + '', // Please always pass this. + 'destination' => '', // And this + 'clear_destination' => false, + 'abort_if_destination_exists' => true, // Abort if the Destination directory exists, Pass clear_destination as false please + 'clear_working' => true, + 'is_multi' => false, + 'hook_extra' => array(), // Pass any extra $hook_extra args here, this will be passed to any hooked filters. + ); + + $options = wp_parse_args( $options, $defaults ); + + /** + * Filter the package options before running an update. + * + * See also {@see 'upgrader_process_complete'}. + * + * @since 4.3.0 + * + * @param array $options { + * Options used by the upgrader. + * + * @type string $package Package for update. + * @type string $destination Update location. + * @type bool $clear_destination Clear the destination resource. + * @type bool $clear_working Clear the working resource. + * @type bool $abort_if_destination_exists Abort if the Destination directory exists. + * @type bool $is_multi Whether the upgrader is running multiple times. + * @type array $hook_extra { + * Extra hook arguments. + * + * @type string $action Type of action. Default 'update'. + * @type string $type Type of update process. Accepts 'plugin', 'theme', or 'core'. + * @type bool $bulk Whether the update process is a bulk update. Default true. + * @type string $plugin Path to the plugin file relative to the plugins directory. + * @type string $theme The stylesheet or template name of the theme. + * @type string $language_update_type The language pack update type. Accepts 'plugin', 'theme', + * or 'core'. + * @type object $language_update The language pack update offer. + * } + * } + */ + $options = apply_filters( 'upgrader_package_options', $options ); + + if ( ! $options['is_multi'] ) { // call $this->header separately if running multiple times + $this->skin->header(); + } + + // Connect to the Filesystem first. + $res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) ); + // Mainly for non-connected filesystem. + if ( ! $res ) { + if ( ! $options['is_multi'] ) { + $this->skin->footer(); + } + return false; + } + + $this->skin->before(); + + if ( is_wp_error( $res ) ) { + $this->skin->error( $res ); + $this->skin->after(); + if ( ! $options['is_multi'] ) { + $this->skin->footer(); + } + return $res; + } + + /* + * Download the package (Note, This just returns the filename + * of the file if the package is a local file) + */ + $download = $this->download_package( $options['package'], true ); + + // Allow for signature soft-fail. + // WARNING: This may be removed in the future. + if ( is_wp_error( $download ) && $download->get_error_data( 'softfail-filename' ) ) { + + // Don't output the 'no signature could be found' failure message for now. + if ( (string) $download->get_error_code() !== 'signature_verification_no_signature' || WP_DEBUG ) { + // Outout the failure error as a normal feedback, and not as an error: + //$this->skin->feedback( $download->get_error_message() ); + + // Report this failure back to WordPress.org for debugging purposes. + wp_version_check( + array( + 'signature_failure_code' => $download->get_error_code(), + 'signature_failure_data' => $download->get_error_data(), + ) + ); + } + + // Pretend this error didn't happen. + $download = $download->get_error_data( 'softfail-filename' ); + } + + if ( is_wp_error( $download ) ) { + $this->skin->error( $download ); + $this->skin->after(); + if ( ! $options['is_multi'] ) { + $this->skin->footer(); + } + return $download; + } + + $delete_package = ( (string) $download !== (string) $options['package'] ); // Do not delete a "local" file. + + // Unzips the file into a temporary directory. + $working_dir = $this->unpack_package( $download, $delete_package ); + if ( is_wp_error( $working_dir ) ) { + $this->skin->error( $working_dir ); + $this->skin->after(); + if ( ! $options['is_multi'] ) { + $this->skin->footer(); + } + return $working_dir; + } + + // With the given options, this installs it to the destination directory. + $result = $this->install_package( + array( + 'source' => $working_dir, + 'destination' => $options['destination'], + 'clear_destination' => $options['clear_destination'], + 'abort_if_destination_exists' => $options['abort_if_destination_exists'], + 'clear_working' => $options['clear_working'], + 'hook_extra' => $options['hook_extra'], + ) + ); + + $this->skin->set_result( $result ); + if ( is_wp_error( $result ) ) { + $this->skin->error( $result ); + //$this->skin->feedback( 'process_failed' ); + } else { + // Installation succeeded. + //$this->skin->feedback( 'process_success' ); + } + + $this->skin->after(); + + if ( ! $options['is_multi'] ) { + + /** + * Fire when the upgrader process is complete. + * + * See also {@see 'upgrader_package_options'}. + * + * @since 3.6.0 + * @since 3.7.0 Added to WP_Upgrader::run(). + * @since 4.6.0 `$translations` was added as a possible argument to `$hook_extra`. + * + * @param WP_Upgrader $this WP_Upgrader instance. In other contexts, $this, might be a + * Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance. + * @param array $hook_extra { + * Array of bulk item update data. + * + * @type string $action Type of action. Default 'update'. + * @type string $type Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'. + * @type bool $bulk Whether the update process is a bulk update. Default true. + * @type array $plugins Array of the basename paths of the plugins' main files. + * @type array $themes The theme slugs. + * @type array $translations { + * Array of translations update data. + * + * @type string $language The locale the translation is for. + * @type string $type Type of translation. Accepts 'plugin', 'theme', or 'core'. + * @type string $slug Text domain the translation is for. The slug of a theme/plugin or + * 'default' for core translations. + * @type string $version The version of a theme, plugin, or core. + * } + * } + */ + do_action( 'upgrader_process_complete', $this, $options['hook_extra'] ); + + $this->skin->footer(); + } + + return $result; + } + + /** + * Toggle maintenance mode for the site. + * + * Create/delete the maintenance file to enable/disable maintenance mode. + * + * @since 2.8.0 + * + * @global WP_Filesystem_Base $wp_filesystem Subclass + * + * @param bool $enable True to enable maintenance mode, false to disable. + */ + public function maintenance_mode( $enable = false ) { + global $wp_filesystem; + $file = $wp_filesystem->abspath() . '.maintenance'; + if ( $enable ) { + //$this->skin->feedback( 'maintenance_start' ); + // Create maintenance file to signal that we are upgrading + $maintenance_string = ''; + $wp_filesystem->delete( $file ); + $wp_filesystem->put_contents( $file, $maintenance_string, FS_CHMOD_FILE ); + } elseif ( ! $enable && $wp_filesystem->exists( $file ) ) { + //$this->skin->feedback( 'maintenance_end' ); + $wp_filesystem->delete( $file ); + } + } + + /** + * Download a package. + * + * @since 2.8.0 + * @since 5.5.0 Added the `$hook_extra` parameter. + * + * @param string $package The URI of the package. If this is the full path to an + * existing local file, it will be returned untouched. + * @param bool $check_signatures Whether to validate file signatures. Default false. + * @param array $hook_extra Extra arguments to pass to the filter hooks. Default empty array. + * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object. + */ + public function download_package( $package, $check_signatures = false, $hook_extra = array() ) { + + /** + * Filters whether to return the package. + * + * @since 3.7.0 + * @since 5.5.0 Added the `$hook_extra` parameter. + * + * @param bool $reply Whether to bail without returning the package. + * Default false. + * @param string $package The package file name. + * @param WP_Upgrader $this The WP_Upgrader instance. + * @param array $hook_extra Extra arguments passed to hooked filters. + */ + $reply = apply_filters( 'upgrader_pre_download', false, $package, $this, $hook_extra ); + if ( false !== $reply ) { + return $reply; + } + + if ( ! preg_match( '!^(http|https|ftp)://!i', $package ) && file_exists( $package ) ) { // Local file or remote? + return $package; // Must be a local file. + } + + if ( empty( $package ) ) { + return new WP_Error( 'no_package', $this->strings['no_package'] ); + } + + //$this->skin->feedback( 'downloading_package', $package ); + + $download_file = download_url( $package, 300, $check_signatures ); + + if ( is_wp_error( $download_file ) && ! $download_file->get_error_data( 'softfail-filename' ) ) { + return new WP_Error( 'download_failed', $this->strings['download_failed'], $download_file->get_error_message() ); + } + + return $download_file; + } + + /** + * Unpack a compressed package file. + * + * @since 2.8.0 + * + * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. + * + * @param string $package Full path to the package file. + * @param bool $delete_package Optional. Whether to delete the package file after attempting + * to unpack it. Default true. + * @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure. + */ + public function unpack_package( $package, $delete_package = true ) { + global $wp_filesystem; + + //$this->skin->feedback( 'unpack_package' ); + + $upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/'; + + //Clean up contents of upgrade directory beforehand. + $upgrade_files = $wp_filesystem->dirlist( $upgrade_folder ); + if ( ! empty( $upgrade_files ) ) { + foreach ( $upgrade_files as $file ) { + $wp_filesystem->delete( $upgrade_folder . $file['name'], true ); + } + } + + // We need a working directory - Strip off any .tmp or .zip suffixes + $working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' ); + + // Clean up working directory + if ( $wp_filesystem->is_dir( $working_dir ) ) { + $wp_filesystem->delete( $working_dir, true ); + } + + // Unzip package to working directory + $result = unzip_file( $package, $working_dir ); + + // Once extracted, delete the package if required. + if ( $delete_package ) { + unlink( $package ); + } + + if ( is_wp_error( $result ) ) { + $wp_filesystem->delete( $working_dir, true ); + if ( $result->get_error_code() === 'incompatible_archive' ) { + return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() ); + } + + return $result; + } + + return $working_dir; + } + + /** + * Install a package. + * + * Copies the contents of a package form a source directory, and installs them in + * a destination directory. Optionally removes the source. It can also optionally + * clear out the destination folder if it already exists. + * + * @since 2.8.0 + * + * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. + * @global array $wp_theme_directories + * + * @param array|string $args { + * Optional. Array or string of arguments for installing a package. Default empty array. + * + * @type string $source Required path to the package source. Default empty. + * @type string $destination Required path to a folder to install the package in. + * Default empty. + * @type bool $clear_destination Whether to delete any files already in the destination + * folder. Default false. + * @type bool $clear_working Whether to delete the files form the working directory + * after copying to the destination. Default false. + * @type bool $abort_if_destination_exists Whether to abort the installation if + * the destination folder already exists. Default true. + * @type array $hook_extra Extra arguments to pass to the filter hooks called by + * WP_Upgrader::install_package(). Default empty array. + * } + * + * @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure. + */ + public function install_package( $args = array() ) { + global $wp_filesystem, $wp_theme_directories; + + $defaults = array( + 'source' => '', // Please always pass this + 'destination' => '', // and this + 'clear_destination' => false, + 'clear_working' => false, + 'abort_if_destination_exists' => true, + 'hook_extra' => array(), + ); + + $args = wp_parse_args( $args, $defaults ); + + // These were previously extract()'d. + $source = $args['source']; + $destination = $args['destination']; + $clear_destination = $args['clear_destination']; + + // @todo does this need to be replaced? + // wpforms_set_time_limit( 300 ); + + if ( empty( $source ) || empty( $destination ) ) { + return new WP_Error( 'bad_request', $this->strings['bad_request'] ); + } + //$this->skin->feedback( 'installing_package' ); + + /** + * Filter the install response before the installation has started. + * + * Returning a truthy value, or one that could be evaluated as a WP_Error + * will effectively short-circuit the installation, returning that value + * instead. + * + * @since 2.8.0 + * + * @param bool|WP_Error $response Response. + * @param array $hook_extra Extra arguments passed to hooked filters. + */ + $res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] ); + + if ( is_wp_error( $res ) ) { + return $res; + } + + // Retain the Original source and destinations. + $remote_source = $args['source']; + $local_destination = $destination; + + $source_files = array_keys( $wp_filesystem->dirlist( $remote_source ) ); + $remote_destination = $wp_filesystem->find_folder( $local_destination ); + $count_source_files = count( $source_files ); + + // Locate which directory to copy to the new folder, This is based on the actual folder holding the files. + if ( $count_source_files === 1 && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) { // Only one folder? Then we want its contents. + $source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] ); + } elseif ( $count_source_files === 0 ) { + return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] ); // There are no files? + } else { // It's only a single file, the upgrader will use the folder name of this file as the destination folder. Folder name is based on zip filename. + $source = trailingslashit( $args['source'] ); + } + + /** + * Filter the source file location for the upgrade package. + * + * @since 2.8.0 + * @since 4.4.0 The $hook_extra parameter became available. + * + * @param string $source File source location. + * @param string $remote_source Remote file source location. + * @param WP_Upgrader $this WP_Upgrader instance. + * @param array $hook_extra Extra arguments passed to hooked filters. + */ + $source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] ); + + if ( is_wp_error( $source ) ) { + return $source; + } + + // Has the source location changed? If so, we need a new source_files list. + if ( $source !== $remote_source ) { + $source_files = array_keys( $wp_filesystem->dirlist( $source ) ); + } + + /* + * Protection against deleting files in any important base directories. + * Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the + * destination directory (WP_PLUGIN_DIR / wp-content/themes) intending + * to copy the directory into the directory, whilst they pass the source + * as the actual files to copy. + */ + $protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' ); + + if ( is_array( $wp_theme_directories ) ) { + $protected_directories = array_merge( $protected_directories, $wp_theme_directories ); + } + + if ( in_array( $destination, $protected_directories ) ) { + $remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) ); + $destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) ); + } + + if ( $clear_destination ) { + // We're going to clear the destination if there's something there. + $removed = $this->clear_destination( $remote_destination ); + + /** + * Filter whether the upgrader cleared the destination. + * + * @since 2.8.0 + * + * @param mixed $removed Whether the destination was cleared. true on success, WP_Error on failure + * @param string $local_destination The local package destination. + * @param string $remote_destination The remote package destination. + * @param array $hook_extra Extra arguments passed to hooked filters. + */ + $removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] ); + + if ( is_wp_error( $removed ) ) { + return $removed; + } + } elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists( $remote_destination ) ) { + // If we're not clearing the destination folder and something exists there already, Bail. + // But first check to see if there are actually any files in the folder. + $_files = $wp_filesystem->dirlist( $remote_destination ); + + if ( ! empty( $_files ) ) { + $wp_filesystem->delete( $remote_source, true ); // Clear out the source files. + + return new WP_Error( 'folder_exists', $this->strings['folder_exists'], $remote_destination ); + } + } + + // Create destination if needed. + if ( ! $wp_filesystem->exists( $remote_destination ) ) { + if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) { + return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination ); + } + } + + // Copy new version of item into place. + $result = copy_dir( $source, $remote_destination ); + + if ( is_wp_error( $result ) ) { + if ( $args['clear_working'] ) { + $wp_filesystem->delete( $remote_source, true ); + } + + return $result; + } + + // Clear the Working folder? + if ( $args['clear_working'] ) { + $wp_filesystem->delete( $remote_source, true ); + } + + $destination_name = basename( str_replace( $local_destination, '', $destination ) ); + + if ( $destination_name === '.' ) { + $destination_name = ''; + } + + $this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' ); + + /** + * Filter the installation response after the installation has finished. + * + * @since 2.8.0 + * + * @param bool $response Installation response. + * @param array $hook_extra Extra arguments passed to hooked filters. + * @param array $result Installation result data. + */ + $res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result ); + + if ( is_wp_error( $res ) ) { + $this->result = $res; + + return $res; + } + + // Bombard the calling function will all the info which we've just used. + return $this->result; + } + + /** + * Install a plugin package. + * + * @since 1.6.3 + * + * @param string $package The full local path or URI of the package. + * @param array $args Optional. Other arguments for installing a plugin package. Default empty array. + * + * @return bool|\WP_Error True if the installation was successful, false or a WP_Error otherwise. + */ + public function install( $package, $args = array() ) { + + $result = parent::install( $package, $args ); + if ( true === $result ) { + do_action( 'edd_plugin_installed', $package ); + } + + return $result; + } +} diff --git a/src/Admin/Installers/PluginSilentUpgraderSkin.php b/src/Admin/Installers/PluginSilentUpgraderSkin.php new file mode 100644 index 00000000000..a3263df669d --- /dev/null +++ b/src/Admin/Installers/PluginSilentUpgraderSkin.php @@ -0,0 +1,57 @@ +get_target_position( $submenu, $target ); + if ( is_null( $target_position ) ) { + return; + } + + // Prepare an HTML class. + // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited + if ( isset( $submenu['edit.php?post_type=download'][ $target_position ][4] ) ) { + $submenu['edit.php?post_type=download'][ $target_position ][4] .= " {$class}"; + } else { + $submenu['edit.php?post_type=download'][ $target_position ][] = $class; + } + } + + /** + * Gets the target position/key in the submenu. + * + * @since 3.1.1 + * @param array $submenu + * @param string $target + * @return null|int + */ + private function get_target_position( $submenu, $target ) { + return key( + array_filter( + $submenu['edit.php?post_type=download'], + static function( $item ) use ( $target ) { + + if ( $target === $item[2] ) { + return true; + }; + + return false !== strpos( $item[2], $target ); + } + ) + ); + } +} diff --git a/src/Admin/Onboarding/Ajax.php b/src/Admin/Onboarding/Ajax.php new file mode 100644 index 00000000000..e8b86da347d --- /dev/null +++ b/src/Admin/Onboarding/Ajax.php @@ -0,0 +1,206 @@ + 'ajax_save_telemetry_settings', + 'wp_ajax_edd_onboarding_create_product' => 'create_product', + 'wp_ajax_edd_onboarding_started' => 'ajax_onboarding_started', + 'wp_ajax_edd_onboarding_completed' => 'ajax_onboarding_completed', + 'wp_ajax_edd_onboarding_skipped' => 'ajax_onboarding_skipped', + 'wp_ajax_edds_stripe_connect_account_info' => array( 'disconnect_url', 5 ), + ); + } + + /** + * Ajax callback for saving telemetry option. + * + * @since 3.1.1 + */ + public function ajax_save_telemetry_settings() { + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'edd_onboarding_wizard' ) ) { + exit(); + } + + if ( ! current_user_can( 'manage_shop_settings' ) ) { + exit; + } + + if ( isset( $_REQUEST['telemetry_toggle'] ) ) { + edd_update_option( 'allow_tracking', filter_var( $_REQUEST['telemetry_toggle'], FILTER_VALIDATE_BOOLEAN ) ); + } + + update_option( 'edd_tracking_notice', true ); + exit; + } + + /** + * Ajax callback for creating a product. + * + * @since 3.1.1 + */ + public function create_product() { + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'edd_onboarding_wizard' ) ) { + exit(); + } + + if ( ! current_user_can( 'edit_products' ) ) { + return; + } + + $response = array( 'success' => false ); + + // Prepare product post details. + $product = array( + 'post_title' => wp_strip_all_tags( $_REQUEST['product_title'] ), + 'post_status' => 'draft', + 'post_type' => 'download', + ); + + // Insert the product into the database. + $post_id = wp_insert_post( $product ); + if ( $post_id ) { + $post = get_post( $post_id ); + + // Save meta fields. + edd_download_meta_box_fields_save( $post_id, $post ); + + // Set featured image. + if ( ! empty( $_REQUEST['product_image_id'] ) ) { + set_post_thumbnail( $post_id, absint( $_REQUEST['product_image_id'] ) ); + } + + $response['success'] = true; + $response['redirect_url'] = get_edit_post_link( $post_id ); + } + + wp_send_json( $response ); + exit; + } + + /** + * Ajax callback when user started the Onboarding flow. + * + * @since 3.1.1 + */ + public function ajax_onboarding_started() { + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'edd_onboarding_wizard' ) ) { + exit; + } + + if ( get_option( 'edd_onboarding_completed' ) ) { + exit; + } + + if ( ! current_user_can( 'manage_options' ) ) { + exit; + } + + update_option( 'edd_onboarding_started', current_time( 'Y-m-d H:i:s' ), false ); + exit; + } + + /** + * Ajax callback for completing the Onboarding. + * + * @since 3.1.1 + */ + public function ajax_onboarding_completed() { + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'edd_onboarding_wizard' ) ) { + exit; + } + + if ( get_option( 'edd_onboarding_completed' ) ) { + exit; + } + + if ( ! current_user_can( 'manage_options' ) ) { + exit; + } + + update_option( 'edd_onboarding_completed', current_time( 'Y-m-d H:i:s' ), false ); + update_option( 'edd_tracking_notice', true ); + + $this->clean_onboarding_options(); + + exit; + } + + /** + * Ajax callback for skipping the Onboarding. + * + * @since 3.1.1 + */ + public function ajax_onboarding_skipped() { + if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'edd_onboarding_wizard' ) ) { + exit(); + } + + if ( get_option( 'edd_onboarding_completed' ) ) { + exit; + } + + if ( ! current_user_can( 'manage_options' ) ) { + exit; + } + + update_option( 'edd_onboarding_completed', current_time( 'Y-m-d H:i:s' ), false ); + + $this->clean_onboarding_options(); + exit; + } + + /** + * Filters the Stripe disconnect URL. + * This has to be hooked into the ajax action before the main ajax work is run. + * + * @since 3.1.1 + * @return void + */ + public function disconnect_url() { + add_filter( + 'edds_stripe_connect_disconnect_url', + function( $url ) { + if ( empty( $_REQUEST['onboardingWizard'] ) ) { + return $url; + } + $stripe_connect_disconnect_url = edd_get_admin_url( + array( + 'page' => 'edd-onboarding-wizard', + 'current_step' => 'payment_methods', + 'edds-stripe-disconnect' => true, + ) + ); + return wp_nonce_url( $stripe_connect_disconnect_url, 'edds-stripe-connect-disconnect' ); + }, + 15 + ); + } + + /** + * Clean onboarding options. + * + * @since 3.1.1 + */ + private function clean_onboarding_options() { + delete_option( 'edd_onboarding_latest_step' ); + } +} diff --git a/src/Admin/Onboarding/Helpers.php b/src/Admin/Onboarding/Helpers.php new file mode 100644 index 00000000000..2aec0f4413a --- /dev/null +++ b/src/Admin/Onboarding/Helpers.php @@ -0,0 +1,76 @@ + $section ) { + if ( ! empty( $wp_settings_fields[ $section_name ][ $section_name ] ) ) { + foreach ( $section as $field ) { + $field_name = "edd_settings[{$field}]"; + if ( array_key_exists( $field_name, $wp_settings_fields[ $section_name ][ $section_name ] ) ) { + $extracted_fields[ $field_name ] = $wp_settings_fields[ $section_name ][ $section_name ][ $field_name ]; + } + } + } + } + + return $extracted_fields; + } + + /** + * Get fields HTML. + * + * @param array $screen_settings Fields. + * + * @since 3.1.1 + */ + public function settings_html( $screen_settings ) { + foreach ( $screen_settings as $field ) : + $class = ''; + + if ( ! empty( $field['args']['class'] ) ) { + $class = ' class="' . esc_attr( $field['args']['class'] ) . '"'; + } + + echo ""; + if ( ! empty( $field['args']['label_for'] ) ) { + echo ''; + } else { + echo '' . $field['title'] . ''; + } + + echo ''; + if ( ! empty( $field['args']['std'] ) ) { + $field['args']['allow_blank'] = false; + } + call_user_func( $field['callback'], $field['args'] ); + echo ''; + echo ''; + endforeach; + } +} diff --git a/src/Admin/Onboarding/Notice.php b/src/Admin/Onboarding/Notice.php new file mode 100644 index 00000000000..b774d293eb8 --- /dev/null +++ b/src/Admin/Onboarding/Notice.php @@ -0,0 +1,66 @@ + +

    +

    Tools, and clicking on the \'Restart Setup Wizard\' button.', 'easy-digital-downloads' ); ?>

    +
    + + +
    + array( + 'business_settings', + 'entity_name', + 'entity_type', + 'business_address', + 'business_address_2', + 'business_city', + 'business_postal_code', + 'base_country', + 'base_state', + ), + 'edd_settings_general_currency' => array( + 'currency_settings', + 'currency', + 'currency_position', + 'thousands_separator', + 'decimal_separator', + ), + ); + ?> +
    + + + + settings_html( $this->extract_settings_fields( $sections ) ); ?> + + + + +
    + array( + 'email_logo', + 'from_name', + 'from_email', + ), + ); + + $sections_purchase_receipt = array( + 'edd_settings_emails_purchase_receipts' => array( + 'purchase_subject', + ), + ); + + ?> +
    + + + + settings_html( $this->extract_settings_fields( $sections ) ); ?> + settings_html( $this->extract_settings_fields( $sections_purchase_receipt ) ); ?> + + + + + + + + + + + +
    + +
    + + + + +
      +
    1. +
    2. +
    3. +
    4. +
    5. +
    6. +
    + +
    + +
    + +
    + +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + get_plugins(); + + ?> +
    +
    + +
    +

    +
    + + +

    + + + +

    + +

    + +

    + +
    +
    + +
    + + telemetry(); ?> + +
    +

    +
    +
    + + + + __( 'Essential eCommerce Features', 'easy-digital-downloads' ), + 'description' => __( 'Get all the essential eCommerce features to sell digital products with WordPress.', 'easy-digital-downloads' ), + 'prechecked' => true, + 'readonly' => true, + 'disabled' => true, + 'plugin_name' => __( 'Easy Digital Downloads', 'easy-digital-downloads' ), + 'plugin_file' => '', + 'plugin_zip' => '', + 'plugin_url' => '', + 'action' => '', + ), + array( + 'name' => __( 'Optimize Checkout', 'easy-digital-downloads' ), + 'description' => __( 'Improve the checkout experience by auto-creating user accounts for new customers.', 'easy-digital-downloads' ), + 'prechecked' => true, + 'plugin_name' => 'Auto Register', + 'plugin_file' => 'edd-auto-register/edd-auto-register.php', + 'plugin_zip' => 'https://downloads.wordpress.org/plugin/edd-auto-register.zip', + 'plugin_url' => 'https://wordpress.org/plugins/edd-auto-register', + 'action' => 'install', + ), + array( + 'name' => __( 'Reliable Email Delivery', 'easy-digital-downloads' ), + 'description' => __( 'Email deliverability is one of the most important services for an eCommerce store. Don’t leave your customers in the dark.', 'easy-digital-downloads' ), + 'prechecked' => true, + 'plugin_name' => 'WP Mail SMTP', + 'plugin_file' => 'wp-mail-smtp/wp_mail_smtp.php', + 'plugin_zip' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip', + 'plugin_url' => 'https://wordpress.org/plugins/wp-mail-smtp/', + 'action' => 'install', + 'conflicts' => array( + 'wp-mail-smtp-pro/wp_mail_smtp.php', + ), + ), + array( + 'name' => __( 'Analytics Tools', 'easy-digital-downloads' ), + 'description' => __( 'Get the #1 analytics plugin to see useful information about your visitors right inside your WordPress dashboard.', 'easy-digital-downloads' ), + 'prechecked' => true, + 'plugin_name' => 'MonsterInsights', + 'plugin_file' => 'google-analytics-for-wordpress/googleanalytics.php', + 'plugin_zip' => 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip', + 'plugin_url' => 'https://wordpress.org/plugins/google-analytics-for-wordpress/', + 'action' => 'install', + 'conflicts' => array( + 'google-analytics-premium/googleanalytics-premium.php', + 'google-analytics-dashboard-for-wp/gadwp.php', + 'exactmetrics-premium/exactmetrics-premium.php', + 'wp-analytify/wp-analytify.php', + 'ga-google-analytics/ga-google-analytics.php', + ), + ), + array( + 'name' => __( 'SEO', 'easy-digital-downloads' ), + 'description' => __( 'Get the tools used by millions of smart business owners to analyze and optimize their store’s traffic with SEO.', 'easy-digital-downloads' ), + 'prechecked' => true, + 'plugin_name' => 'All In One SEO Pack', + 'plugin_file' => 'all-in-one-seo-pack/all_in_one_seo_pack.php', + 'plugin_zip' => 'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.zip', + 'plugin_url' => 'https://wordpress.org/plugins/all-in-one-seo-pack/', + 'action' => 'install', + 'conflicts' => array( + 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php', + 'wordpress-seo/wp-seo.php', + 'wordpress-seo-premium/wp-seo-premium.php', + ), + ), + ); + + // Check the state of the plugins in the current environment. + foreach ( $available_plugins as $key => $plugin ) { + + // If the plugin has a conflict with another plugin, remove it from the list. + if ( ! empty( $plugin['conflicts'] ) ) { + foreach ( $plugin['conflicts'] as $conflicting_slug ) { + if ( is_plugin_active( $conflicting_slug ) ) { + $available_plugins[ $key ]['disabled'] = true; + $available_plugins[ $key ]['prechecked'] = true; + $available_plugins[ $key ]['readonly'] = true; + $available_plugins[ $key ]['has_feature'] = true; + break; + } + } + } + + if ( isset( $plugin['disabled'] ) && $plugin['disabled'] ) { + continue; + } + + // If plugin is already installed, set the action to activate. + if ( $extension_manager->is_plugin_installed( $plugin['plugin_file'] ) ) { + $available_plugins[ $key ]['action'] = 'activate'; + } + + // If this plugin is activated, disable the checkbox on the front. + if ( is_plugin_active( $plugin['plugin_file'] ) ) { + $available_plugins[ $key ]['prechecked'] = true; + $available_plugins[ $key ]['disabled'] = true; + $available_plugins[ $key ]['action'] = ''; + $available_plugins[ $key ]['active'] = true; + } + } + + return $available_plugins; + } + + /** + * Outputs the telemetry checkbox. + * + * @since 3.1.1.3 + * @return void + */ + private function telemetry() { + if ( edd_is_pro() ) { + return; + } + ?> +
    +

    + +

    + + +
    + +
    +
    + 'restart_onboarding', + ); + } + + /** + * Adds a "tool" to allow users to restart the onboarding wizard. + * + * @since 3.1.1 + * @return void + */ + public function restart_onboarding() { + ?> +
    +

    +
    +

    + +
    +
    + 'redirect', + 'admin_menu' => array( 'add_menu_item', 5 ), + 'wp_ajax_edd_onboarding_load_step' => 'ajax_onboarding_load_step', + 'load-download_page_edd-onboarding-wizard' => 'load_onboarding_wizard', + 'admin_enqueue_scripts' => 'enqueue_onboarding_scripts', + ); + } + + /** + * Maybe redirect to the onboarding wizard. + * + * @since 3.1.1 + * @return void + */ + public function redirect() { + if ( wp_doing_ajax() ) { + return; + } + if ( ! get_transient( 'edd_onboarding_redirect' ) ) { + return; + } + + delete_transient( 'edd_onboarding_redirect' ); + + if ( get_option( 'edd_onboarding_prevent_redirect' ) ) { + return; + } + + if ( isset( $_GET['activate-multi'] ) || is_network_admin() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return; + } + + edd_redirect( + edd_get_admin_url( + array( + 'page' => 'edd-onboarding-wizard', + ) + ) + ); + } + + /** + * Add Onboarding Wizard submenu page. + * + * @since 3.1.1 + */ + public function add_menu_item() { + add_submenu_page( 'edit.php?post_type=download', __( 'Setup', 'easy-digital-downloads' ), __( 'Setup', 'easy-digital-downloads' ), 'manage_shop_settings', 'edd-onboarding-wizard', array( $this, 'onboarding_wizard_sub_page' ) ); + if ( $this->has_onboarding_been_completed() ) { + remove_submenu_page( 'edit.php?post_type=download', 'edd-onboarding-wizard' ); + } + + add_action( 'admin_head', array( $this, 'adjust_menu_item_class' ) ); + } + + /** + * Adds the custom pro menu item class. + * + * @since 3.1.1 + * @return void + */ + public function adjust_menu_item_class() { + new \EDD\Admin\Menu\LinkClass( 'edd-onboarding-wizard', 'edd-onboarding__menu-item' ); + } + + /** + * Determine if we are on Onboarding Wizard screen + * and load all of the neccesarry hooks and actions. + * + * @since 3.1.1 + */ + public function load_onboarding_wizard() { + if ( ! $this->is_wizard() ) { + return; + } + // Hide EDD header. + remove_action( 'admin_notices', 'edd_admin_header', 1 ); + + // Set variables. + $this->onboarding_started = $this->has_onboarding_started(); + $this->set_onboarding_steps(); + $this->set_current_onboarding_step(); + + // We don't want any notices on our screen. + remove_all_actions( 'admin_notices' ); + remove_all_actions( 'all_admin_notices' ); + + // Override Stripe callback urls. + add_filter( 'edds_stripe_connect_url', array( $this, 'update_stripe_connect_url' ), 15 ); + + add_filter( 'edd_pointers', function( $pointers ) { + return array(); + } ); + } + + /** + * Load scripts and styles. + * + * @since 3.1.1 + */ + public function enqueue_onboarding_scripts() { + if ( ! $this->is_wizard() ) { + return; + } + wp_enqueue_style( 'edd-admin-onboarding' ); + wp_enqueue_script( 'edd-admin-onboarding' ); + + wp_enqueue_style( 'edd-extension-manager' ); + wp_enqueue_script( 'edd-extension-manager' ); + + wp_enqueue_media(); + wp_enqueue_editor(); + + if ( array_key_exists( 'payment_methods', $this->onboarding_steps ) ) { + edd_stripe_connect_admin_script( 'download_page_edd-settings' ); + } + + edd_email_tags_inserter_enqueue_scripts(); + } + + /** + * Override Stripe connect url. + * + * @since 3.1.1 + */ + public function update_stripe_connect_url() { + $return_url = edd_get_admin_url( + array( + 'redirect_screen' => 'onboarding-wizard', + ) + ); + + return add_query_arg( + array( + 'live_mode' => (int) ! edd_is_test_mode(), + 'state' => str_pad( wp_rand( wp_rand(), PHP_INT_MAX ), 100, wp_rand(), STR_PAD_BOTH ), + 'customer_site_url' => urlencode( esc_url_raw( $return_url ) ), + ), + 'https://easydigitaldownloads.com/?edd_gateway_connect_init=stripe_connect' + ); + } + + /** + * Set onboarding steps. + * + * @since 3.1.1 + */ + public function set_onboarding_steps() { + $this->onboarding_steps = array( + 'business_info' => array( + 'step_title' => __( 'Business', 'easy-digital-downloads' ), + 'step_headline' => __( 'Tell us a little bit about your business.', 'easy-digital-downloads' ), + 'step_intro' => __( 'Where is your business located? This helps Easy Digital Downloads configure the checkout and receipt templates.', 'easy-digital-downloads' ), + 'step_handler' => 'BusinessInfo', + ), + 'payment_methods' => array( + 'step_title' => __( 'Payment Methods', 'easy-digital-downloads' ), + 'step_headline' => __( 'Start accepting payments today!', 'easy-digital-downloads' ), + 'step_intro' => '', + 'step_handler' => 'PaymentMethods', + ), + 'configure_emails' => array( + 'step_title' => __( 'Emails', 'easy-digital-downloads' ), + 'step_headline' => __( 'Configure your Receipts', 'easy-digital-downloads' ), + 'step_intro' => __( 'Customize the purchase receipt that your customers will receive.', 'easy-digital-downloads' ), + 'step_handler' => 'ConfigureEmails', + ), + 'tools' => array( + 'step_title' => __( 'Tools', 'easy-digital-downloads' ), + 'step_headline' => __( 'Conversion and Optimization tools', 'easy-digital-downloads' ), + 'step_intro' => __( 'We have selected our recommended tools and features to help boost conversions and optimize your digital store.', 'easy-digital-downloads' ), + 'step_handler' => 'Tools', + ), + 'products' => array( + 'step_title' => __( 'Products', 'easy-digital-downloads' ), + 'step_headline' => __( 'What are you going to sell?', 'easy-digital-downloads' ), + 'step_intro' => __( 'Let\'s get started creating your first awesome product.', 'easy-digital-downloads' ), + 'step_handler' => 'Products', + ), + ); + + // If Stripe classes are not available, remove payment methods step. + if ( ! defined( 'EDD_STRIPE_VERSION' ) ) { + unset( $this->onboarding_steps['payment_methods'] ); + } + + // Determine products step intro. + $products = new \WP_Query( + array( + 'post_type' => 'download', + 'posts_per_page' => 1, + 'no_found_rows' => true, + 'fields' => 'ids', + ) + ); + if ( ! empty( $products->posts ) ) { + $this->onboarding_steps['products']['step_intro'] = __( 'Let\'s get started with your next great product.', 'easy-digital-downloads' ); + } + + // Set step index in the array and load ajax handlers. + $index = 1; + foreach ( $this->onboarding_steps as $key => $value ) { + $this->onboarding_steps[ $key ]['step_index'] = $index; + $index++; + } + } + + /** + * Set current onboarding step. + * + * @since 3.1.1 + */ + public function set_current_onboarding_step() { + // If Onboarding hasn't started yet, we force the first default step. + if ( ! $this->onboarding_started ) { + return; + } + + // User is requesting a specific step. + $this->current_step = $this->get_current_step(); + + // If requested step does not exist, abort. + if ( ! isset( $this->onboarding_steps[ $this->current_step ] ) ) { + wp_die( __( 'Unknown Onboarding Step.', 'easy-digital-downloads' ), __( 'Onboarding Wizard', 'easy-digital-downloads' ), 404 ); + } + + $this->current_step_index = $this->onboarding_steps[ $this->current_step ]['step_index']; + if ( $this->has_onboarding_been_completed() ) { + return; + } + update_option( 'edd_onboarding_latest_step', $this->current_step, false ); + } + + /** + * Get previous step. + * + * @since 3.1.1 + */ + public function get_previous_step() { + $internal_step = $this->current_step_index - 2; + $step_keys = array_keys( $this->onboarding_steps ); + if ( isset( $step_keys[ $internal_step ] ) ) { + return $step_keys[ $internal_step ]; + } + + return false; + } + + /** + * Get current step. + * + * @since 3.1.1 + */ + public function get_current_step() { + if ( isset( $_GET['current_step'] ) ) { + return sanitize_key( $_GET['current_step'] ); + } + + return sanitize_key( get_option( 'edd_onboarding_latest_step', $this->current_step ) ); + } + + /** + * Get current step details. + * + * @since 3.1.1 + */ + public function get_current_step_details() { + return $this->onboarding_steps[ $this->get_current_step() ]; + } + + /** + * Get next step. + * + * @since 3.1.1 + */ + public function get_next_step() { + $internal_step = $this->current_step_index; + $step_keys = array_keys( $this->onboarding_steps ); + if ( isset( $step_keys[ $internal_step ] ) ) { + return $step_keys[ $internal_step ]; + } + + return false; + } + + /** + * Get pagination. + * + * @since 3.1.1 + */ + public function get_step_pagination() { + return array( + 'previous' => $this->get_previous_step(), + 'current' => $this->get_current_step(), + 'next' => $this->get_next_step(), + ); + } + + /** + * Onboarding Wizard subpage screen. + * + * @since 3.1.1 + */ + public function onboarding_wizard_sub_page() { + $onboarding_initial_style = ( ! $this->onboarding_started ) ? ' style="display:none;"' : ''; + ?> + +
    + +
    + + + get_welcome_screen(); ?> +
    > +
    + load_step_view(); ?> +
    +
    +
    +
    + onboarding_started ) { + return; + } + + $testimonials = array( + array( + 'name' => 'Joe Casabona', + 'company' => 'How I Built It', + 'content' => 'The problem with many e-commerce platforms to sell online courses is they aren\'t made with only digital goods in mind. EDD doesn\'t have that problem, and as a result their platform is perfectly made for selling my online courses.', + 'avatar' => 'joe.jpg', + 'stars' => 5, + ), + array( + 'name' => 'Nicolas Martin', + 'company' => 'Flea Market Insiders', + 'content' => 'Before EDD\'s Recurring Payments was made available, we were only able to sell one-time subscriptions to our customers. Since implementing recurring payments, we\'ve been able to offer quarterly and yearly subscriptions and subsequently increase our subscriptions revenue by 200%.', + 'avatar' => 'nicolas.jpg', + 'stars' => 5, + ), + array( + 'name' => 'Bob Dunn', + 'company' => 'BobWP', + 'content' => 'If anyone asks me what they should use for downloadable products on their WordPress site, it\'s a no-brainer as far as EDD goes.', + 'avatar' => 'bob.jpg', + 'stars' => 5, + ), + ); + ?> +
    +
    +

    👋

    +

    + +

    +
    + +
    +
    + " /> +
    +
    +

    +
    + + + + + + + +
    +
    +
    + +
    +
    +
    + get_current_step_details(); + $pagination = $this->get_step_pagination(); + $step_class_name = 'EDD\\Admin\\Onboarding\\Steps\\' . $current_step_details['step_handler']; + $step_class = new $step_class_name(); + ?> + + + + + +
    +
      + onboarding_steps as $step_key => $step ) : + $step_url = edd_get_admin_url( + array( + 'post_type' => 'download', + 'page' => 'edd-onboarding-wizard', + 'current_step' => sanitize_key( $step_key ), + ) + ); + + $classes = array(); + // Determine if this step is active. + if ( $step['step_index'] === $this->current_step_index ) { + $classes[] = 'active-step'; + } + // Determine if this step is completed. + if ( $this->current_step_index > $step['step_index'] ) { + $classes[] = 'completed-step'; + } + ?> +
    • + + + + +
    • + +
    +
    + +
    + +
    +

    +

    + step_html(); + ?> +
    + +
    +
    + +
    + + load_onboarding_wizard(); + + // Now load the step. + $this->load_step_view(); + exit; + } + + /** + * Whether the current request is the onboarding wizard. + * + * @since 3.1.1 + * @return bool + */ + private function is_wizard() { + // Abort if we are not requesting Onboarding Wizard. + if ( ! empty( $_REQUEST['page'] ) && 'edd-onboarding-wizard' !== wp_unslash( $_REQUEST['page'] ) ) { + return false; + } + + // Stripe calls are marked with onboardingWizard request parameter. + if ( empty( $_REQUEST['page'] ) && empty( $_REQUEST['onboardingWizard'] ) ) { + return false; + } + + return true; + } + + /** + * Whether the onboarding wizard has started. + * Returns true if the onboarding has been marked as completed, too. + * + * @since 3.1.1 + * @return bool + */ + private function has_onboarding_started() { + if ( $this->onboarding_started ) { + return true; + } + + return get_option( 'edd_onboarding_started', false ) || $this->has_onboarding_been_completed(); + } + + /** + * Whether the onboarding wizard has been completed. + * + * @since 3.1.1 + * @return bool + */ + private function has_onboarding_been_completed() { + if ( $this->onboarding_completed ) { + return true; + } + + return get_option( 'edd_onboarding_completed', false ); + } + + /** + * Gets the classes for the "Close and Exit without Saving" button. + * If onboarding has already completed, we don't need to show the confirmation again. + * + * @since 3.1.1 + * @return array + */ + private function get_close_exit_button_classes() { + $classes = array( 'button', 'button-link' ); + if ( ! $this->has_onboarding_been_completed() ) { + $classes[] = 'edd-promo-notice__trigger'; + } else { + $classes[] = 'edd-onboarding__dismiss'; + } + + return $classes; + } +} diff --git a/src/Admin/PassHandler/Actions.php b/src/Admin/PassHandler/Actions.php new file mode 100644 index 00000000000..3581deb1315 --- /dev/null +++ b/src/Admin/PassHandler/Actions.php @@ -0,0 +1,95 @@ +handler = $handler; + } + + /** + * Returns an array of events that this subscriber wants to listen to. + * + * @return array + */ + public static function get_subscribed_events() { + return array( + 'edd_refresh_pass_status' => 'refresh', + ); + } + + /** + * When the "refresh" button is clicked, performs a remote license request to check the pass status. + * Sets a ten minute transient to avoid a double check (this is running twice for some reason) and excessive clicking. + * + * @since 3.1.1 + * @return void + */ + public function refresh() { + if ( ! $this->can_refresh() ) { + edd_redirect( $this->handler->get_extensions_url() ); + } + + $pass_data = $this->handler->get_pro_license(); + if ( empty( $pass_data->key ) ) { + edd_redirect( + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'edd-message' => 'missing-pass-key', + ) + ) + ); + } + + $api_params = array( + 'edd_action' => 'check_license', + 'license' => $pass_data->key, + 'item_id' => $pass_data->pass_id, + ); + + $license_data = $this->handler->remote_request( $api_params ); + if ( empty( $license_data->success ) ) { + edd_redirect( $this->handler->get_extensions_url() ); + } + + $pass_manager = new \EDD\Admin\Pass_Manager(); + $pass_manager->maybe_set_pass_flag( $pass_data->key, $license_data ); + $this->handler->update_pro_license( $license_data ); + set_transient( 'edd_pass_refreshed', true, 10 * MINUTE_IN_SECONDS ); + + edd_redirect( $this->handler->get_extensions_url() ); + } + + /** + * Check if the current user can refresh the pass status. + * + * @return bool + */ + private function can_refresh() { + if ( ! current_user_can( 'manage_options' ) ) { + return false; + } + if ( get_transient( 'edd_pass_refreshed' ) ) { + return false; + } + + return true; + } +} diff --git a/src/Admin/PassHandler/Ajax.php b/src/Admin/PassHandler/Ajax.php new file mode 100644 index 00000000000..99220e530a0 --- /dev/null +++ b/src/Admin/PassHandler/Ajax.php @@ -0,0 +1,198 @@ +handler = $handler; + $this->pass_manager = new Pass_Manager(); + } + + /** + * Returns an array of events that this subscriber wants to listen to. + * + * @return array + */ + public static function get_subscribed_events() { + return array( + 'wp_ajax_edd_verify_pass' => 'verify', + 'wp_ajax_edd_deactivate_pass' => 'deactivate', + 'wp_ajax_edd_delete_pass' => 'delete', + ); + } + + /** + * Attempt to verify a pass license. + * + * @since 3.1.1 + * @return void + */ + public function verify() { + if ( ! $this->can_manage_pass() ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'You do not have permission to manage this pass.', 'easy-digital-downloads' ) ), + ) + ); + } + + $license_key = ! empty( $_POST['license'] ) ? sanitize_text_field( $_POST['license'] ) : false; + if ( ! $license_key ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'Please enter a license key.', 'easy-digital-downloads' ) ), + ) + ); + } + + wp_send_json_success( $this->get_verification_response( $license_key ) ); + } + + /** + * Gets the array of response parameters for a successful license key activation. + * + * @since 3.1.1 + * @param string $license_key + * @return array + */ + private function get_verification_response( $license_key ) { + + $oth = hash( 'sha512', wp_rand() ); + $hashed_oth = hash_hmac( 'sha512', $oth, wp_salt() ); + $endpoint = admin_url( 'admin-ajax.php' ); + $redirect = edd_get_admin_url( array( 'page' => 'edd-settings' ) ); + + update_option( 'edd_connect_token', $oth ); + + $url = add_query_arg( + array( + 'key' => $license_key, + 'oth' => $hashed_oth, + 'endpoint' => $endpoint, + 'version' => EDD_VERSION, + 'siteurl' => admin_url(), + 'homeurl' => network_home_url(), + 'redirect' => rawurldecode( base64_encode( $redirect ) ), // phpcs:ignore + ), + 'https://upgrade.easydigitaldownloads.com' + ); + + return array( + 'message' => false, + 'actions' => '', + 'url' => $url, + 'back_url' => add_query_arg( + array( + 'action' => 'edd_connect', + 'oth' => $hashed_oth, + ), + $endpoint + ), + ); + } + + /** + * Attempt to deactivate a pass license. + * + * @since 3.1.1 + * @return void + */ + public function deactivate() { + if ( ! $this->can_manage_pass() ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'You do not have permission to manage this pass.', 'easy-digital-downloads' ) ), + ) + ); + } + + $pass_data = $this->handler->get_pro_license(); + $api_params = array( + 'edd_action' => 'deactivate_license', + 'license' => $pass_data->key, + 'item_id' => urlencode( $pass_data->pass_id ), + ); + $license_data = $this->handler->remote_request( $api_params ); + + $this->handler->update_pro_license( $license_data ); + $this->pass_manager->maybe_remove_pass_flag( $pass_data->key ); + + wp_send_json_success( + array( + 'message' => wpautop( __( 'Your pass was successfully deactivated.', 'easy-digital-downloads' ) ), + 'actions' => $this->handler->get_pass_actions( 'inactive', $pass_data->key ), + ) + ); + } + + /** + * Deletes a pass key and the related option. + * + * @since 3.1.1 + * @return void + */ + public function delete() { + if ( ! $this->can_manage_pass( 'edd_passhandler-delete' ) ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'You do not have permission to manage this pass.', 'easy-digital-downloads' ) ), + ) + ); + } + + $license = $this->handler->get_pro_license(); + $license->delete(); + + wp_send_json_success( + array( + 'message' => wpautop( __( 'Pass key deleted.', 'easy-digital-downloads' ) ), + ) + ); + } + + /** + * Whether the current user can manage the pass. + * Checks the user capabilities, tokenizer, and nonce. + * + * @since 3.1.1 + * @param string $nonce The name of the specific nonce to validate. + * @return bool + */ + protected function can_manage_pass( $nonce = 'edd_passhandler' ) { + if ( ! current_user_can( 'manage_options' ) ) { + return false; + } + $token = isset( $_POST['token'] ) ? sanitize_text_field( $_POST['token'] ) : ''; + $timestamp = isset( $_POST['timestamp'] ) ? sanitize_text_field( $_POST['timestamp'] ) : ''; + + if ( empty( $timestamp ) || empty( $token ) ) { + return false; + } + + return \EDD\Utils\Tokenizer::is_token_valid( $token, $timestamp ) && wp_verify_nonce( $_POST['nonce'], $nonce ); + } +} diff --git a/src/Admin/PassHandler/Cron.php b/src/Admin/PassHandler/Cron.php new file mode 100644 index 00000000000..cb46ac63df4 --- /dev/null +++ b/src/Admin/PassHandler/Cron.php @@ -0,0 +1,66 @@ + 'weekly_license_check', + ); + } + + /** + * Check if license key is valid once per week + * + * @since 3.1.1 + * @return void + */ + public function weekly_license_check() { + if ( ! edd_doing_cron() ) { + return; + } + + $handler = new \EDD\Admin\PassHandler\Handler(); + $license = $handler->get_pro_license(); + if ( empty( $license->key ) ) { + return; + } + + // data to send in our API request + $api_params = array( + 'edd_action' => 'check_license', + 'license' => $license->key, + 'item_id' => $license->item_id, + 'item_name' => $license->item_name, + 'pass_id' => $license->item_id, + 'url' => network_home_url(), + ); + + $api_handler = new \EDD\Licensing\API(); + $license_data = $api_handler->make_request( $api_params ); + if ( ! $license_data ) { + return false; + } + + $pass_manager = new \EDD\Admin\Pass_Manager(); + $pass_manager->maybe_set_pass_flag( $license->key, $license_data ); + $handler->update_pro_license( $license_data ); + } +} diff --git a/src/Admin/PassHandler/Handler.php b/src/Admin/PassHandler/Handler.php new file mode 100644 index 00000000000..cfb632bd0fb --- /dev/null +++ b/src/Admin/PassHandler/Handler.php @@ -0,0 +1,176 @@ +get_pro_license(); + + return $license->save( $license_data ); + } + + /** + * Gets the button for the pass field. + * + * @since 3.1.1 + * @param string $status The pass status. + * @param string $key The license key. + * @param bool $echo Whether to echo the button. + * @return string + */ + public function get_pass_actions( $status, $key = '', $echo = false ) { + $button = $this->get_button_args( $status, $key ); + $timestamp = time(); + if ( ! $echo ) { + ob_start(); + } + ?> +
    + + + + do_extensions_link(); + } + ?> +
    + 'deactivate', + 'label' => __( 'Deactivate', 'easy-digital-downloads' ), + 'class' => 'secondary', + ); + } + + if ( edd_is_pro() ) { + return array( + 'action' => 'activate', + 'label' => __( 'Activate License', 'easy-digital-downloads' ), + 'class' => 'primary', + ); + } + + return array( + 'action' => 'verify', + 'label' => __( 'Verify License Key', 'easy-digital-downloads' ), + 'class' => 'primary', + ); + } + + /** + * Prints the link to the extensions screen. + * + * @since 3.1.1 + * @return string + */ + private function do_extensions_link() { + printf( + '%s', + esc_url( $this->get_extensions_url() ), + esc_html__( 'View Extensions', 'easy-digital-downloads' ) + ); + } + + /** + * Gets the extensions screen URL. + * + * @return string + */ + public function get_extensions_url() { + return edd_get_admin_url( + array( + 'page' => 'edd-addons', + ) + ); + } + + /** + * Makes the remote request to activate/deactivate a license key. + * + * @since 3.1.1 + * @param array $api_params + * @return stdClass|void + */ + public function remote_request( $api_params ) { + $api_params = wp_parse_args( + $api_params, + array( + 'url' => network_home_url(), + ) + ); + $api = new \EDD\Licensing\API(); + $response = $api->make_request( $api_params ); + + // Make sure there are no errors + if ( ! $response ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'We could not reach the EDD server.', 'easy-digital-downloads' ) ), + ) + ); + } + + return $response; + } +} diff --git a/src/Admin/PassHandler/Settings.php b/src/Admin/PassHandler/Settings.php new file mode 100644 index 00000000000..7a1793c04e4 --- /dev/null +++ b/src/Admin/PassHandler/Settings.php @@ -0,0 +1,194 @@ +handler = $handler; + } + + /** + * Returns an array of events that this subscriber wants to listen to. + * + * @since 3.1.1 + * @return array + */ + public static function get_subscribed_events() { + return array( + 'edd_settings_tab_top_general_main' => 'do_pass_field', + 'admin_enqueue_scripts' => 'register_assets', + ); + } + + /** + * Outputs the EDD pass license field on the main EDD settings screen. + * + * @since 3.1.1 + * @return void + */ + public function do_pass_field() { + $pro_license = $this->handler->get_pro_license(); + $license_key = $pro_license->key; + if ( empty( $pro_license->key ) ) { + $pass_manager = new \EDD\Admin\Pass_Manager(); + if ( ! empty( $pass_manager->highest_license_key ) ) { + $license_key = $pass_manager->highest_license_key; + } + } + $this->enqueue(); + ?> +

    get_heading_text() ); ?>

    + + + + + + + + + __( 'Verifying', 'easy-digital-downloads' ), + 'activating' => __( 'Activating', 'easy-digital-downloads' ), + 'deactivating' => __( 'Deactivating', 'easy-digital-downloads' ), + 'verify_loader' => __( 'Just a moment while we connect your site and upgrade you to (Pro).', 'easy-digital-downloads' ), + ) + ); + } + + /** + * Enqueues the pass handler script/style. + * + * @since 3.1.1 + * @return void + */ + public function enqueue() { + wp_enqueue_style( 'edd-pass-handler' ); + wp_enqueue_script( 'edd-pass-handler' ); + } + + /** + * Gets the heading text for the pass key field. + * + * @since 3.1.1 + * @return string + */ + private function get_heading_text() { + return edd_is_pro() ? + __( 'Easy Digital Downloads (Pro) Key', 'easy-digital-downloads' ) : + __( 'Go Pro With Easy Digital Downloads', 'easy-digital-downloads' ); + } + + /** + * Show the free message to users without active passes. + * + * @since 3.1.1 + * @param array $pro_license + * @return void + */ + private function show_free_message( $pro_license ) { + if ( edd_is_pro() ) { + return; + } + ?> +
    +

    + + +

    +

    + 'settings-general', + 'utm_content' => 'upgrade-to-pro', + ) + ); + echo wp_kses_post( + sprintf( + /* translators: 1. opening link tag; do not translate; 2. closing link tag; do not translate. */ + __( 'To unlock more features, consider %1$supgrading to Pro%2$s.', 'easy-digital-downloads' ), + '', + '' + ) + ); + ?> +

    +

    +

    +
    + 10, + self::EXTENDED_PASS_ID => 20, + self::PROFESSIONAL_PASS_ID => 30, + self::ALL_ACCESS_PASS_ID => 40, + self::ALL_ACCESS_PASS_LIFETIME_ID => 50, + ); + + /** + * The base category assigned to each pass. + * + * @var int[] + */ + public $categories = array( + self::PERSONAL_PASS_ID => 2166, + self::EXTENDED_PASS_ID => 2165, + self::PROFESSIONAL_PASS_ID => 2164, + ); + + /** + * The pro license. + * + * @since 3.1.1 + * @var EDD\Licensing\License + */ + private $pro_license; + + /** + * Pass_Manager constructor. + */ + public function __construct() { + $this->pro_license = $this->get_pro_license(); + if ( ! empty( $this->pro_license->license ) && 'valid' === $this->pro_license->license ) { + $this->highest_license_key = $this->pro_license->key; + $this->highest_pass_id = $this->get_pass_id_from_pro_license(); + if ( $this->highest_pass_id ) { + $this->has_pass_data = true; + } + } else { + // Set up the highest pass data. + $pass_data = get_option( 'edd_pass_licenses' ); + if ( false !== $pass_data ) { + $this->pass_data = json_decode( $pass_data, true ); + $this->has_pass_data = true; + } + $this->set_highest_pass_data(); + } + + $this->number_license_keys = count( \EDD\Extensions\get_licensed_extension_slugs() ); + } + + /** + * Gets the highest pass and defines its data. + * + * @since 2.11.4 + * @return void + */ + private function set_highest_pass_data() { + + if ( ! $this->has_pass_data || ! is_array( $this->pass_data ) ) { + return; + } + + $highest_license_key = null; + $highest_pass_id = null; + + foreach ( $this->pass_data as $license_key => $pass_data ) { + /* + * If this pass was last verified more than 2 months ago, we're not using it. + * This ensures we never deal with a "stale" record for a pass that's no longer + * actually activated, but still exists in our DB array for some reason. + * + * Our cron job should always be updating with active data once per week. + */ + if ( empty( $pass_data['time_checked'] ) || strtotime( '-2 months' ) > $pass_data['time_checked'] ) { + continue; + } + + // We need a pass ID. + if ( empty( $pass_data['pass_id'] ) ) { + continue; + } + + // If we don't yet have a "highest pass", then this one is it automatically. + if ( empty( $highest_pass_id ) ) { + $highest_license_key = $license_key; + $highest_pass_id = intval( $pass_data['pass_id'] ); + continue; + } + + // Otherwise, this pass only takes over the highest pass if it's actually higher. + if ( self::pass_compare( (int) $pass_data['pass_id'], $highest_pass_id, '>' ) ) { + $highest_license_key = $license_key; + $highest_pass_id = intval( $pass_data['pass_id'] ); + } + } + + $this->highest_license_key = $highest_license_key; + $this->highest_pass_id = $highest_pass_id; + } + + /** + * Whether or not a pass is activated. + * + * @since 2.10.6 + * + * @return bool + */ + public function has_pass() { + return ! empty( $this->highest_pass_id ); + } + + /** + * If this is a "free install". That means there are no à la carte or pass licenses activated. + * + * @since 2.11.4 + * + * @return bool + */ + public function isFree() { + return 0 === $this->number_license_keys && empty( $this->highest_pass_id ); + } + + /** + * If this is a "pro install". This means they have the pro version of EDD installed and a valid pass key. + * To check only whether there is an active pass, use `has_pass` instead. + * + * @since 3.1 + * + * @return bool + */ + public static function isPro() { + if ( ! edd_is_pro() ) { + return false; + } + $license = ( new self() )->pro_license; + + return $license->key && 'valid' === $license->license; + } + + /** + * Gets the pro license object. + * + * @since 3.1.1 + * @return EDD\Licensing\License + */ + private function get_pro_license() { + return new \EDD\Licensing\License( 'pro' ); + } + + /** + * If this site has an individual product license active (à la carte), but no pass active. + * + * @since 2.11.4 + * + * @return bool + */ + public function hasIndividualLicense() { + return ! $this->isFree() && ! $this->has_pass(); + } + + /** + * If this site has a Personal Pass active. + * + * @since 2.11.4 + * + * @return bool + */ + public function hasPersonalPass() { + try { + return self::pass_compare( $this->highest_pass_id, self::PERSONAL_PASS_ID, '=' ); + } catch ( \Exception $e ) { + return false; + } + } + + /** + * If this site has an Extended Pass active. + * + * @since 2.11.4 + * + * @return bool + */ + public function hasExtendedPass() { + try { + return self::pass_compare( $this->highest_pass_id, self::EXTENDED_PASS_ID, '=' ); + } catch ( \Exception $e ) { + return false; + } + } + + /** + * If this site has a Professional Pass active. + * + * @since 2.11.4 + * + * @return bool + */ + public function hasProfessionalPass() { + try { + return self::pass_compare( $this->highest_pass_id, self::PROFESSIONAL_PASS_ID, '=' ); + } catch ( \Exception $e ) { + return false; + } + } + + /** + * If this site has an All Access Pass active. + * Note: This uses >= to account for both All Access and lifetime All Access. + * + * @since 2.11.4 + * + * @return bool + */ + public function hasAllAccessPass() { + try { + return self::pass_compare( $this->highest_pass_id, self::ALL_ACCESS_PASS_ID, '>=' ); + } catch ( \Exception $e ) { + return false; + } + } + + /** + * Compares two passes with each other according to the supplied operator. + * + * @since 2.10.6 + * + * @param int $pass_1 ID of the first pass. + * @param int $pass_2 ID of the second pass + * @param string $comparison Comparison operator. + * + * @return bool + */ + public static function pass_compare( $pass_1, $pass_2, $comparison = '>' ) { + if ( ! array_key_exists( $pass_1, self::$pass_hierarchy ) ) { + return false; + } + if ( ! array_key_exists( $pass_2, self::$pass_hierarchy ) ) { + return false; + } + + return version_compare( self::$pass_hierarchy[ $pass_1 ], self::$pass_hierarchy[ $pass_2 ], $comparison ); + } + + /** + * Whether the current pass can access a product by its categories. + * + * @param array $categories The array of a product's categories. + * @return false|int Returns false if the pass cannot access; returns the pass ID if it can. + */ + public function can_access_categories( array $categories ) { + if ( ! $this->has_pass() ) { + return false; + } + if ( $this->hasAllAccessPass() ) { + return $this->highest_pass_id; + } + $categories_to_check = array_intersect( $this->categories, $categories ); + if ( empty( $categories_to_check ) ) { + return false; + } + + foreach ( $categories_to_check as $category_id ) { + if ( in_array( (int) $category_id, $this->categories, true ) ) { + $pass_id = array_search( (int) $category_id, $this->categories, true ); + if ( self::pass_compare( $this->highest_pass_id, $pass_id, '>=' ) ) { + return $pass_id; + } + } + } + + return false; + } + + /** + * Gets the pass name from an ID. + * + * @since 3.1.1 + * @param int $pass_id + * @return string + */ + public function get_pass_name( $pass_id = null ) { + if ( 'valid' === $this->pro_license->license && ! empty( $this->pro_license->item_name ) ) { + return $this->pro_license->item_name; + } + if ( empty( $pass_id ) ) { + $pass_id = $this->highest_pass_id; + } + $names = array( + self::PERSONAL_PASS_ID => __( 'Personal Pass', 'easy-digital-downloads' ), + self::EXTENDED_PASS_ID => __( 'Extended Pass', 'easy-digital-downloads' ), + self::PROFESSIONAL_PASS_ID => __( 'Professional Pass', 'easy-digital-downloads' ), + self::ALL_ACCESS_PASS_ID => __( 'All Access Pass', 'easy-digital-downloads' ), + self::ALL_ACCESS_PASS_LIFETIME_ID => __( 'Lifetime All Access Pass', 'easy-digital-downloads' ), + ); + + return ! empty( $pass_id ) && array_key_exists( $pass_id, $names ) ? $names[ $pass_id ] : ''; + } + + /** + * If the supplied license key is for a pass, updates the `edd_pass_licenses` option with + * the pass ID and the date it was checked. + * + * Note: It's intentional that the `edd_pass_licenses` option is always updated, even if + * the provided license data is not for a pass. This is so we have a clearer idea + * of when the checks started coming through. If the option doesn't exist in the DB + * at all, then we haven't checked any licenses. + * + * @since 2.10.6 + * @since 3.1.1 Moved from the license handler class to the Pass Manager class. + * + * @param string $license + * @param object $api_data + */ + public function maybe_set_pass_flag( $license, $api_data ) { + $passes = get_option( 'edd_pass_licenses' ); + $passes = ! empty( $passes ) ? json_decode( $passes, true ) : array(); + + if ( ! empty( $api_data->pass_id ) && ! empty( $api_data->license ) && 'valid' === $api_data->license ) { + $passes[ $license ] = array( + 'pass_id' => intval( $api_data->pass_id ), + 'time_checked' => time(), + ); + } elseif ( array_key_exists( $license, $passes ) ) { + unset( $passes[ $license ] ); + } + + update_option( 'edd_pass_licenses', json_encode( $passes ) ); + } + + /** + * Removes the pass flag for the supplied license. This happens when a license + * is deactivated. + * + * @since 2.10.6 + * @since 3.1.1 Moved from the license handler class to the Pass Manager class. + * + * @param string $license + */ + public function maybe_remove_pass_flag( $license ) { + $passes = get_option( 'edd_pass_licenses' ); + $passes = ! empty( $passes ) ? json_decode( $passes, true ) : array(); + + if ( array_key_exists( $license, $passes ) ) { + unset( $passes[ $license ] ); + } + + update_option( 'edd_pass_licenses', json_encode( $passes ) ); + } + + /** + * Gets the pass ID from the pro license. + * + * @since 3.1.3 + * @return int|null + */ + private function get_pass_id_from_pro_license() { + // A valid pro pass should always have a pass ID. + if ( ! empty( $this->pro_license->pass_id ) && array_key_exists( $this->pro_license->pass_id, self::$pass_hierarchy ) ) { + return $this->pro_license->pass_id; + } + + // If the pro license is for a pass, but doesn't have a pass ID, we can try the item ID, if it's in the pass hierarchy + if ( array_key_exists( $this->pro_license->item_id, self::$pass_hierarchy ) ) { + return $this->pro_license->item_id; + } + + return null; + } +} diff --git a/src/Admin/Pointers.php b/src/Admin/Pointers.php new file mode 100644 index 00000000000..429c1e594bf --- /dev/null +++ b/src/Admin/Pointers.php @@ -0,0 +1,128 @@ + 'enqueue', + ); + } + + /** + * Enqueue the pointer scripts if there are any pointers to show. + * + * @since 3.1.4 + * @return void + */ + public function enqueue() { + $pointers = $this->get_valid_pointers(); + if ( empty( $pointers ) ) { + return; + } + wp_enqueue_style( 'wp-pointer' ); + wp_enqueue_script( 'edd-pointers', EDD_PLUGIN_URL . 'assets/js/edd-admin-pointers.js', array( 'wp-pointer' ), EDD_VERSION, true ); + wp_localize_script( + 'edd-pointers', + 'eddPointers', + array( + 'pointers' => $pointers, + 'next_label' => __( 'Next', 'easy-digital-downloads' ), + 'close_label' => __( 'Close', 'easy-digital-downloads' ), + ) + ); + } + + /** + * Gets the dismissed_wp_pointers user meta. + * + * @since 3.1.4 + * @param int $user_id THe current user ID. + * @return array + */ + private function get_user_dismissals( $user_id ) { + return explode( ',', (string) get_user_meta( $user_id, 'dismissed_wp_pointers', true ) ); + } + + /** + * Gets the pointers that have not been dismissed by the user. + * + * @since 3.1.4 + * @return array + */ + private function get_valid_pointers() { + $pointers = $this->get_pointers(); + if ( empty( $pointers ) ) { + return array(); + } + + $valid_pointers = array(); + $dismissed = $this->get_user_dismissals( get_current_user_id() ); + + foreach ( $pointers as $pointer ) { + if ( + empty( $pointer ) || + empty( $pointer['pointer_id'] ) || + empty( $pointer['target'] ) || + empty( $pointer['options'] ) || + in_array( $pointer['pointer_id'], $dismissed, true ) + ) { + continue; + } + + $valid_pointers[] = $pointer; + } + + return $valid_pointers; + } + + /** + * Gets all EDD pointers. + * + * @since 3.1.4 + * @return false|array + */ + private function get_pointers() { + if ( ! $this->can_register() ) { + return false; + } + + /** + * Allows adding pointers for registration within the EDD Ecosystem. + * + * @since 3.1.1 + * @param array $pointers The registered pointers for EDD to load. + */ + return apply_filters( 'edd_pointers', array() ); + } + + /** + * Determine whether pointers can be registered. + * Currently this just checks for excluded pages. + * Individual pointers should do their own capability checks. + * + * @since 3.1.4 + * @return bool + */ + private function can_register() { + // Exclude some pages from showing our pointers so we don't interfeer with user behavior. + $excluded_pages = array( + 'update-core.php', + 'plugin-install.php', + ); + + global $pagenow; + + return ! in_array( $pagenow, $excluded_pages, true ); + } +} diff --git a/src/Admin/Promos/Notices/License_Upgrade_Notice.php b/src/Admin/Promos/Notices/License_Upgrade_Notice.php new file mode 100644 index 00000000000..bcab1361b9f --- /dev/null +++ b/src/Admin/Promos/Notices/License_Upgrade_Notice.php @@ -0,0 +1,270 @@ +pass_manager = new Pass_Manager(); + } + + /** + * This notice lasts 90 days. + * + * @return int + */ + public static function dismiss_duration() { + return 3 * MONTH_IN_SECONDS; + } + + /** + * Determines if the current page is an EDD admin page. + * + * @return bool + */ + private function is_edd_admin_page() { + if ( defined( 'EDD_DOING_TESTS' ) && EDD_DOING_TESTS ) { + return true; + } + + $screen = get_current_screen(); + + if ( ! $screen instanceof \WP_Screen || in_array( $screen->id, array( 'dashboard', 'download_page_edd-onboarding-wizard' ), true ) || $screen->is_block_editor() || ! edd_is_admin_page( '', '', false ) ) { + return false; + } + + return true; + } + + /** + * @inheritDoc + * + * @return bool + */ + protected function _should_display() { + + if ( $this->meets_never_display_conditions() ) { + return false; + } + + // Someone with no license keys entered always sees a notice. + if ( $this->pass_manager->isFree() ) { + return true; + } + + // If we have no pass data yet, don't show the notice because we don't yet know what it should say. + if ( ! $this->pass_manager->has_pass_data ) { + return false; + } + + // If someone has an extended pass or higher, and has an active AffiliateWP license, don't show. + try { + if ( + $this->pass_manager->has_pass() && + Pass_Manager::pass_compare( $this->pass_manager->highest_pass_id, Pass_Manager::EXTENDED_PASS_ID, '>=' ) && + $this->has_affiliate_wp_license() && + $this->has_mi_license() + ) { + return false; + } + } catch ( \Exception $e ) { + return true; + } + + return true; + } + + /** + * Defines general conditions which mean the license upgrade notice should not display at all. + * + * @since 3.1.1 + * @return bool + */ + protected function meets_never_display_conditions() { + if ( ! $this->is_edd_admin_page() ) { + return true; + } + + if ( ! get_option( 'edd_onboarding_completed', false ) ) { + return true; + } + + return false; + } + + /** + * Determines whether or not AffiliateWP is installed and has a license key. + * + * @since 2.10.6 + * + * @return bool + */ + private function has_affiliate_wp_license() { + if ( ! function_exists( 'affiliate_wp' ) ) { + return false; + } + + return (bool) affiliate_wp()->settings->get( 'license_key' ); + } + + /** + * Determines whether or not MonsterInsights is installed and has a license key. + * + * @since 2.11.6 + * + * @return bool + */ + private function has_mi_license() { + if ( ! class_exists( 'MonsterInsights' ) ) { + return false; + } + + $mi_license = \MonsterInsights::$instance->license->get_license_key(); + return ! empty( $mi_license ); + } + + /** + * @inheritDoc + */ + protected function _display() { + + try { + if ( $this->pass_manager->isFree() ) { + $utm_parameters = $this->query_args( 'core' ); + $link_url = $this->build_url( + 'https://easydigitaldownloads.com/lite-upgrade/', + $utm_parameters + ); + + $help_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + ) + ); + + printf( + /* Translators: %1$s opening anchor tag; %2$s closing anchor tag */ + __( 'You are using the free version of Easy Digital Downloads. %1$sPurchase a pass%2$s to get email marketing tools and recurring payments. Already have a Pass? %3$sActivate it now%4$s', 'easy-digital-downloads' ), + '', + '', + '', + '' + ); + + } elseif ( ! $this->pass_manager->highest_pass_id ) { + $utm_parameters = $this->query_args( 'extension-license' ); + $link_url = $this->build_url( + 'https://easydigitaldownloads.com/your-account/', + $utm_parameters + ); + + // Individual product license active, but no pass. + printf( + /* Translators: %1$s opening anchor tag; %2$s closing anchor tag */ + __( 'For access to additional Easy Digital Downloads extensions to grow your store, consider %1$spurchasing a pass%2$s.', 'easy-digital-downloads' ), + '', + '' + ); + + } elseif ( Pass_Manager::pass_compare( $this->pass_manager->highest_pass_id, Pass_Manager::PERSONAL_PASS_ID, '=' ) ) { + $utm_parameters = $this->query_args( 'personal-pass' ); + $link_url = $this->build_url( + 'https://easydigitaldownloads.com/your-account/', + $utm_parameters + ); + + // Personal pass active. + printf( + /* Translators: %1$s opening anchor tag; %2$s closing anchor tag */ + __( 'You are using Easy Digital Downloads with a Personal Pass. Consider %1$supgrading%2$s to get recurring payments and more.', 'easy-digital-downloads' ), + '', + '' + ); + + } elseif ( Pass_Manager::pass_compare( $this->pass_manager->highest_pass_id, Pass_Manager::EXTENDED_PASS_ID, '>=' ) ) { + if ( ! $this->has_affiliate_wp_license() ) { + $link_url = edd_link_helper( + 'https://affiliatewp.com', + array( + 'utm_medium' => 'top-promo', + 'utm_content' => 'affiliate-wp', + ) + ); + + printf( + /* Translators: %1$s opening anchor tag; %2$s closing anchor tag */ + __( 'Grow your business and make more money with affiliate marketing. %1$sGet AffiliateWP%2$s', 'easy-digital-downloads' ), + '', + '' + ); + } elseif( ! $this->has_mi_license() ) { + printf( + /* Translators: %1$s opening anchor tag; %2$s closing anchor tag */ + __( 'Gain access to powerful insights to grow your traffic and revenue. %1$sGet MonsterInsights%2$s', 'easy-digital-downloads' ), + '', + '' + ); + } + } + } catch ( \Exception $e ) { + // If we're in here, that means we have an invalid pass ID... what should we do? :thinking:. + } + } + + /** + * Builds the UTM parameters for the URLs. + * + * @since 2.10.6 + * + * @param string $upgrade_from License type upgraded from. + * @param string $source Current page. + * + * @return string[] + */ + private function query_args( $upgrade_from, $source = '' ) { + return array( + 'utm_medium' => 'top-promo', + 'utm_content' => 'upgrade-from-' . urlencode( $upgrade_from ), + ); + } + + /** + * Build a link with UTM parameters + * + * @since 3.1 + * + * @param string $url The Base URL. + * @param array $utm_parameters The UTM tags for the URL. + * + * @return string + */ + private function build_url( $url, $utm_parameters ) { + return esc_url( + edd_link_helper( + $url, + $utm_parameters, + false + ) + ); + } +} diff --git a/src/Admin/Promos/Notices/Lite.php b/src/Admin/Promos/Notices/Lite.php new file mode 100644 index 00000000000..40190520ec1 --- /dev/null +++ b/src/Admin/Promos/Notices/Lite.php @@ -0,0 +1,115 @@ + 'extensions-page-overlay', + 'utm_content' => 'upgrade-to-pro', + ) + ); + ?> +

    +

    +

    + do_features(); ?> + +
    + do_learn_more_link(); + } + + /** + * Duration (in seconds) that the notice is dismissed for. + * `0` means it's dismissed permanently. + * + * @return int + */ + public static function dismiss_duration() { + return 1; + } + + /** + * Outputs the features list. + * + * @since 3.1.1 + * @return void + */ + protected function do_features() { + ?> +
      + __( 'Pro Payment Gateways', 'easy-digital-downloads' ), + 'email-marketing' => __( 'Email Marketing Integrations', 'easy-digital-downloads' ), + 'subscriptions' => __( 'Sell Subscriptions', 'easy-digital-downloads' ), + 'lead-magnets' => __( 'Build Lead Magnets', 'easy-digital-downloads' ), + 'bundle' => __( 'Advanced Bundle Features', 'easy-digital-downloads' ), + 'automate' => __( 'Automate Your Business', 'easy-digital-downloads' ), + ); + foreach ( $list_items as $icon => $label ) { + printf( + '
    • %s
    • ', + esc_url( EDD_PLUGIN_URL . "assets/images/icons/icon-{$icon}.svg" ), + esc_html( $label ) + ); + } + ?> +
    + 'extensions-page-overlay', + 'utm_content' => 'have-questions', + ) + ); + ?> + + get_id() ) && $this->_should_display(); + } + + /** + * Duration (in seconds) that the notice is dismissed for. + * `0` means it's dismissed permanently. + * + * @return int + */ + public static function dismiss_duration() { + return 0; + } + + /** + * Individual notices can override this method to control display logic. + * + * @since 2.10.6 + * + * @return bool + */ + protected function _should_display() { + return true; + } + + /** + * Displays the notice. + * Individual notices typically should not override this method, as it contains + * all the notice wrapper logic. Instead, notices should override `_display()` + * + * @since 2.10.6 + * @return void + */ + public function display() { + ?> +
    + _display(); ?> + + + + +
    + load_notices(); + } + + /** + * Returns an array of events that this subscriber wants to listen to. + * + * @since 3.1.1 + * @return array + */ + public static function get_subscribed_events() { + return array( + 'wp_ajax_edd_dismiss_promo_notice' => 'dismiss_notice', + ); + } + + /** + * Loads and displays all registered promotional notices. + * + * @since 2.10.6 + */ + private function load_notices() { + + foreach ( $this->get_notices() as $notice_class_name ) { + if ( ! class_exists( $notice_class_name ) ) { + $file_name = strtolower( str_replace( '_', '-', basename( str_replace( '\\', '/', $notice_class_name ) ) ) ); + $file_path = EDD_PLUGIN_DIR . 'includes/admin/promos/notices/class-' . $file_name . '.php'; + + if ( file_exists( $file_path ) ) { + require_once $file_path; + } + } + + if ( ! class_exists( $notice_class_name ) ) { + continue; + } + + add_action( $notice_class_name::DISPLAY_HOOK, function () use ( $notice_class_name ) { + /** @var Notice $notice */ + $notice = new $notice_class_name(); + if ( $notice->should_display() ) { + $notice->display(); + } + }, $notice_class_name::DISPLAY_PRIORITY ); + } + } + + /** + * Gets the notices. + * Implemented as a method so that extending classes can access. + * + * @since 3.1.1 + * @return array + */ + protected function get_notices() { + return $this->notices; + } + + /** + * Determines whether or not a notice has been dismissed. + * + * @since 2.10.6 + * + * @param string $id ID of the notice to check. + * + * @return bool + */ + public static function is_dismissed( $id ) { + $is_dismissed = (bool) Persistent_Dismissible::get( array( + 'id' => 'edd-' . $id + ) ); + + return true === $is_dismissed; + } + + /** + * Dismisses a notice. + * + * @since 2.10.6 + * + * @param string $id ID of the notice to dismiss. + * @param int $dismissal_length Number of seconds to dismiss the notice for, or `0` for forever. + */ + public static function dismiss( $id, $dismissal_length = 0 ) { + Persistent_Dismissible::set( array( + 'id' => 'edd-' . $id, + 'life' => $dismissal_length + ) ); + } + + /** + * AJAX callback for dismissing a notice. + * + * @since 2.10.6 + */ + public function dismiss_notice() { + $notice_id = ! empty( $_POST['notice_id'] ) ? sanitize_text_field( $_POST['notice_id'] ) : false; + if ( empty( $notice_id ) ) { + wp_send_json_error( __( 'Missing notice ID.', 'easy-digital-downloads' ), 400 ); + } + + if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'edd-dismiss-notice-' . sanitize_key( $_POST['notice_id'] ) ) ) { + wp_send_json_error( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), 403 ); + } + + $notice_class_name = $this->get_notice_class_name( $notice_id ); + + // No matching notice class was found. + if ( ! $notice_class_name ) { + wp_send_json_error( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), 403 ); + } + + // Check whether the current user can dismiss the notice. + if ( ! defined( $notice_class_name . '::CAPABILITY' ) || ! current_user_can( $notice_class_name::CAPABILITY ) ) { + wp_send_json_error( __( 'You do not have permission to perform this action.', 'easy-digital-downloads' ), 403 ); + } + + $dismissal_length = ! empty( $_POST['lifespan'] ) ? absint( $_POST['lifespan'] ) : 0; + + self::dismiss( sanitize_key( $_POST['notice_id'] ), $dismissal_length ); + + wp_send_json_success(); + } + + /** + * Gets the notice class name for a given notice ID. + * + * @since 2.11.4 + * @param string $notice_id The notice ID to match. + * @return bool|string The class name or false if no matching class was found. + */ + private function get_notice_class_name( $notice_id ) { + $notice_class_name = false; + // Look through the registered notice classes for the one being dismissed. + foreach ( $this->notices as $notice_class_to_check ) { + if ( ! class_exists( $notice_class_to_check ) ) { + $file_name = strtolower( str_replace( '_', '-', basename( str_replace( '\\', '/', $notice_class_to_check ) ) ) ); + $file_path = EDD_PLUGIN_DIR . 'includes/admin/promos/notices/class-' . $file_name . '.php'; + + if ( file_exists( $file_path ) ) { + require_once $file_path; + } + } + + if ( ! class_exists( $notice_class_to_check ) ) { + continue; + } + $notice = new $notice_class_to_check(); + if ( $notice->get_id() === $notice_id ) { + $notice_class_name = $notice_class_to_check; + break; + } + } + + return $notice_class_name; + } +} diff --git a/src/Admin/Settings/EmailMarketing.php b/src/Admin/Settings/EmailMarketing.php new file mode 100644 index 00000000000..0ee5e4b2a70 --- /dev/null +++ b/src/Admin/Settings/EmailMarketing.php @@ -0,0 +1,137 @@ + 'add_section', + 'edd_settings_tab_top_marketing_email_marketing' => 'field', + ); + } + + /** + * Adds an email marketing section to the Marketing tab. + * + * @since 2.11.4 + * @param array $sections + * @return array + */ + public function add_section( $sections ) { + if ( ! $this->is_edd_settings_screen() ) { + return $sections; + } + $product_data = $this->get_product_data(); + if ( ! $product_data || ! is_array( $product_data ) ) { + return $sections; + } + $sections[ $this->settings_section ] = __( 'Email Marketing', 'easy-digital-downloads' ); + + return $sections; + } + + /** + * Gets the customized configuration for the extension card. + * + * @since 2.11.4 + * @param \EDD\Admin\Extensions\ProductData $product_data The product data object. + * @return array + */ + protected function get_configuration( \EDD\Admin\Extensions\ProductData $product_data ) { + $configuration = array(); + if ( ! empty( $product_data->title ) ) { + /* translators: the product name */ + $configuration['heading'] = sprintf( __( 'Get %s Today!', 'easy-digital-downloads' ), $product_data->title ); + } + + return $configuration; + } + + /** + * Adds the email marketing extensions as cards. + * + * @since 2.11.4 + * @return void + */ + public function field() { + $this->hide_submit_button(); + if ( $this->is_activated() ) { + printf( '

    %s

    ', esc_html__( 'Looks like you have an email marketing extension installed, but we support more providers!', 'easy-digital-downloads' ) ); + } + ?> +
    + get_product_data() as $item_id => $extension ) { + $this->do_single_extension_card( $item_id ); + } + ?> +
    + 1578, + ); + } + + /** + * Whether any email marketing extension is active. + * + * @since 2.11.4 + * + * @return bool True if any email marketing extension is active. + */ + protected function is_activated() { + foreach ( $this->get_product_data() as $extension ) { + // The data is stored in the database as an array--at this point it has not been converted to an object. + if ( ! empty( $extension['basename'] ) && $this->manager->is_plugin_active( $extension['basename'] ) ) { + return true; + } + } + + return false; + } +} diff --git a/src/Admin/Settings/Invoices.php b/src/Admin/Settings/Invoices.php new file mode 100644 index 00000000000..00365c1f5fb --- /dev/null +++ b/src/Admin/Settings/Invoices.php @@ -0,0 +1,129 @@ + 'add_section', + 'edd_settings_tab_top_gateways_invoices' => 'settings_field', + 'edd_settings_tab_bottom_gateways_invoices' => 'hide_submit_button', + ); + } + + /** + * Gets the custom configuration for Invoices. + * + * @since 2.11.4 + * @param \EDD\Admin\Extensions\ProductData $product_data The product data object. + * @return array + */ + protected function get_configuration( \EDD\Admin\Extensions\ProductData $product_data ) { + return array( + 'style' => 'detailed-2col', + 'heading' => 'Attractive Invoices For Your Customers', + 'description' => $this->get_custom_description(), + 'features' => array( + 'Generate Attractive Invoices', + 'Build Customer Confidence', + 'PDF Download Support', + 'Include in Purchase Emails', + 'Customizable Templates', + ), + ); + } + + /** + * Gets a custom description for the Invoices extension card. + * + * @since 2.11.4 + * @return string + */ + private function get_custom_description() { + $description = array( + 'Impress customers and build customer loyalty with attractive invoices. Making it easy to locate, save, and print purchase history builds trust with customers.', + 'Provide a professional experience with customizable templates and one-click PDF downloads. ', + ); + + return $this->format_description( $description ); + } + + /** + * Adds the Invoices Payments section to the settings. + * + * @param array $sections + * @return array + */ + public function add_section( $sections ) { + if ( ! $this->can_show_product_section() ) { + return $sections; + } + + $sections[ $this->settings_section ] = __( 'Invoices', 'easy-digital-downloads' ); + + return $sections; + } + + /** + * Whether EDD Invoices active or not. + * + * @since 2.11.4 + * + * @return bool True if Invoices is active. + */ + protected function is_activated() { + if ( $this->manager->is_plugin_active( $this->get_product_data() ) ) { + return true; + } + + return class_exists( 'EDDInvoices' ); + } +} diff --git a/src/Admin/Settings/Recurring.php b/src/Admin/Settings/Recurring.php new file mode 100644 index 00000000000..1d4e993bb7d --- /dev/null +++ b/src/Admin/Settings/Recurring.php @@ -0,0 +1,129 @@ + 'add_section', + 'edd_settings_tab_top_gateways_recurring' => 'settings_field', + 'edd_settings_tab_bottom_gateways_recurring' => 'hide_submit_button', + ); + } + + /** + * Gets the custom configuration for Recurring. + * + * @since 2.11.4 + * @param \EDD\Admin\Extensions\ProductData $product_data The product data object. + * @return array + */ + protected function get_configuration( \EDD\Admin\Extensions\ProductData $product_data ) { + return array( + 'style' => 'detailed-2col', + 'heading' => 'Increase Revenue By Selling Subscriptions!', + 'description' => $this->get_custom_description(), + 'features' => array( + 'Flexible Recurring Payments', + 'Custom Reminder Emails', + 'Free Trial Support', + 'Signup Fees', + 'Recurring Revenue Reports', + ), + ); + } + + /** + * Gets a custom description for the Recurring extension card. + * + * @since 2.11.4 + * @return string + */ + private function get_custom_description() { + $description = array( + 'Grow stable income by selling subscriptions and make renewals hassle free for your customers.', + 'When your customers are automatically billed, you reduce the risk of missed payments and retain more customers.', + ); + + return $this->format_description( $description ); + } + + /** + * Adds the Recurring Payments section to the settings. + * + * @param array $sections + * @return array + */ + public function add_section( $sections ) { + if ( ! $this->can_show_product_section() ) { + return $sections; + } + + $sections[ $this->settings_section ] = __( 'Subscriptions', 'easy-digital-downloads' ); + + return $sections; + } + + /** + * Whether EDD Recurring active or not. + * + * @since 2.11.4 + * + * @return bool True if Recurring is active. + */ + protected function is_activated() { + if ( $this->manager->is_plugin_active( $this->get_product_data() ) ) { + return true; + } + + return class_exists( 'EDD_Recurring' ); + } +} diff --git a/src/Admin/Settings/Register.php b/src/Admin/Settings/Register.php new file mode 100644 index 00000000000..bf6fdae1704 --- /dev/null +++ b/src/Admin/Settings/Register.php @@ -0,0 +1,89 @@ +settings ) ) { + $this->settings = $this->register(); + } + + return apply_filters( 'edd_registered_settings', $this->settings ); + } + + /** + * Register the settings. + * + * @since 3.1.4 + * @return array + */ + private function register() { + $tabs = array( + 'general' => new Tabs\General(), + 'gateways' => new Tabs\Gateways(), + 'emails' => new Tabs\Emails(), + 'marketing' => new Tabs\Marketing(), + 'taxes' => new Tabs\Taxes(), + 'extensions' => new Tabs\Extensions(), + 'licenses' => new Tabs\Licenses(), + 'misc' => new Tabs\Misc(), + 'privacy' => new Tabs\Privacy(), + ); + + $settings = array(); + foreach ( $tabs as $key => $tab ) { + $settings[ $key ] = $tab->get(); + } + + if ( has_filter( 'edd_settings_styles' ) ) { + $settings['styles'] = $this->get_styles(); + } + + return $settings; + } + + /** + * Allow registered settings to surface the deprecated "Styles" tab. + * + * @since 3.1.4 + * @return array + */ + private function get_styles() { + return edd_apply_filters_deprecated( + 'edd_settings_styles', + array( + array( + 'main' => array(), + 'buttons' => array(), + ), + ), + '3.0', + 'edd_settings_misc' + ); + } +} diff --git a/src/Admin/Settings/Reviews.php b/src/Admin/Settings/Reviews.php new file mode 100644 index 00000000000..dea5b8f4ee4 --- /dev/null +++ b/src/Admin/Settings/Reviews.php @@ -0,0 +1,157 @@ + 'add_section', + 'edd_settings_tab_top_marketing_reviews' => 'settings_field', + 'edd_settings_tab_bottom_marketing_reviews' => 'hide_submit_button', + 'add_meta_boxes' => 'maybe_do_metabox', + ); + } + + /** + * Gets the custom configuration for Reviews. + * + * @since 2.11.4 + * @param \EDD\Admin\Extensions\ProductData $product_data The product data object. + * @return array + */ + protected function get_configuration( \EDD\Admin\Extensions\ProductData $product_data ) { + $configuration = array( + 'heading' => 'Build Trust With Real Customer Reviews', + ); + $settings_configuration = array( + 'style' => 'detailed-2col', + 'description' => $this->get_custom_description(), + 'features' => array( + 'Request Reviews', + 'Incentivize Reviewers', + 'Full Schema.org Support', + 'Embed Reviews Via Blocks', + 'Limit Reviews to Customers', + 'Vendor Reviews (with Frontend Submissions)', + ), + ); + return $this->is_edd_settings_screen() ? array_merge( $configuration, $settings_configuration ) : $configuration; + } + + /** + * Gets a custom description for the Reviews extension card. + * + * @since 2.11.4 + * @return string + */ + private function get_custom_description() { + $description = array( + 'Increase sales on your site with social proof. 70% of online shoppers don\'t purchase before reading reviews.', + 'Easily collect, manage, and beautifully display reviews all from your WordPress dashboard.', + ); + + return $this->format_description( $description ); + } + + /** + * Adds the Reviews section to the settings. + * + * @param array $sections + * @return array + */ + public function add_section( $sections ) { + if ( ! $this->can_show_product_section() ) { + return $sections; + } + + $sections[ $this->settings_section ] = __( 'Reviews', 'easy-digital-downloads' ); + + return $sections; + } + + /** + * If Reviews is not active, registers a metabox on individual download edit screen. + * + * @since 2.11.4 + * @return void + */ + public function maybe_do_metabox() { + if ( ! $this->is_download_edit_screen() ) { + return; + } + if ( $this->is_activated() ) { + return; + } + add_meta_box( + 'edd-reviews-status', + __( 'Product Reviews', 'easy-digital-downloads' ), + array( $this, 'settings_field' ), + 'download', + 'side', + 'low' + ); + } + + /** + * Whether EDD Reviews active or not. + * + * @since 2.11.4 + * + * @return bool True if Reviews is active. + */ + protected function is_activated() { + if ( $this->manager->is_plugin_active( $this->get_product_data() ) ) { + return true; + } + + return function_exists( 'edd_reviews' ); + } +} diff --git a/src/Admin/Settings/Sanitize.php b/src/Admin/Settings/Sanitize.php new file mode 100644 index 00000000000..d7d44a3c386 --- /dev/null +++ b/src/Admin/Settings/Sanitize.php @@ -0,0 +1,105 @@ + 'sanitize_sequential_order_numbers', + ); + } + + /** + * Sanitizes the sequential order number starting number. + * + * @since 3.1.4 + * @param array $input The input. + * @return array + */ + public function sanitize_sequential_order_numbers( $input ) { + + // If no starting number was entered return the input. + if ( empty( $input['sequential_start'] ) ) { + return $input; + } + + // If the next order number isn't set, return the input. + $next_order_number = get_option( 'edd_next_order_number', false ); + if ( ! $next_order_number ) { + return $input; + } + + // If the next order number is the same as the original, return the input. + $start = (int) $input['sequential_start']; + $original_start = (int) edd_get_option( 'sequential_start', $start ); + if ( $start === $original_start ) { + return $input; + } + + $can_update = true; + // If the next order number is less than the original, we need to make sure the most recent order number is not less than the new starting number. + if ( $start < (int) $next_order_number ) { + $most_recent_order_number = $this->get_most_recent_order_number(); + if ( ! empty( $most_recent_order_number ) && $start < (int) $most_recent_order_number ) { + $can_update = false; + } + } + + // If order number cannot be updated, return the original value and set a flag. + if ( ! $can_update ) { + $input['sequential_start'] = edd_get_option( 'sequential_start' ); + edd_update_option( 'sequential_start_update_failed', true ); + } else { + // Update the next order number. + update_option( 'edd_next_order_number', $start ); + } + + return $input; + } + + /** + * Gets the most recent order number. + * + * @since 3.1.4 + * @return int|bool + */ + private function get_most_recent_order_number() { + $orders = edd_get_orders( + array( + 'number' => 1, + 'orderby' => 'ID', + 'order' => 'DESC', + 'fields' => 'order_number', + ) + ); + + if ( empty( $orders ) ) { + return false; + } + $last_order_number = reset( $orders ); + $order_number = new \EDD\Orders\Number(); + + return $order_number->unformat( $last_order_number ); + } +} diff --git a/src/Admin/Settings/Tabs/Emails.php b/src/Admin/Settings/Tabs/Emails.php new file mode 100644 index 00000000000..9db9b888aa6 --- /dev/null +++ b/src/Admin/Settings/Tabs/Emails.php @@ -0,0 +1,220 @@ + array( + 'email_header' => array( + 'id' => 'email_header', + 'name' => '' . __( 'Email Configuration', 'easy-digital-downloads' ) . '', + 'type' => 'header', + ), + 'email_template' => array( + 'id' => 'email_template', + 'name' => __( 'Template', 'easy-digital-downloads' ), + 'desc' => __( 'Choose a template. Click "Save Changes" then "Preview Purchase Receipt" to see the new template.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => edd_get_email_templates(), + ), + 'email_logo' => array( + 'id' => 'email_logo', + 'name' => __( 'Logo', 'easy-digital-downloads' ), + 'desc' => __( 'Upload or choose a logo to be displayed at the top of sales receipt emails. Displayed on HTML emails only.', 'easy-digital-downloads' ), + 'type' => 'upload', + ), + 'from_name' => array( + 'id' => 'from_name', + 'name' => __( 'From Name', 'easy-digital-downloads' ), + 'desc' => __( 'This should be your site or shop name. Defaults to Site Title if empty.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => $this->get_site_name(), + 'placeholder' => $this->get_site_name(), + ), + 'from_email' => array( + 'id' => 'from_email', + 'name' => __( 'From Email', 'easy-digital-downloads' ), + 'desc' => __( 'This will act as the "from" and "reply-to" addresses.', 'easy-digital-downloads' ), + 'type' => 'email', + 'std' => $this->get_admin_email(), + 'placeholder' => $this->get_admin_email(), + ), + 'email_settings' => array( + 'id' => 'email_settings', + 'name' => '', + 'desc' => '', + 'type' => 'hook', + ), + ), + 'purchase_receipts' => array( + 'purchase_receipt_email_settings' => array( + 'id' => 'purchase_receipt_email_settings', + 'name' => '', + 'desc' => '', + 'type' => 'hook', + ), + 'purchase_subject' => array( + 'id' => 'purchase_subject', + 'name' => __( 'Purchase Email Subject', 'easy-digital-downloads' ), + 'desc' => __( 'Enter the subject line for the purchase receipt email.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => __( 'Purchase Receipt', 'easy-digital-downloads' ), + ), + 'purchase_heading' => array( + 'id' => 'purchase_heading', + 'name' => __( 'Purchase Email Heading', 'easy-digital-downloads' ), + 'desc' => __( 'Enter the heading for the purchase receipt email.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => __( 'Purchase Receipt', 'easy-digital-downloads' ), + ), + 'purchase_receipt' => array( + 'id' => 'purchase_receipt', + 'name' => __( 'Purchase Receipt', 'easy-digital-downloads' ), + 'desc' => __( 'Text to email customers after completing a purchase. Personalize with HTML and {tag} markers.', 'easy-digital-downloads' ) . '

    ' . edd_get_emails_tags_list(), + 'type' => 'rich_editor', + 'std' => __( 'Dear', 'easy-digital-downloads' ) . " {name},\n\n" . __( 'Thank you for your purchase. Please click on the link(s) below to download your files.', 'easy-digital-downloads' ) . "\n\n{download_list}\n\n{sitename}", + ), + ), + 'sale_notifications' => array( + 'sale_notification_subject' => array( + 'id' => 'sale_notification_subject', + 'name' => __( 'Sale Notification Subject', 'easy-digital-downloads' ), + 'desc' => __( 'Enter the subject line for the sale notification email.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => 'New download purchase - Order #{payment_id}', + ), + 'sale_notification_heading' => array( + 'id' => 'sale_notification_heading', + 'name' => __( 'Sale Notification Heading', 'easy-digital-downloads' ), + 'desc' => __( 'Enter the heading for the sale notification email.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => __( 'New Sale!', 'easy-digital-downloads' ), + ), + 'sale_notification' => array( + 'id' => 'sale_notification', + 'name' => __( 'Sale Notification', 'easy-digital-downloads' ), + 'desc' => __( 'Text to email as a notification for every completed purchase. Personalize with HTML and {tag} markers.', 'easy-digital-downloads' ) . '

    ' . edd_get_emails_tags_list(), + 'type' => 'rich_editor', + 'std' => edd_get_default_sale_notification_email(), + ), + 'admin_notice_emails' => array( + 'id' => 'admin_notice_emails', + 'name' => __( 'Sale Notification Emails', 'easy-digital-downloads' ), + 'desc' => __( 'Enter the email address(es) that should receive a notification anytime a sale is made. One per line.', 'easy-digital-downloads' ), + 'type' => 'textarea', + 'std' => $this->get_admin_email(), + ), + 'disable_admin_notices' => array( + 'id' => 'disable_admin_notices', + 'name' => __( 'Disable Admin Notifications', 'easy-digital-downloads' ), + 'desc' => __( 'Check this box if you do not want to receive sales notification emails.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + ), + ), + 'email_summaries' => $this->get_email_summaries(), + ); + } + + /** + * Gets the email summaries settings. + * + * @since 3.1.4 + * @return array + */ + private function get_email_summaries() { + $email_summary_recipient = edd_get_option( 'email_summary_recipient', 'admin' ); + $email_summary_trigger_url = wp_nonce_url( + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'emails', + 'section' => 'email_summaries', + 'edd_action' => 'trigger_email_summary', + ) + ), + 'edd_trigger_email_summary' + ); + $email_summary_schedule = wp_next_scheduled( \EDD_Email_Summary_Cron::CRON_EVENT_NAME ); + $email_summary_schedule_text = ' ' . esc_html( __( 'The summary email is not yet scheduled. Save the settings to manually schedule it.', 'easy-digital-downloads' ) ) . ''; + if ( $email_summary_schedule ) { + $email_summary_schedule_date = \Carbon\Carbon::createFromTimestamp( $email_summary_schedule )->setTimezone( edd_get_timezone_id() ); + /* Translators: formatted date */ + $email_summary_schedule_text = sprintf( __( 'The next summary email is scheduled to send on %s.', 'easy-digital-downloads' ), $email_summary_schedule_date->format( get_option( 'date_format' ) ) ); + } + + return array( + 'email_summary_frequency' => array( + 'id' => 'email_summary_frequency', + 'name' => __( 'Email Frequency', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => 'weekly', + 'desc' => $email_summary_schedule_text, + 'options' => array( + 'weekly' => __( 'Weekly', 'easy-digital-downloads' ), + 'monthly' => __( 'Monthly', 'easy-digital-downloads' ), + ), + ), + 'email_summary_recipient' => array( + 'id' => 'email_summary_recipient', + 'name' => __( 'Email Recipient', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => 'admin', + 'options' => array( + /* Translators: email */ + 'admin' => sprintf( __( 'Administrator: %s', 'easy-digital-downloads' ), $this->get_admin_email() ), + 'custom' => __( 'Custom Recipients', 'easy-digital-downloads' ), + ), + ), + 'email_summary_custom_recipients' => array( + 'id' => 'email_summary_custom_recipients', + 'class' => ( 'admin' === $email_summary_recipient ) ? 'hidden' : '', + 'name' => __( 'Custom Recipients', 'easy-digital-downloads' ), + 'desc' => __( 'Enter the email address(es) that should receive Email Summaries. One per line.', 'easy-digital-downloads' ), + 'type' => 'textarea', + ), + 'email_summary_buttons' => array( + 'id' => 'email_summary_buttons', + 'name' => '', + 'desc' => ' + ' . esc_html( __( 'Send Test Email', 'easy-digital-downloads' ) ) . ' +
    +
    + ', + 'type' => 'descriptive_text', + ), + 'disable_email_summary' => array( + 'id' => 'disable_email_summary', + 'name' => __( 'Disable Email Summary', 'easy-digital-downloads' ), + 'desc' => '' . __( 'Learn more about Email Summaries.', 'easy-digital-downloads' ) . '', + 'check' => __( 'Check this box to disable Email Summaries.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + ); + } +} diff --git a/src/Admin/Settings/Tabs/Extensions.php b/src/Admin/Settings/Tabs/Extensions.php new file mode 100644 index 00000000000..2a3e06c11eb --- /dev/null +++ b/src/Admin/Settings/Tabs/Extensions.php @@ -0,0 +1,31 @@ + array( + 'test_mode' => $this->get_test_mode(), + 'gateways' => array( + 'id' => 'gateways', + 'name' => __( 'Active Gateways', 'easy-digital-downloads' ), + 'desc' => __( 'Choose the payment gateways you want to enable.', 'easy-digital-downloads' ), + 'type' => 'gateways', + 'options' => $gateways, + ), + 'default_gateway' => array( + 'id' => 'default_gateway', + 'name' => __( 'Default Gateway', 'easy-digital-downloads' ), + 'desc' => __( 'Choose the gateway your checkout will use by default.
    If you choose Automatic, the first enabled gateway from the Active Gateways will be used.', 'easy-digital-downloads' ), + 'type' => 'gateway_select', + 'options' => $gateways, + ), + 'accepted_cards' => array( + 'id' => 'accepted_cards', + 'name' => __( 'Payment Method Icons', 'easy-digital-downloads' ), + 'desc' => __( 'Display icons for the selected payment methods.', 'easy-digital-downloads' ) . '
    ' . __( 'You will also need to configure your gateway settings if you are accepting credit cards.', 'easy-digital-downloads' ), + 'type' => 'payment_icons', + 'options' => apply_filters( + 'edd_accepted_payment_icons', + array( + 'mastercard' => 'Mastercard', + 'visa' => 'Visa', + 'americanexpress' => 'American Express', + 'discover' => 'Discover', + 'paypal' => 'PayPal', + ) + ), + ), + ), + 'checkout' => array( + 'enforce_ssl' => array( + 'id' => 'enforce_ssl', + 'name' => __( 'Enforce SSL on Checkout', 'easy-digital-downloads' ), + 'check' => __( 'Enforced', 'easy-digital-downloads' ), + 'desc' => __( 'Redirect all customers to the secure checkout page. You must have an SSL certificate installed to use this option.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + 'redirect_on_add' => array( + 'id' => 'redirect_on_add', + 'name' => __( 'Redirect to Checkout', 'easy-digital-downloads' ), + 'desc' => __( 'Immediately redirect to checkout after adding an item to the cart?', 'easy-digital-downloads' ), + 'type' => 'checkbox', + 'tooltip_title' => __( 'Redirect to Checkout', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'When enabled, once an item has been added to the cart, the customer will be redirected directly to your checkout page. This is useful for stores that sell single items.', 'easy-digital-downloads' ), + ), + 'logged_in_only' => array( + 'id' => 'logged_in_only', + 'name' => __( 'Require Login', 'easy-digital-downloads' ), + 'desc' => __( 'Require that users be logged-in to purchase files.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + 'tooltip_title' => __( 'Require Login', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'You can require that customers create and login to user accounts prior to purchasing from your store by enabling this option. When unchecked, users can purchase without being logged in by using their name and email address.', 'easy-digital-downloads' ), + ), + 'show_register_form' => array( + 'id' => 'show_register_form', + 'name' => __( 'Show Register / Login Form', 'easy-digital-downloads' ), + 'desc' => __( 'Display the registration and login forms on the checkout page for non-logged-in users.', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => 'none', + 'options' => array( + 'both' => __( 'Registration and Login Forms', 'easy-digital-downloads' ), + 'registration' => __( 'Registration Form Only', 'easy-digital-downloads' ), + 'login' => __( 'Login Form Only', 'easy-digital-downloads' ), + 'none' => __( 'None', 'easy-digital-downloads' ), + ), + ), + 'enable_cart_saving' => array( + 'id' => 'enable_cart_saving', + 'name' => __( 'Enable Cart Saving', 'easy-digital-downloads' ), + 'desc' => __( 'Check this to enable cart saving on the checkout.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + 'tooltip_title' => __( 'Cart Saving', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Cart saving allows shoppers to create a temporary link to their current shopping cart so they can come back to it later, or share it with someone.', 'easy-digital-downloads' ), + ), + 'moderation_settings' => array( + 'id' => 'moderation_settings', + 'name' => '

    ' . __( 'Moderation', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'Moderation', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'It is sometimes necessary to temporarily prevent certain potential customers from checking out. Use these settings to control who can make purchases.', 'easy-digital-downloads' ), + ), + 'banned_emails' => array( + 'id' => 'banned_emails', + 'name' => __( 'Banned Emails', 'easy-digital-downloads' ), + 'desc' => __( 'Emails placed in the box above will not be allowed to make purchases.', 'easy-digital-downloads' ) . '
    ' . __( 'One per line, enter: email addresses, domains (@example.com), or TLDs (.gov).', 'easy-digital-downloads' ), + 'type' => 'textarea', + 'placeholder' => __( '@example.com', 'easy-digital-downloads' ), + ), + ), + 'refunds' => array( + 'refunds_settings' => array( + 'id' => 'refunds_settings', + 'name' => '

    ' . __( 'Refunds', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'Refunds', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'As a shop owner, sometimes refunds are necessary. Use these settings to decide how refunds will work in your shop.', 'easy-digital-downloads' ), + ), + 'refundability' => array( + 'id' => 'refundability', + 'name' => __( 'Default Status', 'easy-digital-downloads' ), + 'desc' => __( 'This will be the store default. It can be changed at a per-product level.', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => 'refundable', + 'options' => edd_get_refundability_types(), + ), + 'refund_window' => array( + 'id' => 'refund_window', + 'name' => __( 'Refund Window', 'easy-digital-downloads' ), + 'desc' => __( 'Number of days (after a sale) when refunds can be processed.
    Default is 30 days. Set to 0 for infinity. It can be changed at a per-product level.', 'easy-digital-downloads' ), + 'std' => 30, + 'type' => 'number', + 'size' => 'small', + 'max' => 3650, // Ten year maximum, because why explicitly support longer + 'min' => 0, + 'step' => 1, + ), + ), + 'accounting' => $this->get_accounting_settings(), + ); + } + + /** + * Get the test mode setting. + * + * @since 3.1.4 + * @return array + */ + private function get_test_mode() { + + $test_mode = array( + 'id' => 'test_mode', + 'name' => __( 'Test Mode', 'easy-digital-downloads' ), + 'check' => __( 'Enabled', 'easy-digital-downloads' ), + 'desc' => __( 'While test mode is enabled, no live transactions are processed.
    Use test mode in conjunction with the sandbox/test account for the payment gateways to test.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ); + // If test_mode is being forced to true, alter the setting so it cannot be modified. + if ( ! edd_is_test_mode_forced() ) { + return $test_mode; + } + + return array_merge( + array( + 'options' => array( + 'disabled' => true, + 'readonly' => true, + ), + 'tooltip_title' => __( 'Forced Test Mode', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'You currently cannot modify the Test Mode setting, as the \'EDD_TEST_MODE\' constant has been defined as \'true\' or the edd_is_test_mode filter is being forced to \'true\'.', 'easy-digital-downloads' ), + ), + $test_mode + ); + } + + /** + * Gets the accounting settings. + * + * @since 3.1.4 + * @return array + */ + private function get_accounting_settings() { + $settings = array( + 'enable_skus' => array( + 'id' => 'enable_skus', + 'name' => __( 'Enable SKU Entry', 'easy-digital-downloads' ), + 'check' => __( 'Check this box to allow entry of product SKUs.', 'easy-digital-downloads' ), + 'desc' => __( 'SKUs will be shown on purchase receipt and exported purchase histories.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + 'enable_sequential' => array( + 'id' => 'enable_sequential', + 'name' => __( 'Enable Sequental Numbering', 'easy-digital-downloads' ), + 'check' => __( 'Check this box to enable sequential order numbers.', 'easy-digital-downloads' ), + 'desc' => __( 'Does not impact previous orders. Future orders will be sequential.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + 'sequential_start' => $this->get_sequential_start(), + 'sequential_prefix' => array( + 'id' => 'sequential_prefix', + 'name' => __( 'Sequential Number Prefix', 'easy-digital-downloads' ), + 'desc' => __( 'A prefix to prepend to all sequential order numbers.', 'easy-digital-downloads' ), + 'type' => 'text', + ), + 'sequential_postfix' => array( + 'id' => 'sequential_postfix', + 'name' => __( 'Sequential Number Postfix', 'easy-digital-downloads' ), + 'desc' => __( 'A postfix to append to all sequential order numbers.', 'easy-digital-downloads' ), + 'type' => 'text', + ), + ); + + if ( ! defined( 'EDD_SON_VERSION' ) ) { + $settings['sequential_help'] = array( + 'id' => 'sequential_help', + 'name' => __( 'Advanced Order Numbers', 'easy-digital-downloads' ), + 'desc' => $this->get_sequential_help_text(), + 'type' => 'descriptive_text', + ); + } + + return $settings; + } + + /** + * Gets the sequential starting number setting. + * + * @since 3.1.4 + * @return array + */ + private function get_sequential_start() { + + $setting = array( + 'id' => 'sequential_start', + 'name' => __( 'Sequential Starting Number', 'easy-digital-downloads' ), + 'desc' => __( 'The number at which the sequence should begin.', 'easy-digital-downloads' ), + 'type' => 'number', + 'size' => 'small', + 'std' => 1, + ); + + if ( (bool) get_option( 'edd_next_order_number' ) ) { + $order_number = new \EDD\Orders\Number(); + $setting['desc'] = __( 'Once sequential order numbering is active, the starting number cannot be updated to an order number that\'s smaller than the highest order number. Update this with care.', 'easy-digital-downloads' ) . + '
    ' . + sprintf( + /* translators: %s: next order number, wrapped in code tags */ + __( 'The next order number will be %s.', 'easy-digital-downloads' ), + '' . $order_number->format( get_option( 'edd_next_order_number' ) ) . '' + ); + } + + return $setting; + } + + /** + * Gets the sequential help text. + * + * @since 3.1.4 + * @return string + */ + private function get_sequential_help_text() { + $text = __( 'Gain access to even more control over your order numbering!', 'easy-digital-downloads' ); + $benefits = array( + __( 'Track free orders in a separate sequential series', 'easy-digital-downloads' ), + __( 'Assign temporary numbers to incomplete orders', 'easy-digital-downloads' ), + __( 'Abandoned orders do not interrupt the complete order series', 'easy-digital-downloads' ), + ); + $text .= '
      '; + foreach ( $benefits as $benefit ) { + $text .= '
    • ' . $benefit . '
    • '; + } + $text .= '
    '; + + if ( ! edd_is_pro() ) { + + $url = edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade/', + array( + 'utm_medium' => 'accounting-settings', + 'utm_content' => 'upgrade-to-pro', + ) + ); + + $text .= sprintf( '' . __( 'Upgrade to Pro', 'easy-digital-downloads' ) . '', $url ); + } else { + $text .= sprintf( + /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag */ + __( 'Access %1$sAdvanced Sequential Order Numbers%2$s today.', 'easy-digital-downloads' ), + '', + '' + ); + } + + return $text; + } +} diff --git a/src/Admin/Settings/Tabs/General.php b/src/Admin/Settings/Tabs/General.php new file mode 100644 index 00000000000..9f10cbfdfba --- /dev/null +++ b/src/Admin/Settings/Tabs/General.php @@ -0,0 +1,250 @@ + array( + 'business_settings' => array( + 'id' => 'business_settings', + 'name' => '

    ' . __( 'Business Info', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'Business Information', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Easy Digital Downloads uses the following business information for things like pre-populating tax fields, and connecting third-party services with the same information.', 'easy-digital-downloads' ), + ), + 'entity_name' => array( + 'id' => 'entity_name', + 'name' => __( 'Business Name', 'easy-digital-downloads' ), + 'desc' => __( 'The official (legal) name of your store. Defaults to Site Title if empty.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => $this->get_site_name(), + 'placeholder' => $this->get_site_name(), + ), + 'entity_type' => array( + 'id' => 'entity_type', + 'name' => __( 'Business Type', 'easy-digital-downloads' ), + 'desc' => __( 'Choose "Individual" if you do not have an official/legal business ID, or "Company" if a registered business entity exists.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => array( + 'individual' => esc_html__( 'Individual', 'easy-digital-downloads' ), + 'company' => esc_html__( 'Company', 'easy-digital-downloads' ), + ), + ), + 'business_address' => array( + 'id' => 'business_address', + 'name' => __( 'Business Address', 'easy-digital-downloads' ), + 'type' => 'text', + 'placeholder' => '', + ), + 'business_address_2' => array( + 'id' => 'business_address_2', + 'name' => __( 'Business Address (Extra)', 'easy-digital-downloads' ), + 'type' => 'text', + 'placeholder' => '', + ), + 'business_city' => array( + 'id' => 'business_city', + 'name' => __( 'Business City', 'easy-digital-downloads' ), + 'type' => 'text', + 'placeholder' => '', + ), + 'business_postal_code' => array( + 'id' => 'business_postal_code', + 'name' => __( 'Business Postal Code', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'medium', + 'placeholder' => '', + ), + 'base_country' => array( + 'id' => 'base_country', + 'name' => __( 'Business Country', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => edd_get_country_list(), + 'chosen' => true, + 'field_class' => 'edd_countries_filter', + 'placeholder' => __( 'Select a country', 'easy-digital-downloads' ), + 'data' => array( + 'nonce' => wp_create_nonce( 'edd-country-field-nonce' ), + ), + ), + 'base_state' => array( + 'id' => 'base_state', + 'name' => __( 'Business Region', 'easy-digital-downloads' ), + 'type' => 'shop_states', + 'chosen' => true, + 'field_class' => 'edd_regions_filter', + 'placeholder' => __( 'Select a region', 'easy-digital-downloads' ), + ), + ), + 'pages' => array( + 'page_settings' => array( + 'id' => 'page_settings', + 'name' => '

    ' . __( 'Pages', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'Page Settings', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Easy Digital Downloads uses the pages below for handling the display of checkout, purchase confirmation, purchase history, and purchase failures. If pages are deleted or removed in some way, they can be recreated manually from the Pages menu. When re-creating the pages, enter the shortcode shown in the page content area.', 'easy-digital-downloads' ), + ), + 'purchase_page' => array( + 'id' => 'purchase_page', + 'name' => __( 'Primary Checkout Page', 'easy-digital-downloads' ), + 'desc' => __( 'This is the checkout page where buyers will complete their purchases.
    The [download_checkout] shortcode must be on this page.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => $pages, + 'chosen' => true, + 'placeholder' => __( 'Select a page', 'easy-digital-downloads' ), + ), + 'success_page' => array( + 'id' => 'success_page', + 'name' => __( 'Success Page', 'easy-digital-downloads' ), + 'desc' => __( 'This is the page buyers are sent to after completing their purchases.
    The [edd_receipt] shortcode should be on this page.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => $pages, + 'chosen' => true, + 'placeholder' => __( 'Select a page', 'easy-digital-downloads' ), + ), + 'failure_page' => array( + 'id' => 'failure_page', + 'name' => __( 'Failed Transaction Page', 'easy-digital-downloads' ), + 'desc' => __( 'This is the page buyers are sent to if their transaction is cancelled or fails.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => $pages, + 'chosen' => true, + 'placeholder' => __( 'Select a page', 'easy-digital-downloads' ), + ), + 'purchase_history_page' => array( + 'id' => 'purchase_history_page', + 'name' => __( 'Purchase History Page', 'easy-digital-downloads' ), + 'desc' => __( 'This page shows a complete purchase history for the current user, including download links.
    The [purchase_history] shortcode should be on this page.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => $pages, + 'chosen' => true, + 'placeholder' => __( 'Select a page', 'easy-digital-downloads' ), + ), + 'login_redirect_page' => array( + 'id' => 'login_redirect_page', + 'name' => __( 'Login Redirect Page', 'easy-digital-downloads' ), + 'desc' => sprintf( + /* translators: %s: home URL */ + __( 'If a customer logs in using the [edd_login] shortcode, this is the page they will be redirected to.
    Note: override using the redirect shortcode attribute: [edd_login redirect="%s"].', 'easy-digital-downloads' ), + trailingslashit( home_url() ) + ), + 'type' => 'select', + 'options' => $pages, + 'chosen' => true, + 'placeholder' => __( 'Select a page', 'easy-digital-downloads' ), + ), + ), + 'currency' => array( + 'currency_settings' => array( + 'id' => 'currency_settings', + 'name' => '

    ' . __( 'Currency', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'Currency Settings', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Different countries use different formatting for their currency. You will want to pick what most of your users will expect to use.', 'easy-digital-downloads' ), + ), + 'currency' => array( + 'id' => 'currency', + 'name' => __( 'Currency', 'easy-digital-downloads' ), + 'desc' => __( 'Choose your currency. Note that some payment gateways have currency restrictions.', 'easy-digital-downloads' ), + 'type' => 'select', + 'chosen' => true, + 'options' => edd_get_currencies(), + ), + 'currency_position' => array( + 'id' => 'currency_position', + 'name' => __( 'Currency Position', 'easy-digital-downloads' ), + 'desc' => __( 'Choose the location of the currency sign.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => array( + 'before' => __( 'Before ($10)', 'easy-digital-downloads' ), + 'after' => __( 'After (10$)', 'easy-digital-downloads' ), + ), + ), + 'thousands_separator' => array( + 'id' => 'thousands_separator', + 'name' => __( 'Thousandths Separator', 'easy-digital-downloads' ), + 'desc' => __( 'The symbol to separate thousandths. Usually , or ..', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'small', + 'field_class' => 'code', + 'std' => ',', + 'placeholder' => ',', + ), + 'decimal_separator' => array( + 'id' => 'decimal_separator', + 'name' => __( 'Decimal Separator', 'easy-digital-downloads' ), + 'desc' => __( 'The symbol to separate decimal points. Usually , or ..', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'small', + 'field_class' => 'code', + 'std' => '.', + 'placeholder' => '.', + ), + ), + 'api' => array( + 'api_settings' => array( + 'id' => 'api_settings', + 'name' => '

    ' . __( 'API', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'API Settings', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'The Easy Digital Downloads REST API provides access to store data through our API endpoints. Enable this setting if you would like all user accounts to be able to generate their own API keys.', 'easy-digital-downloads' ), + ), + 'api_allow_user_keys' => array( + 'id' => 'api_allow_user_keys', + 'name' => __( 'Allow User Keys', 'easy-digital-downloads' ), + 'check' => __( 'Check this box to allow all users to generate API keys.', 'easy-digital-downloads' ), + 'desc' => __( 'Users who can manage_shop_settings are always allowed to generate keys.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + 'api_help' => array( + 'id' => 'api_help', + 'desc' => sprintf( + /* translators: %s: API documentation URL */ + __( 'Visit the REST API documentation for further information.', 'easy-digital-downloads' ), + edd_link_helper( + 'https://easydigitaldownloads.com/categories/docs/api-reference/', + array( + 'utm_medium' => 'settings', + 'utm_content' => 'api-documentation', + ) + ) + ), + 'type' => 'descriptive_text', + ), + ), + ); + } +} diff --git a/src/Admin/Settings/Tabs/Licenses.php b/src/Admin/Settings/Tabs/Licenses.php new file mode 100644 index 00000000000..918caf8cb29 --- /dev/null +++ b/src/Admin/Settings/Tabs/Licenses.php @@ -0,0 +1,31 @@ + array( + 'recapture' => array( + 'id' => 'recapture', + 'name' => __( 'Abandoned Cart Recovery', 'easy-digital-downloads' ), + 'desc' => '', + 'type' => 'recapture', + ), + 'allow_multiple_discounts' => array( + 'id' => 'allow_multiple_discounts', + 'name' => __( 'Multiple Discounts', 'easy-digital-downloads' ), + 'desc' => __( 'Allow customers to use multiple discounts on the same purchase?', 'easy-digital-downloads' ), + 'type' => 'checkbox', + ), + ), + ); + } +} diff --git a/src/Admin/Settings/Tabs/Misc.php b/src/Admin/Settings/Tabs/Misc.php new file mode 100644 index 00000000000..244ada56def --- /dev/null +++ b/src/Admin/Settings/Tabs/Misc.php @@ -0,0 +1,212 @@ + array( + 'debug_mode' => array( + 'id' => 'debug_mode', + 'name' => __( 'Debug Mode', 'easy-digital-downloads' ), + 'check' => __( 'Enabled', 'easy-digital-downloads' ), + 'desc' => __( 'Check this box to enable Debug Mode.', 'easy-digital-downloads' ) . ' ' . $this->get_debug_log_link(), + 'type' => 'checkbox_description', + ), + 'disable_styles' => array( + 'id' => 'disable_styles', + 'name' => __( 'Disable Styles', 'easy-digital-downloads' ), + 'check' => __( 'Check this box to disable all included styling.', 'easy-digital-downloads' ), + 'desc' => __( 'This includes buttons, checkout fields, product pages, and all other elements', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + 'tooltip_title' => __( 'Disabling Styles', 'easy-digital-downloads' ), + 'tooltip_desc' => __( "If your theme has a complete custom CSS file for Easy Digital Downloads, you may wish to disable our default styles. This is not recommended unless you're sure your theme has a complete custom CSS.", 'easy-digital-downloads' ), + ), + 'item_quantities' => array( + 'id' => 'item_quantities', + 'name' => __( 'Cart Item Quantities', 'easy-digital-downloads' ), + /* translators: %s is the plural label for downloads */ + 'desc' => sprintf( __( 'Allow quantities to be adjusted when adding %s to the cart, and while viewing the checkout cart.', 'easy-digital-downloads' ), edd_get_label_plural( true ) ), + 'type' => 'checkbox', + ), + 'uninstall_on_delete' => array( + 'id' => 'uninstall_on_delete', + 'name' => __( 'Remove Data on Uninstall', 'easy-digital-downloads' ), + 'desc' => __( 'Check this box if you would like EDD to completely remove all of its data when the plugin is deleted.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + ), + ), + 'button_text' => array( + 'button_style' => array( + 'id' => 'button_style', + 'name' => __( 'Default Button Style', 'easy-digital-downloads' ), + 'desc' => __( 'Choose the style you want to use for the buttons.', 'easy-digital-downloads' ), + 'type' => 'select', + 'options' => edd_get_button_styles(), + ), + 'checkout_color' => array( + 'id' => 'checkout_color', + 'name' => __( 'Default Button Color', 'easy-digital-downloads' ), + 'desc' => __( 'Choose the color you want to use for the buttons.', 'easy-digital-downloads' ), + 'type' => 'color_select', + 'options' => edd_get_button_colors(), + 'std' => 'blue', + ), + 'checkout_label' => array( + 'id' => 'checkout_label', + 'name' => __( 'Complete Purchase Text', 'easy-digital-downloads' ), + 'desc' => __( 'The button label for completing a purchase.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => __( 'Purchase', 'easy-digital-downloads' ), + ), + 'free_checkout_label' => array( + 'id' => 'free_checkout_label', + 'name' => __( 'Complete Free Purchase Text', 'easy-digital-downloads' ), + 'desc' => __( 'The button label for completing a free purchase.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => __( 'Free Download', 'easy-digital-downloads' ), + ), + 'add_to_cart_text' => array( + 'id' => 'add_to_cart_text', + 'name' => __( 'Add to Cart Text', 'easy-digital-downloads' ), + 'desc' => __( 'Text shown on the Add to Cart Buttons.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => __( 'Add to Cart', 'easy-digital-downloads' ), + ), + 'checkout_button_text' => array( + 'id' => 'checkout_button_text', + 'name' => __( 'Checkout Button Text', 'easy-digital-downloads' ), + 'desc' => __( 'Text shown on the Add to Cart Button when the product is already in the cart.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => _x( 'Checkout', 'text shown on the Add to Cart Button when the product is already in the cart', 'easy-digital-downloads' ), + ), + 'buy_now_text' => $this->get_buy_now_text(), + ), + 'file_downloads' => array( + 'require_login_to_download' => array( + 'id' => 'require_login_to_download', + 'name' => __( 'Require Login', 'easy-digital-downloads' ), + 'desc' => __( 'Require a user to login before file download links deliver the file.', 'easy-digital-downloads' ), + 'tooltip_title' => __( 'Require Login', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Download links expire after the link expiration setting, but you can restrict file downloads to only logged in users. Note: This may affect links from purchase receipts and customers if you have guest checkout enabled.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + ), + 'download_method' => array( + 'id' => 'download_method', + 'name' => __( 'Download Method', 'easy-digital-downloads' ), + 'desc' => sprintf( __( 'Select the file download method. Note, not all methods work on all servers.', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'type' => 'select', + 'tooltip_title' => __( 'Download Method', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Due to its consistency in multiple platforms and better file protection, \'forced\' is the default method. Because Easy Digital Downloads uses PHP to process the file with the \'forced\' method, larger files can cause problems with delivery, resulting in hitting the \'max execution time\' of the server. If users are getting 404 or 403 errors when trying to access their purchased files when using the \'forced\' method, changing to the \'redirect\' method can help resolve this.', 'easy-digital-downloads' ), + 'options' => array( + 'direct' => __( 'Forced', 'easy-digital-downloads' ), + 'redirect' => __( 'Redirect', 'easy-digital-downloads' ), + ), + ), + 'symlink_file_downloads' => array( + 'id' => 'symlink_file_downloads', + 'name' => __( 'Symbolically Link Files', 'easy-digital-downloads' ), + 'desc' => __( 'Check this if you are delivering really large files or having problems with file downloads completing.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + ), + 'file_download_limit' => array( + 'id' => 'file_download_limit', + 'name' => __( 'File Download Limit', 'easy-digital-downloads' ), + /* translators: %s is the singular label for a download */ + 'desc' => sprintf( __( 'The maximum number of times files can be downloaded for purchases. Can be overwritten for each %s.', 'easy-digital-downloads' ), edd_get_label_singular() ), + 'type' => 'number', + 'size' => 'small', + 'tooltip_title' => __( 'File Download Limits', 'easy-digital-downloads' ), + /* translators: %s is the singular label for a download */ + 'tooltip_desc' => sprintf( __( 'Set the global default for the number of times a customer can download items they purchase. Using a value of 0 is unlimited. This can be defined on a %s-specific level as well. Download limits can also be reset for an individual purchase.', 'easy-digital-downloads' ), edd_get_label_singular( true ) ), + ), + 'download_link_expiration' => array( + 'id' => 'download_link_expiration', + 'name' => __( 'Download Link Expiration', 'easy-digital-downloads' ), + 'desc' => __( 'How long should download links be valid for? Default is 24 hours from the time they are generated. Enter a time in hours.', 'easy-digital-downloads' ), + 'tooltip_title' => __( 'Download Link Expiration', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'When a customer receives a link to their downloads via email, in their receipt, or in their purchase history, the link will only be valid for the timeframe (in hours) defined in this setting. Sending a new purchase receipt or visiting the account page will re-generate a valid link for the customer.', 'easy-digital-downloads' ), + 'type' => 'number', + 'size' => 'small', + 'std' => '24', + 'min' => '0', + ), + 'disable_redownload' => array( + 'id' => 'disable_redownload', + 'name' => __( 'Disable Redownload', 'easy-digital-downloads' ), + 'desc' => __( 'Check this if you do not want to allow users to redownload items from their purchase history.', 'easy-digital-downloads' ), + 'type' => 'checkbox', + ), + ), + ); + } + + /** + * Gets the buy now text setting. + * + * @since 3.1.4 + * @return array + */ + private function get_buy_now_text() { + $text = array( + 'id' => 'buy_now_text', + 'name' => __( 'Buy Now Text', 'easy-digital-downloads' ), + 'desc' => __( 'Text shown on the Buy Now Buttons.', 'easy-digital-downloads' ), + 'type' => 'text', + 'std' => __( 'Buy Now', 'easy-digital-downloads' ), + ); + + if ( edd_shop_supports_buy_now() ) { + return $text; + } + + $text['disabled'] = true; + $text['tooltip_title'] = __( 'Buy Now Disabled', 'easy-digital-downloads' ); + $text['tooltip_desc'] = __( 'Buy Now buttons are only available for stores that have a single supported gateway active and that do not use taxes.', 'easy-digital-downloads' ); + + return $text; + } + + /** + * Gets the link for the debug log. + * + * @since 3.1.4 + * @return string + */ + private function get_debug_log_link() { + $debug_log_url = edd_get_admin_url( + array( + 'page' => 'edd-tools', + 'tab' => 'debug_log', + ) + ); + + return '' . __( 'View the Log', 'easy-digital-downloads' ) . ''; + } +} diff --git a/src/Admin/Settings/Tabs/Privacy.php b/src/Admin/Settings/Tabs/Privacy.php new file mode 100644 index 00000000000..0e875961a33 --- /dev/null +++ b/src/Admin/Settings/Tabs/Privacy.php @@ -0,0 +1,187 @@ + array( + '' => array( + 'id' => 'privacy_settings', + 'name' => '

    ' . __( 'Privacy Policy', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'Privacy Policy Settings', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Depending on legal and regulatory requirements, it may be necessary for your site to show a checkbox for agreement to a privacy policy.', 'easy-digital-downloads' ), + ), + 'show_agree_to_privacy_policy' => array( + 'id' => 'show_agree_to_privacy_policy', + 'name' => __( 'Agreement', 'easy-digital-downloads' ), + 'check' => __( 'Check this box to show an "Agree to Privacy Policy" checkbox on checkout.', 'easy-digital-downloads' ), + 'desc' => __( 'Customers must agree to your privacy policy before purchasing.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + 'agree_privacy_label' => array( + 'id' => 'privacy_agree_label', + 'name' => __( 'Agreement Label', 'easy-digital-downloads' ), + 'desc' => __( 'Label for the "Agree to Privacy Policy" checkbox.', 'easy-digital-downloads' ), + 'type' => 'text', + 'placeholder' => __( 'I agree to the privacy policy', 'easy-digital-downloads' ), + 'size' => 'regular', + ), + 'show_privacy_policy_on_checkout' => array( + 'id' => 'show_privacy_policy_on_checkout', + 'name' => __( 'Privacy Policy on Checkout', 'easy-digital-downloads' ), + 'check' => __( 'Display the entire Privacy Policy at checkout.', 'easy-digital-downloads' ) . ' ' . __( 'Set your Privacy Policy here', 'easy-digital-downloads' ) . '.', + 'desc' => + __( 'Display your Privacy Policy on checkout.', 'easy-digital-downloads' ) . ' ' . __( 'Set your Privacy Policy here', 'easy-digital-downloads' ) . '.' . + /* translators: %1$s: Opening anchor tag, %2$s: Closing anchor tag. */ + '

    ' . sprintf( __( 'Need help creating a Privacy Policy? We recommend %1$sTermageddon%2$s.', 'easy-digital-downloads' ), '', '' ) . '

    ', + 'type' => 'checkbox', + ), + ), + 'site_terms' => array( + '' => array( + 'id' => 'terms_settings', + 'name' => '

    ' . __( 'Terms & Agreements', 'easy-digital-downloads' ) . '

    ', + 'desc' => '', + 'type' => 'header', + 'tooltip_title' => __( 'Terms & Agreements Settings', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'Depending on legal and regulatory requirements, it may be necessary for your site to show checkbox for agreement to terms.', 'easy-digital-downloads' ), + ), + 'show_agree_to_terms' => array( + 'id' => 'show_agree_to_terms', + 'name' => __( 'Agreement', 'easy-digital-downloads' ), + 'check' => __( 'Check this box to show an "Agree to Terms" checkbox on checkout.', 'easy-digital-downloads' ), + 'desc' => + __( 'Check this to show an agree to terms on checkout that users must agree to before purchasing.', 'easy-digital-downloads' ) . + '

    ' . + sprintf( + /* translators: 1: Opening anchor tag, 2: Closing anchor tag. */ + __( 'Need help creating a Terms of Agreement? We recommend using %1$sTermageddon%2$s.', 'easy-digital-downloads' ), + '', + '' + ) . + '

    ', + 'type' => 'checkbox_description', + ), + 'agree_label' => array( + 'id' => 'agree_label', + 'name' => __( 'Agreement Label', 'easy-digital-downloads' ), + 'desc' => __( 'Label for the "Agree to Terms" checkbox.', 'easy-digital-downloads' ), + 'placeholder' => __( 'I agree to the terms', 'easy-digital-downloads' ), + 'type' => 'text', + 'size' => 'regular', + ), + 'agree_text' => array( + 'id' => 'agree_text', + 'name' => __( 'Agreement Text', 'easy-digital-downloads' ), + 'type' => 'rich_editor', + ), + ), + 'export_erase' => $this->get_export_erase(), + ); + } + + /** + * Get the export and erase settings. + * + * @since 3.1.4 + * @return array + */ + private function get_export_erase() { + $export_erase = array( + array( + 'id' => 'payment_privacy_status_action_header', + 'name' => '

    ' . __( 'Order Statuses', 'easy-digital-downloads' ) . '

    ', + 'type' => 'header', + 'desc' => __( 'When a user requests to be anonymized or removed from a site, these are the actions that will be taken on payments associated with their customer, by status.', 'easy-digital-downloads' ), + 'tooltip_title' => __( 'What settings should I use?', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'By default, Easy Digital Downloads sets suggested actions based on the Payment Status. These are purely recommendations, and you may need to change them to suit your store\'s needs. If you are unsure, you can safely leave these settings as is.', 'easy-digital-downloads' ), + ), + array( + 'id' => 'payment_privacy_status_action_text', + 'name' => __( 'Rules', 'easy-digital-downloads' ), + 'type' => 'descriptive_text', + 'desc' => __( 'When a user wants their order history anonymized or removed, the following rules will be used:', 'easy-digital-downloads' ), + ), + ); + + $options = array( + 'none' => __( 'Do Nothing', 'easy-digital-downloads' ), + 'anonymize' => __( 'Anonymize', 'easy-digital-downloads' ), + 'delete' => __( 'Delete', 'easy-digital-downloads' ), + ); + $payment_statuses = edd_get_payment_statuses(); + + // Add Privacy settings for statuses + foreach ( $payment_statuses as $status => $label ) { + + $export_erase[] = array( + 'id' => "payment_privacy_status_action_{$status}", + 'name' => $label, + 'desc' => '', + 'type' => 'select', + 'std' => $this->get_action( $status ), + 'options' => $options, + ); + } + + return $export_erase; + } + + /** + * Get the default action to take for a given status. + * + * @since 3.1.4 + * @param string $status The status to get the action for. + * @return string + */ + private function get_action( $status ) { + switch ( $status ) { + case 'complete': + case 'refunded': + case 'revoked': + $action = 'anonymize'; + break; + + case 'failed': + case 'abandoned': + $action = 'delete'; + break; + + case 'pending': + case 'processing': + default: + $action = 'none'; + break; + } + + return $action; + } +} diff --git a/src/Admin/Settings/Tabs/Tab.php b/src/Admin/Settings/Tabs/Tab.php new file mode 100644 index 00000000000..7732ccebd24 --- /dev/null +++ b/src/Admin/Settings/Tabs/Tab.php @@ -0,0 +1,81 @@ +get_id(), $this->register() ); + } + + /** + * Get the ID for this tab. + * + * @since 3.1.4 + * @return string + */ + protected function get_id() { + try { + if ( empty( $this->id ) ) { + // translators: %s: Tab class name. + throw new \Exception( sprintf( __( 'The %s settings class is missing the required ID property.', 'easy-digital-downloads' ), get_class( $this ) ) ); + } + } catch ( \Exception $e ) { + wp_die( esc_html( $e->getMessage() ) ); + } + + return $this->id; + } + + /** + * Register the settings for this tab. + * + * @since 3.1.4 + * @return array + */ + abstract protected function register(); + + /** + * Gets the site name. + * + * @since 3.1.4 + * @return string + */ + protected function get_site_name() { + return get_bloginfo( 'name' ); + } + + /** + * Gets the admin email address. + * + * @since 3.1.4 + * @return string + */ + protected function get_admin_email() { + return get_bloginfo( 'admin_email' ); + } +} diff --git a/src/Admin/Settings/Tabs/Taxes.php b/src/Admin/Settings/Tabs/Taxes.php new file mode 100644 index 00000000000..810bfeab6c2 --- /dev/null +++ b/src/Admin/Settings/Tabs/Taxes.php @@ -0,0 +1,124 @@ + array( + 'enable_taxes' => array( + 'id' => 'enable_taxes', + 'name' => __( 'Taxes', 'easy-digital-downloads' ), + 'check' => __( 'Enabled', 'easy-digital-downloads' ), + 'desc' => __( 'Check this to enable taxes on purchases.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + 'tooltip_title' => __( 'Enabling Taxes', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'With taxes enabled, customers will be taxed based on the rates you define, and are required to input their address on checkout so rates can be calculated accordingly.', 'easy-digital-downloads' ), + ), + 'tax_help' => array( + 'id' => 'tax_help', + 'name' => '', + /* translators: %s - tax setup documentation URL. */ + 'desc' => sprintf( __( 'Visit the Tax setup documentation for further information.

    If you need VAT support, there are options listed on the documentation page.

    ', 'easy-digital-downloads' ), 'https://easydigitaldownloads.com/docs/tax-settings/' ), + 'type' => 'descriptive_text', + ), + 'prices_include_tax' => array( + 'id' => 'prices_include_tax', + 'name' => __( 'Prices Include Tax', 'easy-digital-downloads' ), + 'desc' => __( 'This option affects how you enter prices.', 'easy-digital-downloads' ), + 'type' => 'radio', + 'std' => 'no', + 'options' => array( + 'yes' => __( 'Yes, I will enter prices inclusive of tax', 'easy-digital-downloads' ), + 'no' => __( 'No, I will enter prices exclusive of tax', 'easy-digital-downloads' ), + ), + 'tooltip_title' => __( 'Prices Inclusive of Tax', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'When using prices inclusive of tax, you will be entering your prices as the total amount you want a customer to pay for the download, including tax. Easy Digital Downloads will calculate the proper amount to tax the customer for the defined total price.', 'easy-digital-downloads' ), + ), + 'display_tax_rate' => array( + 'id' => 'display_tax_rate', + 'name' => __( 'Show Tax Rate on Prices', 'easy-digital-downloads' ), + 'check' => __( 'Show', 'easy-digital-downloads' ), + 'desc' => __( 'Some countries require a notice that product prices include tax.', 'easy-digital-downloads' ), + 'type' => 'checkbox_description', + ), + 'checkout_include_tax' => array( + 'id' => 'checkout_include_tax', + 'name' => __( 'Show in Checkout', 'easy-digital-downloads' ), + 'desc' => __( 'Should prices on the checkout page be shown with or without tax?', 'easy-digital-downloads' ), + 'type' => 'select', + 'std' => 'no', + 'options' => array( + 'yes' => __( 'Including tax', 'easy-digital-downloads' ), + 'no' => __( 'Excluding tax', 'easy-digital-downloads' ), + ), + 'tooltip_title' => __( 'Taxes Displayed for Products on Checkout', 'easy-digital-downloads' ), + 'tooltip_desc' => __( 'This option will determine whether the product price displays with or without tax on checkout.', 'easy-digital-downloads' ), + ), + ), + 'rates' => $this->get_rates(), + ); + } + + /** + * Get the tax rates settings. + * + * @since 3.1.4 + * @return array + */ + private function get_rates() { + + $rates = array( + 'tax_rates' => array( + 'id' => 'tax_rates', + 'name' => '' . __( 'Regional Rates', 'easy-digital-downloads' ) . '', + 'desc' => __( 'Configure rates for each region you wish to collect sales tax in.', 'easy-digital-downloads' ), + 'type' => 'tax_rates', + ), + ); + + if ( false === edd_get_option( 'tax_rate' ) ) { + return $rates; + } + + // Show a disabled "Default Rate" in "Tax Rates" if the value is not 0. + return array_merge( + array( + 'tax_rate' => array( + 'id' => 'tax_rate', + 'type' => 'tax_rate', + 'name' => __( 'Default Rate', 'easy-digital-downloads' ), + 'desc' => ( + '

    ' . __( 'This setting is no longer used in this version of Easy Digital Downloads. We have migrated any fallback tax rates for you to verify below. Click "Save Changes" to dismiss this notice.', 'easy-digital-downloads' ) . '

    ' + ), + ), + ), + $rates + ); + } +} diff --git a/src/Admin/Settings/WP_SMTP.php b/src/Admin/Settings/WP_SMTP.php new file mode 100644 index 00000000000..04da6b504db --- /dev/null +++ b/src/Admin/Settings/WP_SMTP.php @@ -0,0 +1,242 @@ + 'wp-mail-smtp/wp_mail_smtp.php', + 'lite_wporg_url' => 'https://wordpress.org/plugins/wp-mail-smtp/', + 'lite_download_url' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip', + 'pro_plugin' => 'wp-mail-smtp-pro/wp_mail_smtp.php', + 'smtp_settings' => 'admin.php?page=wp-mail-smtp', + 'smtp_wizard' => 'admin.php?page=wp-mail-smtp-setup-wizard', + ); + + /** + * The Extension Manager + * + * @var \EDD\Admin\Extensions\Extension_Manager + */ + private $manager; + + public function __construct() { + $this->manager = new \EDD\Admin\Extensions\Extension_Manager(); + } + + /** + * Returns an array of events that this subscriber wants to listen to. + * + * @since 3.1.1 + * @return array + */ + public static function get_subscribed_events() { + return array( + 'edd_settings_emails' => 'register_setting', + 'edd_wpsmtp' => 'settings_field', + ); + } + + /** + * Register the setting to show the WP SMTP installer if it isn't active. + * + * @param array $settings + * @return array + */ + public function register_setting( $settings ) { + if ( $this->is_smtp_configured() ) { + return $settings; + } + $settings['main']['wpsmtp'] = array( + 'id' => 'wpsmtp', + 'name' => __( 'Improve Email Deliverability', 'easy-digital-downloads' ), + 'desc' => '', + 'type' => 'hook', + ); + + return $settings; + } + + /** + * Output the settings field (installation helper). + * + * @param array $args + * @return void + */ + public function settings_field( $args ) { + $this->manager->enqueue(); + ?> +
    +

    + +

    + +
    +
    + manager->button( $this->get_button_parameters() ); ?> +
    + + is_smtp_activated() ) { + return; + } + ?> + +
    +
    + manager->is_plugin_installed( $this->config['lite_plugin'] ) && ! $this->manager->is_plugin_installed( $this->config['pro_plugin'] ) ) { + $button['plugin'] = $this->config['lite_download_url']; + $button['action'] = 'install'; + $button['button_text'] = __( 'Install & Activate WP Mail SMTP', 'easy-digital-downloads' ); + } elseif ( ! $this->is_smtp_activated() ) { + // If one of the SMTP plugins is installed, but not activated, the button will prompt to activate it. + $button['plugin'] = $this->config['lite_plugin']; + $button['action'] = 'activate'; + $button['button_text'] = __( 'Activate WP Mail SMTP', 'easy-digital-downloads' ); + } elseif ( ! $this->is_smtp_configured() ) { + // If the plugin is active, but not configured, the button will send them to the setup wizard. + $button = $this->get_link_parameters(); + } + + return $button; + } + + /** + * Gets the array of parameters for the link to configure WP Mail SMTP. + * + * @since 2.11.4 + * @return array + */ + private function get_link_parameters() { + return $this->is_smtp_configured() ? + array( + 'button_text' => __( 'Configure WP Mail SMTP', 'easy-digital-downloads' ), + 'href' => admin_url( $this->config['smtp_settings'] ), + ) : + array( + 'button_text' => __( 'Run the WP Mail SMTP Setup Wizard', 'easy-digital-downloads' ), + 'href' => admin_url( $this->config['smtp_wizard'] ), + ); + } + + /** + * Whether WP Mail SMTP plugin configured or not. + * + * @since 2.11.4 + * + * @return bool True if some mailer is selected and configured properly. + */ + protected function is_smtp_configured() { + + if ( ! $this->is_smtp_activated() || ! class_exists( '\\WPMailSMTP\\Options' ) ) { + return false; + } + + $phpmailer = $this->get_phpmailer(); + + $mailer = \WPMailSMTP\Options::init()->get( 'mail', 'mailer' ); + $is_mailer_complete = ! empty( $mailer ) && wp_mail_smtp()->get_providers()->get_mailer( $mailer, $phpmailer )->is_mailer_complete(); + + return 'mail' !== $mailer && $is_mailer_complete; + } + + /** + * Whether WP Mail SMTP plugin active or not. + * + * @since 2.11.4 + * + * @return bool True if SMTP plugin is active. + */ + protected function is_smtp_activated() { + return function_exists( 'wp_mail_smtp' ) && ( is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ) ); + } + + /** + * Get $phpmailer instance. + * + * @since 2.11.4 + * + * @return \PHPMailer|\PHPMailer\PHPMailer\PHPMailer Instance of PHPMailer. + */ + protected function get_phpmailer() { + + if ( version_compare( get_bloginfo( 'version' ), '5.5-alpha', '<' ) ) { + $phpmailer = $this->get_phpmailer_v5(); + } else { + $phpmailer = $this->get_phpmailer_v6(); + } + + return $phpmailer; + } + + /** + * Get $phpmailer v5 instance. + * + * @since 2.11.4 + * + * @return \PHPMailer Instance of PHPMailer. + */ + private function get_phpmailer_v5() { + + global $phpmailer; + + if ( ! ( $phpmailer instanceof \PHPMailer ) ) { + require_once ABSPATH . WPINC . '/class-phpmailer.php'; + require_once ABSPATH . WPINC . '/class-smtp.php'; + $phpmailer = new \PHPMailer( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } + + return $phpmailer; + } + + /** + * Get $phpmailer v6 instance. + * + * @since 2.11.4 + * + * @return \PHPMailer\PHPMailer\PHPMailer Instance of PHPMailer. + */ + private function get_phpmailer_v6() { + + global $phpmailer; + + if ( ! ( $phpmailer instanceof \PHPMailer\PHPMailer\PHPMailer ) ) { + require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php'; + require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php'; + require_once ABSPATH . WPINC . '/PHPMailer/Exception.php'; + $phpmailer = new \PHPMailer\PHPMailer\PHPMailer( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } + + return $phpmailer; + } +} diff --git a/src/Admin/SiteHealth/Direct.php b/src/Admin/SiteHealth/Direct.php new file mode 100644 index 00000000000..bd292482ed9 --- /dev/null +++ b/src/Admin/SiteHealth/Direct.php @@ -0,0 +1,324 @@ + __( 'EDD Checkout Page', 'easy-digital-downloads' ), + 'test' => array( $this, 'get_test_missing_purchase_page' ), + ); + + $tests['edd_uploads_url_protected'] = array( + 'label' => __( 'Protected Download Files', 'easy-digital-downloads' ), + 'test' => array( $this, 'get_test_uploads_url_protected' ), + 'skip_cron' => true, + ); + + $tests['edd_gateways_enabled'] = array( + 'label' => __( 'Enabled Gateways', 'easy-digital-downloads' ), + 'test' => array( $this, 'get_test_gateways_enabled' ), + ); + } + + $tests['edd_cron_enabled'] = array( + 'label' => __( 'Cron Events', 'easy-digital-downloads' ), + 'test' => array( $this, 'get_test_cron_enabled' ), + ); + + $licenses = new Licenses(); + $licenses_test = $licenses->get(); + + if ( ! empty( $licenses_test ) ) { + $tests['edd_licenses'] = $licenses_test; + } + + return $tests; + } + + /** + * Adds a test for the purchase/checkout page. + * + * @since 3.1.2 + * @return array + */ + public function get_test_missing_purchase_page() { + $result = array( + 'label' => __( 'You have a checkout page set', 'easy-digital-downloads' ), + 'status' => 'good', + 'badge' => $this->get_default_badge(), + 'description' => sprintf( + '

    %s

    ', + __( 'Your checkout page is set up and ready to process orders.', 'easy-digital-downloads' ) + ), + 'actions' => '', + 'test' => 'edd_missing_purchase_page', + ); + + $purchase_page = (int) edd_get_option( 'purchase_page', false ); + if ( empty( $purchase_page ) ) { + $result['label'] = __( 'Your checkout page is missing', 'easy-digital-downloads' ); + + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; + + $result['description'] = sprintf( + '

    %s

    ', + __( 'Easy Digital Downloads requires a specific checkout page to be set to easily handle user interactions.', 'easy-digital-downloads' ) + ); + $result['actions'] = sprintf( + '%s', + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'general', + 'section' => 'pages', + ) + ), + __( 'Fix the Checkout Page', 'easy-digital-downloads' ) + ); + } else { + if ( ! has_block( 'edd/checkout', $purchase_page ) ) { + $result['label'] = __( 'Your checkout page is using the legacy shortcode', 'easy-digital-downloads' ); + $result['status'] = 'recommended'; + $result['badge']['color'] = 'orange'; + + $result['description'] = wpautop( + sprintf( + /* translators: %1$s - opening tag, %2$s - closing tag */ + __( 'Your checkout page is configured; however, it is currently using the legacy %1$s[download_checkout]%2$s shortcode. We recommend changing your checkout to use the EDD Checkout Block.', 'easy-digital-downloads' ), + '', + '' + ) + ); + + // Link the action to edit the checkout page. + $result['actions'] = sprintf( + '%s', + admin_url( 'post.php?post=' . $purchase_page . '&action=edit' ), + __( 'Edit Checkout Page', 'easy-digital-downloads' ) + ); + } + } + + return $result; + } + + /** + * Adds a test for whether the EDD uploads directory is protected. + * + * @return array + */ + public function get_test_uploads_url_protected() { + $result = array( + 'label' => __( 'Your download files are protected', 'easy-digital-downloads' ), + 'status' => 'good', + 'badge' => $this->get_default_badge(), + 'description' => sprintf( + '

    %s

    ', + __( 'Your checkout page is a critical part of your store.', 'easy-digital-downloads' ) + ), + 'actions' => '', + 'test' => 'edd_uploads_url_protected', + ); + + if ( edd_is_uploads_url_protected() ) { + return $result; + } + + // Attempt to get the main index.php file. If we get a 403, the downloads are protected after all. + $check = wp_safe_remote_get( trailingslashit( edd_get_upload_url() ) . 'index.php' ); + if ( 403 === wp_remote_retrieve_response_code( $check ) ) { + return $result; + } + + // Get the upload directory. + $upload_directory = edd_get_upload_dir(); + + // Running NGINX. + $show_nginx_notice = apply_filters( 'edd_show_nginx_redirect_notice', true ); + if ( $show_nginx_notice && ! empty( $GLOBALS['is_nginx'] ) ) { + // The default NGINX recommendation for users. + $result['label'] = __( 'Your download files may not be protected', 'easy-digital-downloads' ); + $result['status'] = 'recommended'; + $result['badge']['color'] = 'orange'; + $result['description'] = sprintf( + '

    %s %s

    ', + sprintf( + /* translators: 1. opening link tag; 2. closing link tag */ + __( 'To ensure the best protection, you should use this doc to add this %1$sNGINX redirect rule%2$s.', 'easy-digital-downloads' ), + '', + '' + ), + __( 'If you have already done this, you can disregard this notice.', 'easy-digital-downloads' ) + ); + + $download_method = edd_get_option( 'download_method', 'direct' ); + $symlink = edd_get_option( 'symlink_file_downloads', false ); + + $additional_description = ''; + + if ( 'direct' === $download_method ) { + // If using the 'direct' download method, let the customer know that we are already obfuscating the URL, but for the best protection, make the recommended changes. + $additional_description .= sprintf( + '

    %s

    ', + __( 'No need to worry, you are using the recommended \'Forced\' download method, and customers should never see the direct path to the files. The following action is still recommended, however.', 'easy-digital-downloads' ) + ); + } elseif ( 'redirect' === $download_method && false === $symlink ) { + // If using the 'redirect' download method but not the symlink, they need to make this change. Adjust to a critical notice with a link to make suggested changes. + $result['badge']['color'] = 'red'; + $result['status'] = 'critical'; + $additional_description = sprintf( + '

    %s

    ', + __( 'You currently are using the \'Redirect\' download method, which may expose your downloadable products. Either switch to the \'Forced\' method or enable \'Symlinks\'.', 'easy-digital-downloads' ) + ); + + $result['actions'] = sprintf( + '%s', + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'misc', + 'section' => 'file-downloads', + ) + ), + __( 'Protect your files', 'easy-digital-downloads' ) + ); + } else { + // If using the 'redirect' download method and the symlink, they are already protected, but we can let them know that they can make the recommended changes. + $additional_description = sprintf( + '

    %s

    ', + __( 'Your current download method creates a temporary copy of the file for the customer to download. After they successfully download it, it is removed, ensuring they never have direct access to your product files.', 'easy-digital-downloads' ) + ); + } + + if ( ! empty( $additional_description ) ) { + $result['description'] = $additional_description . $result['description']; + } + + return $result; + } + + // Running Apache. + if ( ! empty( $GLOBALS['is_apache'] ) && ! edd_htaccess_exists() && ! get_user_meta( get_current_user_id(), '_edd_htaccess_missing_dismissed', true ) ) { + $result['label'] = __( 'Your download files are currently not protected', 'easy-digital-downloads' ); + $result['status'] = 'critical'; + $result['badge']['color'] = 'orange'; + $result['description'] = sprintf( + '

    %s %s

    ', + sprintf( + /* translators: the upload directory */ + __( 'The .htaccess file is missing from: %s', 'easy-digital-downloads' ), + '' . $upload_directory . '' + ), + sprintf( + /* translators: the upload directory */ + __( 'First, please resave the Misc settings tab a few times. If this warning continues to appear, create a file called ".htaccess" in the %s directory, and copy the following into it:', 'easy-digital-downloads' ), + '' . $upload_directory . '' + ) + ); + $result['actions'] = sprintf( + '%s', + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'misc', + ) + ), + __( 'Miscellaneous Settings', 'easy-digital-downloads' ) + ); + + return $result; + } + + return $result; + } + + /** + * Adds a test for enabled gateways. + * + * @since 3.1.2 + * @return array + */ + public function get_test_gateways_enabled() { + $result = array( + 'label' => __( 'You have at least one gateway enabled', 'easy-digital-downloads' ), + 'status' => 'good', + 'badge' => $this->get_default_badge(), + 'description' => sprintf( + '

    %s

    ', + __( 'Fantastic! You have enabled a gateway and can accept orders.', 'easy-digital-downloads' ) + ), + 'actions' => '', + 'test' => 'edd_gateways_enabled', + ); + + if ( edd_get_option( 'gateways' ) ) { + return $result; + } + + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; + $result['actions'] = sprintf( + '%s', + edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'gateways', + ) + ), + __( 'Please enable at least one gateway in order to allow customers to make purchases.', 'easy-digital-downloads' ) + ); + + if ( edd_is_test_mode() ) { + $result['status'] = 'recommended'; + $result['badge']['color'] = 'gray'; + } + + return $result; + } + + /** + * Adds a test for checking whether cron events are disabled. + * + * @since 3.1.2 + * @return array + */ + public function get_test_cron_enabled() { + $result = array( + 'label' => __( 'Scheduled events are running', 'easy-digital-downloads' ), + 'status' => 'good', + 'badge' => $this->get_default_badge(), + 'description' => sprintf( + '

    %s

    ', + __( 'Easy Digital Downloads uses scheduled events in a number of ways to help maintain performance and stability.', 'easy-digital-downloads' ) + ), + 'actions' => '', + 'test' => 'edd_cron_enabled', + ); + + if ( ! defined( 'WP_DISABLE_CRON' ) || empty( WP_DISABLE_CRON ) ) { + return $result; + } + + $result['label'] = __( 'Scheduled events are not running', 'easy-digital-downloads' ); + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; + $result['description'] .= sprintf( + '

    %s

    %s

    ', + __( 'Your site has cron events disabled. WordPress cron events should run at least every ten minutes for your store to manage order related events.', 'easy-digital-downloads' ), + __( 'Some hosting providers disable cron events by default, in favor of their own solution to running WP_CRON. Please contact your hosting provider to confirm any necessary changes.', 'easy-digital-downloads' ) + ); + + return $result; + } +} diff --git a/src/Admin/SiteHealth/Gateways.php b/src/Admin/SiteHealth/Gateways.php new file mode 100644 index 00000000000..9a709164db8 --- /dev/null +++ b/src/Admin/SiteHealth/Gateways.php @@ -0,0 +1,57 @@ + __( 'Easy Digital Downloads — Gateways', 'easy-digital-downloads' ), + 'fields' => $this->get_gateways(), + ); + } + + /** + * Gets the gateways data. + * + * @since 3.1.2 + */ + private function get_gateways() { + $all_gateways = edd_get_payment_gateways(); + $gateways = array(); + + if ( ! empty( $all_gateways ) ) { + + $default_gateway = edd_get_default_gateway(); + + foreach ( $all_gateways as $key => $gateway ) { + $gateways[ $key ] = array( + 'label' => $gateway['admin_label'], + 'value' => edd_is_gateway_active( $key ) ? 'Active' : 'Inactive', + ); + + if ( $default_gateway === $key ) { + $gateways[ $key ]['value'] .= ' (Default)'; + } + } + } + + return $gateways; + } +} diff --git a/src/Admin/SiteHealth/General.php b/src/Admin/SiteHealth/General.php new file mode 100644 index 00000000000..3a439844737 --- /dev/null +++ b/src/Admin/SiteHealth/General.php @@ -0,0 +1,147 @@ +pass_manager = new Pass_Manager(); + } + + /** + * Gets the site health section. + * + * @since 3.1.2 + * @return array + */ + public function get() { + return array( + 'label' => __( 'Easy Digital Downloads — General', 'easy-digital-downloads' ), + 'fields' => array( + 'version' => array( + 'label' => 'EDD Version', + 'value' => EDD_VERSION, + ), + 'edd_timezone' => array( + 'label' => 'EDD Timezone', + 'value' => edd_get_timezone_abbr(), + ), + 'upgraded' => array( + 'label' => 'Upgraded From', + 'value' => get_option( 'edd_version_upgraded_from', 'None' ), + ), + 'edd_is_pro' => array( + 'label' => 'EDD (Pro) Status', + 'value' => $this->get_pro_status(), + ), + 'edd_activated' => array( + 'label' => 'EDD Activation Date', + 'value' => $this->get_date( 'edd_activation_date' ), + ), + 'edd_pro_activated' => array( + 'label' => 'EDD (Pro) Activation Date', + 'value' => $this->get_date( 'edd_pro_activation_date' ), + ), + 'edd_pass' => array( + 'label' => 'EDD Pass Status', + 'value' => $this->pass_manager->highest_pass_id ? 'Valid Pass' : 'Missing', + ), + 'edd_test_mode' => array( + 'label' => 'Test Mode', + 'value' => edd_is_test_mode() ? 'Enabled' : 'Disabled', + ), + 'edd_ajax' => array( + 'label' => 'AJAX', + 'value' => ! edd_is_ajax_disabled() ? 'Enabled' : 'Disabled', + ), + 'edd_guest_checkout' => array( + 'label' => 'Guest Checkout', + 'value' => edd_no_guest_checkout() ? 'Disabled' : 'Enabled', + ), + 'symlinks' => array( + 'label' => 'Symlinks', + 'value' => apply_filters( 'edd_symlink_file_downloads', edd_get_option( 'symlink_file_downloads', false ) ) && function_exists( 'symlink' ) ? 'Enabled' : 'Disabled', + ), + 'download_method' => array( + 'label' => 'Download Method', + 'value' => ucfirst( edd_get_file_download_method() ), + ), + 'currency_code' => array( + 'label' => 'Currency Code', + 'value' => edd_get_currency(), + ), + 'currency_code' => array( + 'label' => 'Currency Position', + 'value' => edd_get_option( 'currency_position', 'before' ), + ), + 'decimal_separator' => array( + 'label' => 'Decimal Separator', + 'value' => edd_get_option( 'decimal_separator', '.' ), + ), + 'thousands_separator' => array( + 'label' => 'Thousands Separator', + 'value' => edd_get_option( 'thousands_separator', '.' ), + ), + 'completed_upgrades' => array( + 'label' => 'Upgrades Completed', + 'value' => implode( ', ', edd_get_completed_upgrades() ), + ), + 'download_link_expiration' => array( + 'label' => 'Download Link Expiration', + 'value' => edd_get_option( 'download_link_expiration' ) . ' hour(s)', + ), + ), + ); + } + + /** + * Gets the date for an option. + * + * @since 3.1.2 + * @param string $option The option name. + * @return string + */ + private function get_date( $option ) { + $date = get_option( $option ); + + return $date ? edd_date_i18n( $date, 'Y-m-d' ) : 'n/a'; + } + + /** + * Gets the pro license status for the site. + * + * @since 3.1.2 + * @return string + */ + private function get_pro_status() { + if ( ! edd_is_pro() ) { + return 'Disabled'; + } + + return $this->pass_manager::isPro() ? 'Enabled' : 'Missing License'; + } +} diff --git a/src/Admin/SiteHealth/Information.php b/src/Admin/SiteHealth/Information.php new file mode 100644 index 00000000000..e0d8232bd16 --- /dev/null +++ b/src/Admin/SiteHealth/Information.php @@ -0,0 +1,86 @@ + 'maybe_filter_debug', + ); + } + + /** + * Adds the EDD filters to the debug information. + * Additionally, removes other filters on the information if using the + * EDD system info link. + * + * @since 3.1.2 + * @return void + */ + public function maybe_filter_debug() { + if ( ! empty( $_GET['edd'] ) && 'filter' === $_GET['edd'] ) { + remove_all_filters( 'debug_information' ); + } + add_filter( 'debug_information', array( $this, 'get_data' ) ); + } + + /** + * Gets the array of EDD sections for the Site Health. + * + * @since 3.1.2 + * @param array $information The debug information. + * @return array + */ + public function get_data( $information ) { + return array_merge( $information, $this->get_edd_data() ); + } + + /** + * Gets all of the EDD data for the Site Health. + * + * @since 3.1.4 + * @return array + */ + private function get_edd_data() { + $collectors = array( + 'edd_general' => new General(), + 'edd_tables' => new Tables(), + 'edd_pages' => new Pages(), + 'edd_templates' => new Templates(), + 'edd_gateways' => new Gateways(), + 'edd_taxes' => new Taxes(), + 'edd_sessions' => new Sessions(), + ); + + $information = array(); + foreach ( $collectors as $key => $class ) { + $information[ $key ] = $class->get(); + } + + /** + * Allow extensions to add their own debug information that's specific to EDD. + * + * @since 3.1.4 + * @param array $information The debug information. + */ + return apply_filters( 'edd_debug_information', $information ); + } +} diff --git a/src/Admin/SiteHealth/Licenses.php b/src/Admin/SiteHealth/Licenses.php new file mode 100644 index 00000000000..a3ff7e0077c --- /dev/null +++ b/src/Admin/SiteHealth/Licenses.php @@ -0,0 +1,146 @@ +get_licensed_products() ) ) { + return false; + } + + return array( + 'label' => __( 'Licensed Extensions', 'easy-digital-downloads' ), + 'test' => array( $this, 'get_test_edd_licenses' ), + 'skip_cron' => true, + ); + } + + /** + * Adds a test for whether EDD licenses are valid/missing/expired. + * + * @since 3.1.2 + * @return array + */ + public function get_test_edd_licenses() { + $result = array( + 'label' => __( 'Your extensions are receiving updates', 'easy-digital-downloads' ), + 'status' => 'good', + 'badge' => $this->get_default_badge(), + 'description' => sprintf( + '

    %s

    ', + __( 'Your EDD extensions are all licensed and receiving updates.', 'easy-digital-downloads' ) + ), + 'actions' => '', + 'test' => 'edd_licenses', + ); + if ( ! $this->has_missing_licenses() ) { + return $result; + } + + $result['label'] = __( 'You are not receiving updates for some extensions', 'easy-digital-downloads' ); + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; + $result['description'] = sprintf( + '

    %s

    ', + __( 'At least one of your extensions is missing a license key, or the license is expired. Your site may be missing critical software updates.', 'easy-digital-downloads' ) + ); + $result['actions'] = $this->get_licensing_action_links(); + + return $result; + } + + /** + * Gets the licensed products global. + * + * @since 3.1.2 + * @return array + */ + private function get_licensed_products() { + global $edd_licensed_products; + + return $edd_licensed_products; + } + + /** + * Checks the licensed products global for unlicensed extensions. + * + * @since 3.1.2 + * @return bool + */ + private function has_missing_licenses() { + return in_array( 0, $this->get_licensed_products(), true ); + } + + /** + * Gets the licensing action links. + * + * @since 3.1.2 + * @return string + */ + private function get_licensing_action_links() { + $actions = $this->get_licensing_actions(); + $action_links = array(); + foreach ( $actions as $action ) { + $action_links[] = sprintf( + '%s', + esc_url( $action['url'] ), + esc_html( $action['label'] ) + ); + } + + return ! empty( $action_links ) ? implode( ' | ', $action_links ) : ''; + } + + /** + * Gets the licensing actions. + * + * @since 3.1.2 + * @return array + */ + private function get_licensing_actions() { + return array( + array( + 'label' => __( 'Upgrade to EDD (Pro)', 'easy-digital-downloads' ), + 'url' => edd_link_helper( + 'https://easydigitaldownloads.com/pricing/', + array( + 'utm_medium' => 'site-health', + 'utm_content' => 'upgrade-to-pro', + ), + false + ), + ), + array( + 'label' => __( 'Enter a license key for EDD (Pro)', 'easy-digital-downloads' ), + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'general', + ) + ), + ), + array( + 'label' => __( 'Enter a license key for an extension', 'easy-digital-downloads' ), + 'url' => edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'licenses', + ) + ), + ), + ); + } +} diff --git a/src/Admin/SiteHealth/Pages.php b/src/Admin/SiteHealth/Pages.php new file mode 100644 index 00000000000..3cf59fae66d --- /dev/null +++ b/src/Admin/SiteHealth/Pages.php @@ -0,0 +1,66 @@ + __( 'Easy Digital Downloads — Pages', 'easy-digital-downloads' ), + 'fields' => $this->get_pages(), + ); + } + + /** + * Gets the page data. + * + * @since 3.1.2 + * @return array + */ + private function get_pages() { + $purchase_page = edd_get_option( 'purchase_page', '' ); + $success_page = edd_get_option( 'success_page', '' ); + $failure_page = edd_get_option( 'failure_page', '' ); + $pages = array( + 'checkout' => array( + 'label' => 'Checkout', + 'value' => ! empty( $purchase_page ) ? 'Valid' : 'Invalid', + ), + 'checkout_uri' => array( + 'label' => 'Checkout Page', + 'value' => ! empty( $purchase_page ) ? get_permalink( $purchase_page ) : '', + ), + 'success_uri' => array( + 'label' => 'Success Page', + 'value' => ! empty( $success_page ) ? get_permalink( $success_page ) : '', + ), + 'failure_uri' => array( + 'label' => 'Failure Page', + 'value' => ! empty( $failure_page ) ? get_permalink( $failure_page ) : '', + ), + 'downloads_slug' => array( + 'label' => 'Downloads Slug', + 'value' => defined( 'EDD_SLUG' ) ? '/' . EDD_SLUG : '/downloads', + ), + ); + + return $pages; + } +} diff --git a/src/Admin/SiteHealth/Sessions.php b/src/Admin/SiteHealth/Sessions.php new file mode 100644 index 00000000000..52c2448281b --- /dev/null +++ b/src/Admin/SiteHealth/Sessions.php @@ -0,0 +1,88 @@ + __( 'Easy Digital Downloads — Sessions', 'easy-digital-downloads' ), + 'fields' => $this->get_data(), + ); + } + + /** + * Gets the array of session data. + * + * @since 3.1.2 + * @return array + */ + private function get_data() { + $data = array( + 'edd_use_sessions' => array( + 'label' => 'EDD Use Sessions', + 'value' => defined( 'EDD_USE_PHP_SESSIONS' ) && EDD_USE_PHP_SESSIONS ? 'Enforced' : ( EDD()->session->use_php_sessions() ? 'Enabled' : 'Disabled' ), + ), + 'session_enabled' => array( + 'label' => 'Session', + 'value' => isset( $_SESSION ) ? 'Enabled' : 'Disabled', + ), + ); + + $session_data = $this->get_session_data(); + + return $session_data ? array_merge( $data, $session_data ) : $data; + } + + /** + * Gets the data from the $_SESSION global. + * + * @since 3.1.2 + * @return false|array + */ + private function get_session_data() { + if ( ! isset( $_SESSION ) ) { + return false; + } + + return array( + 'name' => array( + 'label' => 'Session Name', + 'value' => ini_get( 'session.name' ), + ), + 'cookie_path' => array( + 'label' => 'Cookie Path', + 'value' => ini_get( 'session.cookie_path' ), + ), + 'save_path' => array( + 'label' => 'Save Path', + 'value' => ini_get( 'session.save_path' ), + ), + 'use_cookies' => array( + 'label' => 'Use Cookies', + 'value' => ini_get( 'session.use_cookies' ) ? 'On' : 'Off', + ), + 'use_only_cookies' => array( + 'label' => 'Use Only Cookies', + 'value' => ini_get( 'session.use_only_cookies' ) ? 'On' : 'Off', + ), + ); + } +} diff --git a/src/Admin/SiteHealth/Tables.php b/src/Admin/SiteHealth/Tables.php new file mode 100644 index 00000000000..dd04ae2d081 --- /dev/null +++ b/src/Admin/SiteHealth/Tables.php @@ -0,0 +1,78 @@ + __( 'Easy Digital Downloads — Custom Tables', 'easy-digital-downloads' ), + 'fields' => $this->get_tables(), + ); + } + + /** + * Gets the name/version of each EDD table that's registered as a component. + * + * @since 3.1.2 + * @return array + */ + private function get_tables() { + $tables = array( + 'default' => array( + 'label' => 'Table Name', + 'value' => 'Version / Count', + ), + ); + foreach ( EDD()->components as $component ) { + + // Object. + $thing = $component->get_interface( 'table' ); + if ( ! empty( $thing ) ) { + $tables[ $thing->name ] = array( + 'label' => $thing->name, + 'value' => $this->get_value_string( $thing ), + ); + } + + // Meta. + $thing = $component->get_interface( 'meta' ); + if ( ! empty( $thing ) ) { + $tables[ $thing->name ] = array( + 'label' => $thing->name, + 'value' => $this->get_value_string( $thing ), + ); + } + } + + return $tables; + } + + /** + * Gets the value string for the table data. + * + * @since 3.1.2 + * @param object $thing The table or meta object. + * @return string + */ + private function get_value_string( $thing ) { + return sprintf( '%s / %s', $thing->get_version(), $thing->count() ); + } +} diff --git a/src/Admin/SiteHealth/Taxes.php b/src/Admin/SiteHealth/Taxes.php new file mode 100644 index 00000000000..453eda4daef --- /dev/null +++ b/src/Admin/SiteHealth/Taxes.php @@ -0,0 +1,75 @@ + __( 'Easy Digital Downloads — Taxes', 'easy-digital-downloads' ), + 'fields' => $this->get_taxes(), + ); + } + + /** + * Gets the tax information. + * + * @since 3.1.2 + * @return array + */ + private function get_taxes() { + $taxes = array( + 'taxes_enabled' => array( + 'label' => 'Taxes', + 'value' => edd_use_taxes() ? 'Enabled' : 'Disabled', + ), + 'default_rate' => array( + 'label' => 'Default Tax Rate', + 'value' => edd_get_formatted_tax_rate(), + ), + 'display_on_checkout' => array( + 'label' => 'Display on Checkout', + 'value' => edd_get_option( 'checkout_include_tax', false ) ? 'Displayed' : 'Not Displayed', + ), + 'prices_include_tax' => array( + 'label' => 'Prices Include Tax', + 'value' => edd_prices_include_tax() ? 'Yes' : 'No', + ), + ); + $rates = edd_get_tax_rates( array(), OBJECT ); + if ( ! empty( $rates ) ) { + foreach ( $rates as $rate ) { + if ( 'global' === $rate->scope ) { + continue; + } + $tax_rate = $rate->name; + if ( ! empty( $rate->description ) ) { + $tax_rate .= ' / ' . $rate->description; + } + $taxes[ $rate->id ] = array( + 'label' => $tax_rate, + 'value' => edd_get_formatted_tax_rate( $rate->name, $rate->description ), + ); + } + } + + return $taxes; + } +} diff --git a/src/Admin/SiteHealth/Templates.php b/src/Admin/SiteHealth/Templates.php new file mode 100644 index 00000000000..f4d2a5970c7 --- /dev/null +++ b/src/Admin/SiteHealth/Templates.php @@ -0,0 +1,56 @@ + __( 'Easy Digital Downloads — Customized Templates', 'easy-digital-downloads' ), + 'fields' => $this->get_templates(), + ); + } + + /** + * Gets the customized templates. + * + * @since 3.1.2 + * @return array + */ + private function get_templates() { + $customized_template_files = edd_get_theme_edd_templates(); + $templates = array(); + if ( empty( $customized_template_files ) ) { + $templates['empty'] = array( + 'label' => '', + 'value' => 'No custom templates found.', + ); + } else { + foreach ( $customized_template_files as $customized_template_file ) { + $templates[] = array( + 'label' => '', + 'value' => $customized_template_file, + ); + } + } + + return $templates; + } +} diff --git a/src/Admin/SiteHealth/Test.php b/src/Admin/SiteHealth/Test.php new file mode 100644 index 00000000000..6b90109d779 --- /dev/null +++ b/src/Admin/SiteHealth/Test.php @@ -0,0 +1,25 @@ + __( 'Easy Digital Downloads', 'easy-digital-downloads' ), + 'color' => 'blue', + ); + } +} diff --git a/src/Admin/SiteHealth/Tests.php b/src/Admin/SiteHealth/Tests.php new file mode 100644 index 00000000000..eaabbed1ef6 --- /dev/null +++ b/src/Admin/SiteHealth/Tests.php @@ -0,0 +1,41 @@ + 'add_tests', + ); + } + + /** + * Register custom tests for EDD. + * + * @since 3.1.2 + * @param array $tests + * @return array + */ + public function add_tests( $tests ) { + $direct = new Direct(); + $direct_tests = $direct->get(); + if ( ! empty( $direct_tests ) ) { + $tests['direct'] = array_merge( $tests['direct'], $direct_tests ); + } + + return $tests; + } +} diff --git a/src/Core.php b/src/Core.php new file mode 100644 index 00000000000..cd1c9421aac --- /dev/null +++ b/src/Core.php @@ -0,0 +1,70 @@ +pass_handler ), + new Admin\Extensions\Extension_Manager(), + new Customers\Recalculations(), + new Admin\PassHandler\Cron(), + ); + } + + /** + * Gets the admin service providers. + * + * @since 3.1.1 + * @return array + */ + protected function get_admin_providers() { + if ( ! is_admin() ) { + return array(); + } + + return array( + new Admin\PassHandler\Settings( $this->pass_handler ), + new Admin\PassHandler\Actions( $this->pass_handler ), + new Admin\Extensions\Menu(), + new Admin\Settings\EmailMarketing(), + new Admin\Settings\Invoices(), + new Admin\Settings\Recurring(), + new Admin\Settings\Reviews(), + new Admin\Settings\WP_SMTP(), + new Admin\Downloads\Meta(), + new Admin\Onboarding\Tools(), + new Admin\Onboarding\Wizard(), + new Admin\Onboarding\Ajax(), + new Licensing\Ajax(), + new Admin\SiteHealth\Tests(), + new Admin\SiteHealth\Information(), + new Admin\Pointers(), + new Admin\Settings\Sanitize(), + ); + } + + /** + * Gets providers that may be extended/replaced in lite/pro. + * + * @return array + */ + protected function get_replaceable_providers() { + return array( + new Admin\Extensions\Legacy(), + new Admin\Promos\PromoHandler(), + ); + } +} diff --git a/src/Currency/Currency.php b/src/Currency/Currency.php new file mode 100644 index 00000000000..e907e5f72b6 --- /dev/null +++ b/src/Currency/Currency.php @@ -0,0 +1,242 @@ +code = strtoupper( $currency_code ); + + $this->setup(); + } + + /** + * Returns a new currency object. + * + * @param string $currency_code + * + * @since 3.0 + * + * @return Currency + */ + public static function from_code( $currency_code ) { + return new self( $currency_code ); + } + + /** + * Sets up properties. + * + * @since 3.0 + */ + private function setup() { + $this->symbol = $this->get_symbol(); + $this->decimal_separator = edd_get_option( 'decimal_separator', '.' ); + $this->thousands_separator = edd_get_option( 'thousands_separator', ',' ); + $this->position = edd_get_option( 'currency_position', 'before' ); + + /** + * Filters the decimal separator. + * + * @param string $decimal_separator + * @param string $code + * + * @since 3.0 + */ + $this->decimal_separator = apply_filters( 'edd_currency_decimal_separator', $this->decimal_separator, $this->code ); + + /** + * Filters the thousands separator. + * + * @param string $thousands_separator + * @param string $code + * + * @since 3.0 + */ + $this->thousands_separator = apply_filters( 'edd_currency_thousands_separator', $this->thousands_separator, $this->code ); + + $separator = $this->_has_space_around_symbol() ? ' ' : ''; + if ( 'before' === $this->position ) { + $this->prefix = $this->symbol . $separator; + } else { + $this->suffix = $separator . $this->symbol; + } + + /** + * Filters the currency prefix. + * + * @param string $prefix + * @param string $code + * + * @since 3.0 + */ + $this->prefix = apply_filters( 'edd_currency_prefix', $this->prefix, $this->code ); + + /** + * Filters the currency suffix. + * + * @param string $prefix + * @param string $code + * + * @since 3.0 + */ + $this->suffix = apply_filters( 'edd_currency_suffix', $this->suffix, $this->code ); + + $this->number_decimals = $this->_is_zero_decimal() ? 0 : 2; + } + + /** + * Whether or not this currency has a space between the symbol and the amount. + * + * @since 3.0 + * + * @return bool + */ + private function _has_space_around_symbol() { + return ! in_array( $this->code, array( + 'GBP', + 'BRL', + 'EUR', + 'USD', + 'AUD', + 'CAD', + 'HKD', + 'MXN', + 'SGD', + 'JPY' + ) ); + } + + /** + * Returns the symbol for this currency. + * Depending on settings, this will be used as either the prefix or the suffix. + * + * @since 3.0 + * @return string + */ + public function get_symbol() { + switch ( $this->code ) : + case "GBP" : + $symbol = '£'; + break; + case "BRL" : + $symbol = 'R$'; + break; + case "EUR" : + $symbol = '€'; + break; + case "USD" : + case "AUD" : + case "NZD" : + case "CAD" : + case "HKD" : + case "MXN" : + case "SGD" : + $symbol = '$'; + break; + case "JPY" : + $symbol = '¥'; + break; + case "AOA" : + $symbol = 'Kz'; + break; + default : + $symbol = $this->code; + break; + endswitch; + + /** + * Filters the currency symbol. + * + * @since unknown + * + * @param string $symbol Currency symbol. + * @param string $code Currency code. + */ + return apply_filters( 'edd_currency_symbol', $symbol, $this->code ); + } + + /** + * Determines whether or not the currency is zero-decimal. + * + * @since 3.0 + * + * @return bool + */ + private function _is_zero_decimal() { + $currencies = array( + 'bif', + 'clp', + 'djf', + 'gnf', + 'huf', + 'jpy', + 'kmf', + 'krw', + 'mga', + 'pyg', + 'rwf', + 'ugx', + 'vnd', + 'vuv', + 'xaf', + 'xof', + 'xpf', + ); + + return in_array( strtolower( $this->code ), $currencies, true ); + } + +} diff --git a/src/Currency/Money_Formatter.php b/src/Currency/Money_Formatter.php new file mode 100644 index 00000000000..362a68128d3 --- /dev/null +++ b/src/Currency/Money_Formatter.php @@ -0,0 +1,232 @@ +original_amount = $amount; + $this->amount = $amount; + $this->typed_amount = $amount; + $this->currency = $currency; + } + + /** + * Un-formats an amount. + * This ensures the amount is put into a state where we can perform mathematical + * operations on it --- that means using `.` as the decimal separator and no + * thousands separator. + * + * @return float|int + */ + private function unformat() { + $amount = $this->original_amount; + + $sep_found = strpos( $amount, $this->currency->decimal_separator ); + if ( ',' === $this->currency->decimal_separator && false !== $sep_found ) { + $whole = substr( $amount, 0, $sep_found ); + $part = substr( $amount, $sep_found + 1, ( strlen( $amount ) - 1 ) ); + $amount = $whole . '.' . $part; + } + + // Strip "," and " " from the amount (if set as the thousands separator). + foreach ( array( ',', ' ' ) as $thousands_separator ) { + if ( $thousands_separator === $this->currency->thousands_separator && false !== strpos( $amount, $this->currency->thousands_separator ) ) { + $amount = str_replace( $thousands_separator, '', $amount ); + } + } + + return $amount; + } + + /** + * Returns the number of decimals ot use for the formatted amount. + * + * Based on the currency code used when instantiating the class, determines how many + * decimal points the value should have once formatted. + * + * @param bool $decimals If we should include decimals or not in the formatted amount. + * @param float $amount The amount to format. + * + * @return int The number of decimals places to use when formatting the amount. + */ + private function get_decimals( $decimals, $amount ) { + /** + * Filter number of decimals to use for formatted amount + * + * @since unknown + * + * @param int $number Default 2. Number of decimals. + * @param int|string $amount Amount being formatted. + * @param string $currency_code Currency code being formatted. + */ + return apply_filters( 'edd_format_amount_decimals', $decimals ? $this->currency->number_decimals : 0, $amount, $this->currency->code ); + } + + /** + * Formats the amount for display. + * Does not apply the currency code. + * + * @since 3.0 + * + * @param bool $decimals If we should include decimal places or not when formatting. + * + * @return Money_Formatter + */ + public function format_for_display( $decimals = true ) { + $amount = $this->unformat(); + + if ( empty( $amount ) ) { + $amount = 0; + } + + $decimals = $this->get_decimals( $decimals, $amount ); + + // Format amount using decimals and separators (also rounds up or down). + $formatted = number_format( (float) $amount, $decimals, $this->currency->decimal_separator, $this->currency->thousands_separator ); + + /** + * Filter the formatted amount before returning + * + * @since unknown + * + * @param mixed $formatted Formatted amount. + * @param mixed $amount Original amount. + * @param int $decimals Default 2. Number of decimals. + * @param string $decimal_separator Default '.'. Decimal separator. + * @param string $thousands_separator Default ','. Thousands separator. + * @param string $currency_code Currency used for formatting. + */ + $this->amount = apply_filters( 'edd_format_amount', $formatted, $amount, $decimals, $this->currency->decimal_separator, $this->currency->thousands_separator, $this->currency->code ); + + return $this; + } + + /** + * Formats the amount for typed data returns. + * Does not apply the currency code and returns a foat instead of a string. + * + * @since 3.0 + * + * @param bool $decimals If we should include decimal places or not when formatting. + * + * @return Money_Formatter + */ + public function format_for_typed( $decimals = true ) { + $amount = $this->unformat(); + + if ( empty( $amount ) ) { + $amount = 0; + } + + $decimals = $this->get_decimals( $decimals, $amount ); + + /** + * Since we want to return a float value here, intentionally only supply a decimal separator. + * + * The separators here are hard coded intentionally as we're looking to get truncated, raw format of float + * which requires '.' for decimal separators and no thousands separator. + * + * This is also intentionally not filtered for the time being. + */ + $formatted = floatval( number_format( (float) $amount, $decimals, '.', '' ) ); + + // Set the amount to $this->amount. + $this->typed_amount = $formatted; + + return $this; + } + + /** + * Applies the currency prefix/suffix to the amount. + * + * @since 3.0 + * @return string + */ + public function apply_symbol() { + $amount = $this->amount; + $is_negative = is_numeric( $this->amount ) && $this->amount < 0; + + // Remove "-" from start. + if ( $is_negative ) { + $amount = substr( $amount, 1 ); + } + + $formatted = ''; + if ( ! empty( $this->currency->prefix ) ) { + $formatted .= $this->currency->prefix; + } + + $formatted .= $amount; + + if ( ! empty( $this->currency->suffix ) ) { + $formatted .= $this->currency->suffix; + } + + if ( ! empty( $this->currency->prefix ) ) { + /** + * Filters the output with a prefix. + * + * @param string $formatted + * @param string $currency_code + * @param string $amount + */ + $formatted = apply_filters( 'edd_' . strtolower( $this->currency->code ) . '_currency_filter_before', $formatted, $this->currency->code, $amount ); + } + + if ( ! empty( $this->currency->suffix ) ) { + /** + * Filters the output with a suffix. + * + * @param string $formatted + * @param string $currency_code + * @param string $amount + */ + $formatted = apply_filters( 'edd_' . strtolower( $this->currency->code ) . '_currency_filter_after', $formatted, $this->currency->code, $amount ); + } + + // Add the "-" sign back to the start of the string. + if ( $is_negative ) { + $formatted = '-' . $formatted; + } + + return $formatted; + } + +} diff --git a/src/Customers/Recalculations.php b/src/Customers/Recalculations.php new file mode 100644 index 00000000000..6676e46d16d --- /dev/null +++ b/src/Customers/Recalculations.php @@ -0,0 +1,127 @@ + array( 'maybe_schedule_recalculation', 10, 2 ), + 'edd_order_updated' => array( 'maybe_schedule_recalculation', 10, 3 ), + 'edd_order_deleted' => 'maybe_schedule_recalculation', + 'edd_recalculate_customer_deferred' => 'recalculate', + ); + } + + /** + * When an order is added, updated, or changed, the customer stats may need to be recalculated. + * + * @param int $order_id The order ID. + * @param array $data The array of order data. + * @param bool|EDD\Orders|Order $previous_order The previous order object (when updating). + * @return void + */ + public function maybe_schedule_recalculation( $order_id, $data = array(), $previous_order = false ) { + + if ( get_option( '_edd_v30_doing_order_migration', false ) ) { + return; + } + + // Recalculations do not need to run when the order item is first being added to the database if it's pending. + if ( 'edd_order_added' === current_action() && ( empty( $data['status'] ) || 'pending' === $data['status'] ) ) { + return; + } + + // If the order item data being updated doesn't affect sales/earnings, recalculations do not need to be run. + if ( $previous_order instanceof EDD\Orders\Order ) { + $columns_affecting_stats = array( 'status', 'total', 'subtotal', 'discount', 'tax', 'rate', 'customer_id' ); + + // If the data being updated isn't one of these columns then we don't need to recalculate. + if ( empty( array_intersect( array_keys( $data ), $columns_affecting_stats ) ) ) { + return; + } + + // If the data exists but matches, we don't need to recalculate. + if ( + ( empty( $data['status'] ) || $previous_order->status === $data['status'] ) && + ( ! isset( $data['total'] ) || $previous_order->total == $data['total'] ) && + ( ! isset( $data['subtotal'] ) || $previous_order->subtotal == $data['subtotal'] ) && + ( ! isset( $data['discount'] ) || $previous_order->discount == $data['discount'] ) && + ( ! isset( $data['tax'] ) || $previous_order->tax == $data['tax'] ) && + ( ! isset( $data['rate'] ) || $previous_order->rate == $data['rate'] ) && + ( empty( $data['customer_id'] ) || $previous_order->customer_id == $data['customer_id'] ) + ) { + return; + } + + // Recalculate the previous product values if the product ID has changed. + if ( ! empty( $data['customer_id'] ) && $previous_order->customer_id != $data['customer_id'] ) { + $this->schedule_recalculation( $previous_order->customer_id ); + } + } + + $order = edd_get_order( $order_id ); + if ( empty( $order->customer_id ) ) { + return; + } + + $this->schedule_recalculation( $order->customer_id ); + } + + /** + * Recalculate the value of a customer. + * + * @since 3.1.1.4 + * @param int $customer_id + * @return void + */ + public function recalculate( $customer_id ) { + $customer = edd_get_customer( $customer_id ); + if ( ! $customer instanceof \EDD_Customer ) { + return; + } + $customer->recalculate_stats(); + } + + /** + * Maybe schedule the customer recalculation--it will be skipped if already scheduled. + * + * @since 3.1.1.4 + * @param int $customer_id + * @return void + */ + private function schedule_recalculation( $customer_id ) { + $is_scheduled = wp_next_scheduled( 'edd_recalculate_customer_deferred', array( $customer_id ) ); + $bypass_cron = apply_filters( 'edd_recalculate_bypass_cron', false ); + + // Check if the recalculation has already been scheduled. + if ( $is_scheduled && ! $bypass_cron ) { + edd_debug_log( 'Recalculation is already scheduled for customer ' . $customer_id . ' at ' . edd_date_i18n( $is_scheduled, 'datetime' ) ); + return; + } + + // If we are intentionally bypassing cron somehow, recalculate now and return. + if ( $bypass_cron || ( defined( 'EDD_DOING_TESTS' ) && EDD_DOING_TESTS ) || ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) ) { + $this->recalculate( $customer_id ); + return; + } + + edd_debug_log( 'Scheduling recalculation for customer ' . $customer_id ); + wp_schedule_single_event( + time() + ( 5 * MINUTE_IN_SECONDS ), + 'edd_recalculate_customer_deferred', + array( $customer_id ) + ); + } +} diff --git a/src/Database/NotificationsDB.php b/src/Database/NotificationsDB.php new file mode 100644 index 00000000000..789ec940a17 --- /dev/null +++ b/src/Database/NotificationsDB.php @@ -0,0 +1,248 @@ +run(); + } + + /** + * Let MySQL handle most of the defaults. + * We just set the dates here to ensure they get saved in UTC. + * + * @since 2.11.4 + * + * @return array + */ + public function get_column_defaults() { + return array( + 'date_created' => gmdate( 'Y-m-d H:i:s' ), + 'date_updated' => gmdate( 'Y-m-d H:i:s' ), + ); + } + + /** + * Adds or updates a local notification. + * + * @param array $data + * @return false|int Returns false if the notification could not be added/updated; the ID of the notification if it could. + */ + public function maybe_add_local_notification( $data = array() ) { + + // A remote_id is required and it cannot be numeric for local notifications. + if ( empty( $data['remote_id'] ) || is_numeric( $data['remote_id'] ) ) { + return false; + } + // The source is always always local. + $data['source'] = 'local'; + + $existing = $this->get_item_by( 'remote_id', $data['remote_id'] ); + if ( $existing ) { + return $this->update( + $existing->id, + $data + ); + } + + return $this->insert( $data ); + } + + /** + * JSON-encodes any relevant columns. + * + * @since 2.11.4 + * + * @param array $data + * + * @return array + */ + protected function maybeJsonEncode( $data ) { + $jsonColumns = array( 'buttons', 'conditions' ); + + foreach ( $jsonColumns as $column ) { + if ( ! empty( $data[ $column ] ) && is_array( $data[ $column ] ) ) { + $data[ $column ] = json_encode( $data[ $column ] ); + } + } + + return $data; + } + + /** + * Inserts a new notification. + * + * @since 2.11.4 + * + * @param array $data + * @param string $type + * + * @return int + */ + public function insert( $data, $type = 'notification' ) { + + $data = $this->maybeJsonEncode( $data ); + $notifications = new \EDD\Database\Queries\Notification(); + + $result = $notifications->add_item( $data ); + + wp_cache_delete( 'edd_active_notification_count', 'edd_notifications' ); + + return $result; + } + + /** + * Updates an existing notification. + * + * @since 2.11.4 + * + * @param int $row_id + * @param array $data + * @param string $where + * + * @return bool + */ + public function update( $row_id, $data = array(), $where = '' ) { + $notifications = new \EDD\Database\Queries\Notification(); + + return $notifications->update_item( $row_id, $this->maybeJsonEncode( $data ) ); + } + + /** + * Gets a notification by ID. + * + * @param int $id + * @return false|Notification + */ + public function get( $id ) { + $notifications = new \EDD\Database\Queries\Notification(); + + return $notifications->get_item( $id ); + } + + /** + * Gets an item by the column name and value. + * + * @param string $column_name + * @param string $column_value + * @return false|Notification + */ + public function get_item_by( $column_name = '', $column_value = '' ) { + $notifications = new \EDD\Database\Queries\Notification(); + + return $notifications->get_item_by( $column_name, $column_value ); + } + + /** + * Returns all notifications that have not been dismissed and should be + * displayed on this site. + * + * @since 2.11.4 + * + * @param bool $conditionsOnly If set to true, then only the `conditions` column is retrieved + * for each notification. + * + * @return Notification[] + */ + public function getActiveNotifications( $conditionsOnly = false ) { + global $wpdb; + + $environmentChecker = new EnvironmentChecker(); + $notifications = $wpdb->get_results( $this->getActiveQuery( $conditionsOnly ) ); + + $models = array(); + if ( is_array( $notifications ) ) { + foreach ( $notifications as $notification ) { + $model = new Notification( (array) $notification ); + + try { + // Only add to the array if all conditions are met or if the notification has no conditions. + if ( + ! $model->conditions || + ( is_array( $model->conditions ) && $environmentChecker->meetsConditions( $model->conditions ) ) + ) { + $models[] = $model; + } + } catch ( \Exception $e ) { + + } + } + } + + unset( $notifications ); + + return $models; + } + + /** + * Builds the query for selecting or counting active notifications. + * + * @since 2.11.4 + * + * @param bool $conditionsOnly + * + * @return string + */ + private function getActiveQuery( $conditionsOnly = false ) { + global $wpdb; + + $select = $conditionsOnly ? 'conditions' : '*'; + + return $wpdb->prepare( + "SELECT {$select} FROM {$wpdb->edd_notifications} + WHERE dismissed = 0 + AND (start <= %s OR start IS NULL) + AND (end >= %s OR end IS NULL) + ORDER BY start DESC, id DESC", + gmdate( 'Y-m-d H:i:s' ), + gmdate( 'Y-m-d H:i:s' ) + ); + } + + /** + * Counts the number of active notifications. + * Note: We can't actually do a real `COUNT(*)` on the database, because we want + * to double-check the conditions are met before displaying. That's why we use + * `getActiveNotifications()` which runs the conditions through the EnvironmentChecker. + * + * @since 2.11.4 + * + * @return int + */ + public function countActiveNotifications() { + $numberActive = wp_cache_get( 'edd_active_notification_count', 'edd_notifications' ); + if ( false === $numberActive ) { + $numberActive = count( $this->getActiveNotifications( true ) ); + + wp_cache_set( 'edd_active_notification_count', $numberActive, 'edd_notifications' ); + } + + return $numberActive; + } +} diff --git a/src/Database/Queries/Notification.php b/src/Database/Queries/Notification.php new file mode 100644 index 00000000000..644e972d9d8 --- /dev/null +++ b/src/Database/Queries/Notification.php @@ -0,0 +1,115 @@ + 'id', + 'type' => 'bigint', + 'length' => '20', + 'unsigned' => true, + 'extra' => 'auto_increment', + 'primary' => true, + 'sortable' => true, + ), + + // remote_id + array( + 'name' => 'remote_id', + 'type' => 'varchar', + 'length' => '20', + 'default' => null, + ), + + // source + array( + 'name' => 'source', + 'type' => 'varchar', + 'default' => 'api', + 'allow_null' => false, + ), + + // title + array( + 'name' => 'title', + 'type' => 'text', + 'default' => '', + 'allow_null' => false, + ), + + // content + array( + 'name' => 'content', + 'type' => 'longtext', + 'default' => '', + 'allow_null' => false, + ), + + // buttons + array( + 'name' => 'buttons', + 'type' => 'longtext', + 'default' => null, + 'allow_null' => true, + ), + + // type + array( + 'name' => 'type', + 'type' => 'varchar', + 'length' => '64', + 'allow_null' => true, + ), + + // conditions + array( + 'name' => 'conditions', + 'type' => 'longtext', + 'default' => null, + 'allow_null' => true, + ), + + // start + array( + 'name' => 'start', + 'type' => 'datetime', + 'default' => null, + 'date_query' => true, + 'sortable' => true, + 'allow_null' => true, + ), + + // end + array( + 'name' => 'end', + 'type' => 'datetime', + 'default' => null, + 'date_query' => true, + 'sortable' => true, + 'allow_null' => true, + ), + + // dismissed + array( + 'name' => 'dismissed', + 'type' => 'tinyint', + 'length' => '1', + 'unsigned' => true, + 'allow_null' => true, + 'default' => 0, + ), + + // date_created + array( + 'name' => 'date_created', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'date_query' => true, + 'sortable' => true, + 'created' => true, + ), + + // date_updated + array( + 'name' => 'date_updated', + 'type' => 'datetime', + 'default' => '', // Defaults to current time in query class + 'date_query' => true, + 'sortable' => true, + 'modified' => true, + ), + ); +} diff --git a/src/Database/Tables/Notifications.php b/src/Database/Tables/Notifications.php new file mode 100644 index 00000000000..e8b767d0d5c --- /dev/null +++ b/src/Database/Tables/Notifications.php @@ -0,0 +1,146 @@ + 202303220, + ); + + /** + * Setup the database schema. + * + * @access protected + * @since 3.1.1 + * @return void + */ + protected function set_schema() { + $this->schema = "id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + remote_id varchar(20) DEFAULT NULL, + source varchar(20) NOT NULL DEFAULT 'api', + title text NOT NULL, + content longtext NOT NULL, + buttons longtext DEFAULT NULL, + type varchar(64) NOT NULL DEFAULT 'success', + conditions longtext DEFAULT NULL, + start datetime DEFAULT NULL, + end datetime DEFAULT NULL, + dismissed tinyint(1) UNSIGNED NOT NULL DEFAULT 0, + date_created datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + date_updated datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY dismissed_start_end (dismissed, start, end), + KEY remote_id (remote_id)"; + } + + /** + * Deletes the original database version option. + * + * @since 3.1.1 + * @return bool + */ + protected function __202301251() { + return delete_option( "{$this->table_name}_db_version" ); + } + + /** + * Upgrade to version 202302131 + * - Add the `source` text column and modify the remote_id column. + * + * @since 3.1.1 + * + * @return boolean + */ + protected function __202302131() { + + $updates = array( + 'add-source' => false, + 'remote-id' => false, + ); + + if ( false === $this->column_exists( 'source' ) ) { + $source = $this->get_db()->query( + "ALTER TABLE {$this->table_name} ADD COLUMN `source` varchar(20) NOT NULL DEFAULT 'api' AFTER `remote_id`;" + ); + + if ( $this->is_success( $source ) ) { + $updates['add-source'] = $this->get_db()->query( "UPDATE {$this->table_name} SET `source` = 'api'" ); + } + } else { + $updates['add-source'] = true; + } + + $remote_id_column = $this->get_db()->get_row( "SHOW FIELDS FROM {$this->table_name} WHERE Field = 'remote_id'" ); + if ( 'varchar(20)' !== $remote_id_column->Type ) { + $updates['remote-id'] = $this->get_db()->query( + "ALTER TABLE {$this->table_name} MODIFY COLUMN `remote_id` varchar(20) DEFAULT NULL;" + ); + } else { + $updates['remote-id'] = true; + } + + foreach ( $updates as $query_key => $result ) { + if ( ! $this->is_success( $result ) ) { + return false; + } + } + + return true; + } + + /** + * Runs another database upgrade for sites which got into a bit of a snarl with the database versions. + * + * @since 3.1.1.3 + * @return bool + */ + protected function __202303220() { + $this->__202301251(); + + return $this->__202302131(); + } +} diff --git a/src/Downloads/Search.php b/src/Downloads/Search.php new file mode 100644 index 00000000000..b01908c4f7b --- /dev/null +++ b/src/Downloads/Search.php @@ -0,0 +1,224 @@ + '', + 'results' => array(), + ) + ); + + // Get the search string. + $new_search = isset( $_GET['s'] ) + ? sanitize_text_field( $_GET['s'] ) + : ''; + + // Limit to only alphanumeric characters, including unicode and spaces. + $new_search = preg_replace( '/[^\pL^\pN\pZ]/', ' ', $new_search ); + + // Bail early if the search text has not changed. + if ( $search['text'] === $new_search ) { + echo wp_json_encode( $search['results'] ); + edd_die(); + } + + // Set the local static search variable. + $search['text'] = $new_search; + + // Are we excluding the current ID? + $excludes = isset( $_GET['current_id'] ) + ? array_unique( array_map( 'absint', (array) $_GET['current_id'] ) ) + : array(); + + // Are we excluding bundles? + $no_bundles = isset( $_GET['no_bundles'] ) + ? filter_var( $_GET['no_bundles'], FILTER_VALIDATE_BOOLEAN ) + : false; + + // Are we including variations? + $variations = isset( $_GET['variations'] ) + ? filter_var( $_GET['variations'], FILTER_VALIDATE_BOOLEAN ) + : false; + + $variations_only = isset( $_GET['variations_only'] ) + ? filter_var( $_GET['variations_only'], FILTER_VALIDATE_BOOLEAN ) + : false; + + // Are we including all statuses, or only public ones? + $status = ! current_user_can( 'edit_products' ) + ? apply_filters( 'edd_product_dropdown_status_nopriv', array( 'publish' ) ) + : apply_filters( 'edd_product_dropdown_status', array( 'publish', 'draft', 'private', 'future' ) ); + + // Default query arguments. + $args = array( + 'orderby' => 'title', + 'order' => 'ASC', + 'post_type' => 'download', + 'posts_per_page' => 50, + 'post_status' => implode( ',', $status ), // String. + 'post__not_in' => $excludes, // Array. + 'edd_search' => $new_search, // String. + 'suppress_filters' => false, + ); + + // Maybe exclude bundles. + if ( true === $no_bundles ) { + $args['meta_query'] = array( + 'relation' => 'OR', + array( + 'key' => '_edd_product_type', + 'value' => 'bundle', + 'compare' => '!=', + ), + array( + 'key' => '_edd_product_type', + 'value' => 'bundle', + 'compare' => 'NOT EXISTS', + ), + ); + } + + add_filter( 'posts_where', array( $this, 'filter_where' ), 10, 2 ); + // Get downloads. + $items = get_posts( $args ); + remove_filter( 'posts_where', array( $this, 'filter_where' ), 10, 2 ); + + // Pluck title & ID. + if ( ! empty( $items ) ) { + $items = wp_list_pluck( $items, 'post_title', 'ID' ); + + // Loop through all items... + foreach ( $items as $post_id => $title ) { + $product_title = $title; + + // Look for variable pricing. + $prices = edd_get_variable_prices( $post_id ); + + if ( ! empty( $prices ) && ( false === $variations || ! $variations_only ) ) { + $title .= ' (' . __( 'All Price Options', 'easy-digital-downloads' ) . ')'; + } + + if ( empty( $prices ) || ! $variations_only ) { + // Add item to results array. + $search['results'][] = array( + 'id' => $post_id, + 'name' => $title, + ); + } + + // Maybe include variable pricing. + if ( ! empty( $variations ) && ! empty( $prices ) ) { + foreach ( $prices as $key => $value ) { + $name = ! empty( $value['name'] ) ? $value['name'] : ''; + + if ( ! empty( $name ) ) { + $search['results'][] = array( + 'id' => $post_id . '_' . $key, + 'name' => esc_html( $product_title . ': ' . $name ), + ); + } + } + } + } + } else { + // Empty the results array. + $search['results'] = array(); + } + + // Update the transient. + set_transient( 'edd_download_search', $search, 30 ); + + // Output the results. + echo wp_json_encode( $search['results'] ); + + // Done! + edd_die(); + } + + /** + * Filters the WHERE SQL query for the edd_download_search. + * This searches the download titles only, not the excerpt/content. + * + * @since 3.1.0.2 + * @since 3.1.0.5 Moved to EDD\Downloads\Ajax. + * @param string $where + * @param WP_Query $wp_query + * @return string + */ + public function filter_where( $where, $wp_query ) { + $search = $wp_query->get( 'edd_search' ); + if ( ! $search ) { + return $where; + } + + $terms = $this->parse_search_terms( $search ); + if ( empty( $terms ) ) { + return $where; + } + + global $wpdb; + $query = ''; + foreach ( $terms as $term ) { + $operator = empty( $query ) ? '' : ' AND '; + $term = $wpdb->esc_like( $term ); + $query .= "{$operator}{$wpdb->posts}.post_title LIKE '%{$term}%'"; + } + if ( $query ) { + $where .= " AND ({$query})"; + } + + return $where; + } + + /** + * Parses the search terms to allow for a "fuzzy" search. + * + * @since 3.1.0.5 + * @param string $search + * @return array + */ + protected function parse_search_terms( $search ) { + $terms = explode( ' ', $search ); + $strtolower = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower'; + $checked = array(); + + foreach ( $terms as $term ) { + // Keep before/after spaces when term is for exact match. + if ( preg_match( '/^".+"$/', $term ) ) { + $term = trim( $term, "\"'" ); + } else { + $term = trim( $term, "\"' " ); + } + + // Avoid single A-Z and single dashes. + if ( ! $term || ( 1 === strlen( $term ) && preg_match( '/^[a-z\-]$/i', $term ) ) ) { + continue; + } + + $checked[] = $term; + } + + return $checked; + } +} diff --git a/src/EventManagement/EventManager.php b/src/EventManagement/EventManager.php new file mode 100644 index 00000000000..04b73bde659 --- /dev/null +++ b/src/EventManagement/EventManager.php @@ -0,0 +1,69 @@ +get_subscribed_events() as $hook_name => $parameters ) { + $this->add_subscriber_callback( $subscriber, $hook_name, $parameters ); + } + } + + /** + * Remove an event subscriber. + * + * The event manager removes all the hooks that the given subscriber + * wants to register with the WordPress Plugin API. + * + * @param SubscriberInterface $subscriber + */ + public function remove_subscriber( SubscriberInterface $subscriber ) { + foreach ( $subscriber->get_subscribed_events() as $hook_name => $parameters ) { + $this->remove_subscriber_callback( $subscriber, $hook_name, $parameters ); + } + } + + /** + * Adds the given subscriber's callback to a specific hook + * of the WordPress plugin API. + * + * @param SubscriberInterface $subscriber + * @param string $hook_name + * @param mixed $parameters + */ + private function add_subscriber_callback( SubscriberInterface $subscriber, $hook_name, $parameters ) { + if ( is_string( $parameters ) ) { + $this->add_callback( $hook_name, array( $subscriber, $parameters ) ); + } elseif ( is_array( $parameters ) && isset( $parameters[0] ) ) { + $this->add_callback( $hook_name, array( $subscriber, $parameters[0] ), isset( $parameters[1] ) ? $parameters[1] : 10, isset( $parameters[2] ) ? $parameters[2] : 1 ); + } + } + + /** + * Removes the given subscriber's callback to a specific hook + * of the WordPress plugin API. + * + * @param SubscriberInterface $subscriber + * @param string $hook_name + * @param mixed $parameters + */ + private function remove_subscriber_callback( SubscriberInterface $subscriber, $hook_name, $parameters ) { + if ( is_string( $parameters ) ) { + $this->remove_callback( $hook_name, array( $subscriber, $parameters ) ); + } elseif ( is_array( $parameters ) && isset( $parameters[0] ) ) { + $this->remove_callback( $hook_name, array( $subscriber, $parameters[0] ), isset( $parameters[1] ) ? $parameters[1] : 10 ); + } + } +} diff --git a/src/EventManagement/PluginAPIManager.php b/src/EventManagement/PluginAPIManager.php new file mode 100644 index 00000000000..ad540658df6 --- /dev/null +++ b/src/EventManagement/PluginAPIManager.php @@ -0,0 +1,96 @@ + 'method_name') + * * array('event_name' => array('method_name', $priority)) + * * array('event_name' => array('method_name', $priority, $accepted_args)) + * + * @return array + */ + public static function get_subscribed_events(); +} diff --git a/src/EventManagement/Subscribers.php b/src/EventManagement/Subscribers.php new file mode 100644 index 00000000000..d5f1f67d94f --- /dev/null +++ b/src/EventManagement/Subscribers.php @@ -0,0 +1,76 @@ +pass_handler = new \EDD\Admin\PassHandler\Handler(); + $this->add_service_providers(); + } + + /** + * Add registered service providers. + * + * @since 3.1.1 + * @return void + */ + private function add_service_providers() { + $events = new EventManager(); + + if ( ! $events instanceof EventManager ) { + return; + } + + $service_providers = array_merge( + $this->get_service_providers(), + $this->get_admin_providers(), + $this->get_replaceable_providers() + ); + + // Attach subscribers. + foreach ( $service_providers as $service_provider ) { + try { + $events->add_subscriber( $service_provider ); + } catch ( Exception $e ) { + // Do not subscribe. + } + } + } + + /** + * Gets providers that may be extended/replaced in lite/pro. + * + * @return array + */ + protected function get_replaceable_providers() { + return array(); + } + + /** + * Gets the service providers for EDD. + * + * @return array + */ + abstract protected function get_service_providers(); + + /** + * Gets the admin service providers for EDD. + * + * @return array + */ + abstract protected function get_admin_providers(); +} diff --git a/src/Extensions/ExtensionRegistry.php b/src/Extensions/ExtensionRegistry.php new file mode 100644 index 00000000000..17f9b51e53b --- /dev/null +++ b/src/Extensions/ExtensionRegistry.php @@ -0,0 +1,73 @@ +offsetExists( $pluginId ) ) { + throw new \InvalidArgumentException( sprintf( + 'The extension %d is already registered.', + $pluginId + ) ); + } + + $this->offsetSet( + $pluginId, + new Handler( $pluginFile, $pluginId, $pluginName, $currentVersion, $optionName ) + ); + } + + /** + * Returns all registered extensions, regardless of whether they have licenses activated. + * + * At some point we could make this public, just making it private for now so that we have + * flexibility to change exactly what it returns in the future. + * + * @since 2.11.4 + * @return Handler[] + */ + private function getExtensions() { + return $this->getArrayCopy(); + } + + /** + * Counts the number of licensed extensions active on this site. + * Note: This only looks at extensions registered via this registry, then filters down + * to those that have a license key entered. It does not check to verify if the license + * key is actually valid / not expired. + * + * @since 2.11.4 + * + * @return int + */ + public function countLicensedExtensions() { + $licensedExtensions = array_filter( $this->getExtensions(), function ( Handler $license ) { + return ! empty( $license->license_key ); + } ); + + return count( $licensedExtensions ); + } + +} diff --git a/src/Extensions/Handler.php b/src/Extensions/Handler.php new file mode 100644 index 00000000000..f52c6ed41c2 --- /dev/null +++ b/src/Extensions/Handler.php @@ -0,0 +1,419 @@ +file = $_file; + $this->item_id = absint( $_item_id ); + $this->item_name = $_item_name; + $this->item_shortname = $this->get_shortname(); + $this->version = $_version; + $this->pass_manager = new Pass_Manager(); + $this->edd_license = $this->get_license( $_optname ); + $this->license_key = $this->edd_license->key; + + $this->hooks(); + $this->update_global(); + } + + /** + * Set up hooks. + * + * @access private + * @return void + */ + private function hooks() { + + // Register settings. + add_filter( 'edd_settings_licenses', array( $this, 'settings' ), 1 ); + + // Check that license is valid once per week. + if ( ! $this->is_pro_license ) { + add_action( 'edd_weekly_scheduled_events', array( $this, 'weekly_license_check' ) ); + } + + // Updater. + add_action( 'init', array( $this, 'auto_updater' ) ); + + // Display notices to admins. + add_action( 'admin_notices', array( $this, 'notices' ) ); + add_action( 'in_plugin_update_message-' . plugin_basename( $this->file ), array( $this, 'plugin_row_license_missing' ), 10, 2 ); + + // Register plugins for beta support. + add_filter( 'edd_beta_enabled_extensions', array( $this, 'register_beta_support' ) ); + } + + /** + * Auto updater + * + * @return void + */ + public function auto_updater() { + + if ( ! current_user_can( 'manage_options' ) && ! edd_doing_cron() ) { + return; + } + + // Fall back to the highest license key if one is not saved for this extension or there isn't a pro license. + if ( empty( $this->license_key ) ) { + if ( $this->pass_manager->highest_license_key ) { + $this->license_key = $this->pass_manager->highest_license_key; + } + } + + // Don't check for updates if there isn't a license key. + if ( empty( $this->license_key ) ) { + return; + } + + $args = array( + 'version' => $this->version, + 'license' => $this->license_key, + 'item_id' => $this->item_id, + 'beta' => function_exists( 'edd_extension_has_beta_support' ) && edd_extension_has_beta_support( $this->item_shortname ), + ); + + // Set up the updater. + new Updater( + $this->file, + $args + ); + } + + /** + * Add license field to settings, unless the extension is included in the user's pass. + * + * @param array $settings + * @return array + */ + public function settings( $settings ) { + if ( $this->is_pro_license && $this->is_included_in_pass() ) { + return $settings; + } + + return array_merge( + $settings, + array( + array( + 'id' => "{$this->item_shortname}_license_key", + 'name' => $this->item_name, + 'type' => 'license_key', + 'options' => array( + 'is_valid_license_option' => "{$this->item_shortname}_license_active", + 'item_id' => $this->item_id, + ), + ), + ) + ); + } + + /** + * Check if license key is valid once per week + * + * @since 2.5 + * @return void + */ + public function weekly_license_check() { + + // Don't fire when saving settings. + if ( ! empty( $_POST['edd_settings'] ) ) { + return; + } + + if ( empty( $this->license_key ) ) { + return; + } + + if ( ! edd_doing_cron() ) { + return; + } + + // data to send in our API request + $api_params = array( + 'edd_action' => 'check_license', + 'license' => $this->license_key, + 'item_name' => urlencode( $this->item_name ), + 'item_id' => $this->item_id, + ); + + $api_handler = new API(); + $license_data = $api_handler->make_request( $api_params ); + if ( ! $license_data ) { + return false; + } + + $this->pass_manager->maybe_set_pass_flag( $this->license_key, $license_data ); + $this->edd_license->save( $license_data ); + } + + /** + * Admin notices for errors. + * + * @return void + */ + public function notices() { + if ( ! $this->should_show_error_notice() ) { + return; + } + + EDD()->notices->add_notice( + array( + 'id' => 'edd-missing-license', + 'class' => "error {$this->item_shortname}-license-error", + 'message' => sprintf( + /* translators: 1. opening anchor tag; 2. closing anchor tag */ + __( 'You have invalid or expired license keys for Easy Digital Downloads. %1$sActivate License(s)%2$s', 'easy-digital-downloads' ), + '', + '' + ), + 'is_dismissible' => false, + ) + ); + } + + /** + * Displays message inline on plugin row that the license key is missing + * + * @since 2.5 + * @return void + */ + public function plugin_row_license_missing( $plugin_data, $version_info ) { + static $showed_imissing_key_message = array(); + + if ( ! $this->is_license_valid() && empty( $showed_imissing_key_message[ $this->item_shortname ] ) ) { + echo ' ' . esc_html__( 'Enter valid license key for automatic updates.', 'easy-digital-downloads' ) . ''; + $showed_imissing_key_message[ $this->item_shortname ] = true; + } + } + + /** + * Adds this plugin to the beta page + * + * @param array $products + * @since 2.6.11 + * @return array + */ + public function register_beta_support( $products ) { + $products[ $this->item_shortname ] = $this->item_name; + + return $products; + } + + /** + * Gets the URL for the licensing tab. + * + * @since 3.1.1.4 + * @return string + */ + private function get_license_tab_url() { + return edd_get_admin_url( + array( + 'page' => 'edd-settings', + 'tab' => 'licenses', + ) + ); + } + + /** + * Whether the license is valid. + * + * @since 3.1.1.4 + * @return bool + */ + private function is_license_valid() { + return ! empty( $this->license_key ) && 'valid' === $this->edd_license->license; + } + + /** + * Gets the extension shortname. + * + * @since 3.1.1.4 + * @return string + */ + private function get_shortname() { + return 'edd_' . preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->item_name ) ) ); + } + + /** + * Maintain an array of active, licensed plugins that have a license key entered. + * This is to help us more easily determine if the site has a license key entered + * at all. Initializing it this way helps us limit the data to activated plugins only. + * If we relied on the options table (`edd_%_license_active`) then we could accidentally + * be picking up plugins that have since been deactivated. + * + * @see \EDD\Admin\Promos\Notices\License_Upgrade_Notice::__construct() + */ + private function update_global() { + global $edd_licensed_products; + if ( ! is_array( $edd_licensed_products ) ) { + $edd_licensed_products = array(); + } + $edd_licensed_products[ $this->item_shortname ] = (int) (bool) $this->is_license_valid(); + } + + /** + * Whether a given product is included in the customer's active pass. + * Note this is nearly a copy of what's in EDD\Licensing\Traits\Controls\is_included_in_pass(). + * + * @since 3.1.1.4 + * @return bool + */ + private function is_included_in_pass() { + // All Access and lifetime passes can access everything. + if ( $this->pass_manager->hasAllAccessPass() ) { + return true; + } + + $api = new \EDD\Admin\Extensions\ExtensionsAPI(); + $product_data = $api->get_product_data( array(), $this->item_id ); + if ( ! $product_data || empty( $product_data->categories ) ) { + return false; + } + + return (bool) $this->pass_manager->can_access_categories( $product_data->categories ); + } + + /** + * Helper method to determine if we should show the error notice. + * + * @since 3.1.1.4 + * @return bool + */ + private function should_show_error_notice() { + + // Included in pass. + if ( $this->is_included_in_pass() ) { + return false; + } + + if ( ! edd_is_admin_page() ) { + return false; + } + + // Not a pro license, but valid. + if ( ! $this->is_pro_license && $this->is_license_valid() ) { + return false; + } + + // Current user lacks permissions. + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return false; + } + + // It's the licenses tab. + if ( ! empty( $_GET['tab'] ) && 'licenses' === $_GET['tab'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + + return true; + } + + /** + * Gets the license object. + * The pro license is preferred; the individual extension license is used as a fallback. + * + * @since 3.1.4 + * @param null|string $option_name The custom option name for the license key. + * @return \EDD\Licensing\License + */ + private function get_license( $option_name ) { + $pro_license = new License( 'pro' ); + if ( ! empty( $pro_license->key ) && $this->is_included_in_pass() ) { + $this->is_pro_license = true; + return $pro_license; + } + + return new License( $this->item_name, $option_name ); + } +} diff --git a/src/Extensions/Updater.php b/src/Extensions/Updater.php new file mode 100644 index 00000000000..2b2897596cf --- /dev/null +++ b/src/Extensions/Updater.php @@ -0,0 +1,605 @@ +api_handler = new \EDD\Licensing\API(); + $this->api_url = trailingslashit( $this->api_handler->get_url() ); + $this->api_data = $_api_data; + $this->plugin_file = $_plugin_file; + $this->name = plugin_basename( $_plugin_file ); + $this->slug = basename( $_plugin_file, '.php' ); + $this->version = $_api_data['version']; + $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false; + $this->beta = ! empty( $this->api_data['beta'] ) ? true : false; + $this->failed_request_cache_key = 'edd_sl_failed_http_' . md5( $this->api_url ); + + $edd_plugin_data[ $this->slug ] = $this->api_data; + + // Set up hooks. + $this->init(); + } + + /** + * Set up WordPress filters to hook into WP's update process. + * + * @uses add_filter() + * + * @return void + */ + public function init() { + add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) ); + add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 ); + add_action( 'after_plugin_row', array( $this, 'show_update_notification' ), 10, 2 ); + add_action( 'admin_init', array( $this, 'show_changelog' ) ); + } + + /** + * Check for Updates at the defined API endpoint and modify the update array. + * + * This function dives into the update API just when WordPress creates its update array, + * then adds a custom API call and injects the custom plugin data retrieved from the API. + * It is reassembled from parts of the native WordPress plugin update code. + * See wp-includes/update.php line 121 for the original wp_update_plugins() function. + * + * @uses api_request() + * + * @param array $_transient_data Update array build by WordPress. + * @return array Modified update array with custom plugin data. + */ + public function check_update( $_transient_data ) { + + if ( ! is_object( $_transient_data ) ) { + $_transient_data = new \stdClass(); + } + + if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) { + return $_transient_data; + } + + $current = $this->get_repo_api_data(); + if ( false !== $current && is_object( $current ) && isset( $current->new_version ) ) { + if ( version_compare( $this->version, $current->new_version, '<' ) ) { + $_transient_data->response[ $this->name ] = $current; + } else { + // Populating the no_update information is required to support auto-updates in WordPress 5.5. + $_transient_data->no_update[ $this->name ] = $current; + } + } + $_transient_data->last_checked = time(); + $_transient_data->checked[ $this->name ] = $this->version; + + return $_transient_data; + } + + /** + * Get repo API data from store. + * Save to cache. + * + * @return \stdClass + */ + public function get_repo_api_data() { + $version_info = $this->get_cached_version_info(); + + if ( false === $version_info ) { + $version_info = $this->api_request( + 'plugin_latest_version', + array( + 'slug' => $this->slug, + 'beta' => $this->beta, + ) + ); + if ( ! $version_info ) { + return false; + } + + // This is required for your plugin to support auto-updates in WordPress 5.5. + $version_info->plugin = $this->name; + $version_info->id = $this->name; + $version_info->tested = $this->get_tested_version( $version_info ); + + $this->set_version_info_cache( $version_info ); + } + + return $version_info; + } + + /** + * Gets the plugin's tested version. + * + * @since 1.9.2 + * @param object $version_info + * @return null|string + */ + private function get_tested_version( $version_info ) { + + // There is no tested version. + if ( empty( $version_info->tested ) ) { + return null; + } + + // Strip off extra version data so the result is x.y or x.y.z. + list( $current_wp_version ) = explode( '-', get_bloginfo( 'version' ) ); + + // The tested version is greater than or equal to the current WP version, no need to do anything. + if ( version_compare( $version_info->tested, $current_wp_version, '>=' ) ) { + return $version_info->tested; + } + $current_version_parts = explode( '.', $current_wp_version ); + $tested_parts = explode( '.', $version_info->tested ); + + // The current WordPress version is x.y.z, so update the tested version to match it. + if ( isset( $current_version_parts[2] ) && $current_version_parts[0] === $tested_parts[0] && $current_version_parts[1] === $tested_parts[1] ) { + $tested_parts[2] = $current_version_parts[2]; + } + + return implode( '.', $tested_parts ); + } + + /** + * Show the update notification on multisite subsites. + * + * @param string $file + * @param array $plugin + */ + public function show_update_notification( $file, $plugin ) { + + // Return early if in the network admin, or if this is not a multisite install. + if ( is_network_admin() || ! is_multisite() ) { + return; + } + + // Allow single site admins to see that an update is available. + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + if ( $this->name !== $file ) { + return; + } + + // Do not print any message if update does not exist. + $update_cache = get_site_transient( 'update_plugins' ); + + if ( ! isset( $update_cache->response[ $this->name ] ) ) { + if ( ! is_object( $update_cache ) ) { + $update_cache = new \stdClass(); + } + $update_cache->response[ $this->name ] = $this->get_repo_api_data(); + } + + // Return early if this plugin isn't in the transient->response or if the site is running the current or newer version of the plugin. + if ( empty( $update_cache->response[ $this->name ] ) || version_compare( $this->version, $update_cache->response[ $this->name ]->new_version, '>=' ) ) { + return; + } + + printf( + '', + $this->slug, + $file, + in_array( $this->name, $this->get_active_plugins(), true ) ? 'active' : 'inactive' + ); + + echo ''; + echo '

    '; + + $changelog_link = ''; + if ( ! empty( $update_cache->response[ $this->name ]->sections->changelog ) ) { + $changelog_link = add_query_arg( + array( + 'edd_sl_action' => 'view_plugin_changelog', + 'plugin' => urlencode( $this->name ), + 'slug' => urlencode( $this->slug ), + 'TB_iframe' => 'true', + 'width' => 77, + 'height' => 911, + ), + self_admin_url( 'index.php' ) + ); + } + $update_link = add_query_arg( + array( + 'action' => 'upgrade-plugin', + 'plugin' => urlencode( $this->name ), + ), + self_admin_url( 'update.php' ) + ); + + printf( + /* translators: the plugin name. */ + esc_html__( 'There is a new version of %1$s available.', 'easy-digital-downloads' ), + esc_html( $plugin['Name'] ) + ); + + if ( ! current_user_can( 'update_plugins' ) ) { + echo ' '; + esc_html_e( 'Contact your network administrator to install the update.', 'easy-digital-downloads' ); + } elseif ( empty( $update_cache->response[ $this->name ]->package ) && ! empty( $changelog_link ) ) { + echo ' '; + printf( + /* translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. */ + __( '%1$sView version %2$s details%3$s.', 'easy-digital-downloads' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '' + ); + } elseif ( ! empty( $changelog_link ) ) { + echo ' '; + printf( + __( '%1$sView version %2$s details%3$s or %4$supdate now%5$s.', 'easy-digital-downloads' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '', + '', + '' + ); + } else { + printf( + ' %1$s%2$s%3$s', + '', + esc_html__( 'Update now.', 'easy-digital-downloads' ), + '' + ); + } + + do_action( "in_plugin_update_message-{$file}", $plugin, $plugin ); + + echo '

    '; + } + + /** + * Gets the plugins active in a multisite network. + * + * @return array + */ + private function get_active_plugins() { + $active_plugins = (array) get_option( 'active_plugins' ); + $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins' ); + + return array_merge( $active_plugins, array_keys( $active_network_plugins ) ); + } + + /** + * Updates information on the "View version x.x details" page with custom data. + * + * @uses api_request() + * + * @param mixed $_data + * @param string $_action + * @param object $_args + * @return object $_data + */ + public function plugins_api_filter( $_data, $_action = '', $_args = null ) { + + if ( 'plugin_information' !== $_action ) { + return $_data; + } + + if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) { + return $_data; + } + + $to_send = array( + 'slug' => $this->slug, + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => array(), + 'reviews' => false, + 'icons' => array(), + ), + ); + + // Get the transient where we store the api request for this plugin for 24 hours + $edd_api_request_transient = $this->get_cached_version_info(); + + //If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now. + if ( empty( $edd_api_request_transient ) ) { + + $api_response = $this->api_request( 'plugin_information', $to_send ); + + // Expires in 3 hours + $this->set_version_info_cache( $api_response ); + + if ( false !== $api_response ) { + $_data = $api_response; + } + } else { + $_data = $edd_api_request_transient; + } + + // Convert sections into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) { + $_data->sections = $this->convert_object_to_array( $_data->sections ); + } + + // Convert banners into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) { + $_data->banners = $this->convert_object_to_array( $_data->banners ); + } + + // Convert icons into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) { + $_data->icons = $this->convert_object_to_array( $_data->icons ); + } + + // Convert contributors into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) { + $_data->contributors = $this->convert_object_to_array( $_data->contributors ); + } + + if ( ! isset( $_data->plugin ) ) { + $_data->plugin = $this->name; + } + + return $_data; + } + + /** + * Convert some objects to arrays when injecting data into the update API + * + * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON + * decoding, they are objects. This method allows us to pass in the object and return an associative array. + * + * @since 3.6.5 + * + * @param stdClass $data + * + * @return array + */ + private function convert_object_to_array( $data ) { + if ( ! is_array( $data ) && ! is_object( $data ) ) { + return array(); + } + $new_data = array(); + foreach ( $data as $key => $value ) { + $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value; + } + + return $new_data; + } + + /** + * Calls the API and, if successfull, returns the object delivered by the API. + * + * @uses get_bloginfo() + * @uses wp_remote_get() + * @uses is_wp_error() + * + * @param string $_action The requested action. + * @param array $_data Parameters for the API action. + * @return false|object + */ + private function api_request( $_action, $_data ) { + $data = array_merge( $this->api_data, $_data ); + + if ( $data['slug'] !== $this->slug ) { + return false; + } + + if ( $this->request_recently_failed() ) { + return false; + } + + return $this->get_version_from_remote(); + } + + /** + * Determines if a request has recently failed. + * + * @since 1.9.1 + * + * @return bool + */ + private function request_recently_failed() { + $failed_request_details = get_option( $this->failed_request_cache_key ); + + // Request has never failed. + if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) { + return false; + } + + /* + * Request previously failed, but the timeout has expired. + * This means we're allowed to try again. + */ + if ( time() > $failed_request_details ) { + delete_option( $this->failed_request_cache_key ); + + return false; + } + + return true; + } + + /** + * Logs a failed HTTP request for this API URL. + * We set a timestamp for 1 hour from now. This prevents future API requests from being + * made to this domain for 1 hour. Once the timestamp is in the past, API requests + * will be allowed again. This way if the site is down for some reason we don't bombard + * it with failed API requests. + * + * @see EDD_SL_Plugin_Updater::request_recently_failed + * + * @since 1.9.1 + */ + private function log_failed_request() { + update_option( $this->failed_request_cache_key, strtotime( '+1 hour' ), false ); + } + + /** + * If available, show the changelog for sites in a multisite install. + */ + public function show_changelog() { + + if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) { + return; + } + + if ( empty( $_REQUEST['plugin'] ) ) { + return; + } + + if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) { + return; + } + + if ( ! current_user_can( 'update_plugins' ) ) { + wp_die( esc_html__( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $version_info = $this->get_repo_api_data(); + if ( isset( $version_info->sections ) ) { + $sections = $this->convert_object_to_array( $version_info->sections ); + if ( ! empty( $sections['changelog'] ) ) { + echo '
    ' . wp_kses_post( $sections['changelog'] ) . '
    '; + } + } + + exit; + } + + /** + * Gets the current version information from the remote site. + * + * @return array|false + */ + private function get_version_from_remote() { + + $request = $this->api_handler->make_request( $this->get_api_params() ); + + if ( $request && isset( $request->sections ) ) { + $request->sections = maybe_unserialize( $request->sections ); + } else { + return false; + } + + if ( isset( $request->banners ) ) { + $request->banners = maybe_unserialize( $request->banners ); + } + + if ( isset( $request->icons ) ) { + $request->icons = maybe_unserialize( $request->icons ); + } + + if ( ! empty( $request->sections ) ) { + foreach ( $request->sections as $key => $section ) { + $request->$key = (array) $section; + } + } + + return $request; + } + + /** + * Get the version info from the cache, if it exists. + * + * @param string $cache_key + * @return object + */ + public function get_cached_version_info( $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $cache = get_option( $cache_key ); + + // Cache is expired + if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) { + return false; + } + + // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point. + $cache['value'] = json_decode( $cache['value'] ); + if ( ! empty( $cache['value']->icons ) ) { + $cache['value']->icons = (array) $cache['value']->icons; + } + + return $cache['value']; + } + + /** + * Adds the plugin version information to the database. + * + * @param string $value + * @param string $cache_key + */ + public function set_version_info_cache( $value = '', $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $data = array( + 'timeout' => strtotime( '+3 hours', time() ), + 'value' => wp_json_encode( $value ), + ); + + update_option( $cache_key, $data, false ); + } + + /** + * Gets the parameters for the API request. + * + * @since 3.1.1.4 + * @return array + */ + private function get_api_params() { + return array( + 'edd_action' => 'get_version', + 'license' => ! empty( $this->api_data['license'] ) ? $this->api_data['license'] : '', + 'item_id' => isset( $this->api_data['item_id'] ) ? $this->api_data['item_id'] : false, + 'version' => isset( $this->api_data['version'] ) ? $this->api_data['version'] : false, + 'slug' => $this->slug, + 'beta' => $this->beta, + 'php_version' => phpversion(), + 'wp_version' => get_bloginfo( 'version' ), + 'easy-digital-downloads_version' => EDD_VERSION, + ); + } + + /** + * Gets the unique key (option name) for a plugin. + * + * @since 1.9.0 + * @return string + */ + private function get_cache_key() { + $string = $this->slug . $this->api_data['license'] . $this->beta; + + return 'edd_sl_' . md5( serialize( $string ) ); + } +} diff --git a/src/Licensing/API.php b/src/Licensing/API.php new file mode 100644 index 00000000000..2ea5c58c354 --- /dev/null +++ b/src/Licensing/API.php @@ -0,0 +1,90 @@ +api_url = $url; + } + } + + /** + * Gets the API URL. + * + * @since 3.1.1 + * @return string + */ + public function get_url() { + return $this->api_url; + } + + /** + * Makes a request to the Software Licensing API. + * + * @since 3.1.1 + * @param array $api_params The parameters for the API request. + * @return false|stdClass + */ + public function make_request( $api_params = array() ) { + if ( empty( $api_params ) || ! is_array( $api_params ) ) { + return false; + } + + $request = wp_remote_get( + $this->api_url, + array( + 'timeout' => 15, + 'sslverify' => true, + 'body' => $this->get_body( $api_params ), + ) + ); + + // If there was an API error, return false. + if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) { + return false; + } + + return json_decode( wp_remote_retrieve_body( $request ) ); + } + + /** + * Updates the API parameters with the defaults. + * + * @param array $api_params The parameters for the specific request. + * @return array + */ + private function get_body( array $api_params ) { + return wp_parse_args( + $api_params, + array( + 'url' => home_url(), + ) + ); + } +} diff --git a/src/Licensing/Ajax.php b/src/Licensing/Ajax.php new file mode 100644 index 00000000000..b200697fcd8 --- /dev/null +++ b/src/Licensing/Ajax.php @@ -0,0 +1,216 @@ + 'activate', + 'wp_ajax_edd_deactivate_extension_license' => 'deactivate', + 'wp_ajax_edd_delete_extension_license' => 'delete', + ); + } + + /** + * Attempt to activate an extension license. + * + * @since 3.1.1 + * @return void + */ + public function activate() { + if ( ! $this->can_manage() ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'You do not have permission to manage this extension.', 'easy-digital-downloads' ) ), + ) + ); + } + + if ( ! empty( $_POST['license'] ) ) { + $this->license_key = sanitize_text_field( $_POST['license'] ); + } + if ( ! $this->license_key ) { + wp_send_json_error( + array( + 'message' => __( 'No key provided.', 'easy-digital-downloads' ), + ) + ); + } + + $this->name = filter_input( INPUT_POST, 'item_name', FILTER_SANITIZE_SPECIAL_CHARS ); + $api_params = array( + 'edd_action' => 'activate_license', + 'license' => $this->license_key, + 'item_name' => $this->name, + 'item_id' => filter_input( INPUT_POST, 'item_id', FILTER_SANITIZE_NUMBER_INT ), + 'environment' => function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production', + ); + $custom_api = filter_input( INPUT_POST, 'api', FILTER_SANITIZE_URL ); + $api = new API( $custom_api ); + $license_data = $api->make_request( $api_params ); + + if ( empty( $license_data->success ) ) { + if ( ! empty( $license_data ) ) { + $messages = new \EDD\Licensing\Messages( + array( + 'status' => $license_data->error, + 'license_key' => $this->license_key, + 'expires' => ! empty( $license_data->expires ) ? $license_data->expires : false, + 'name' => $this->name, + 'subscription' => ! empty( $license_data->subscription ) ? $license_data->subscription : null, + 'api_url' => $custom_api, + ) + ); + $message = $messages->get_message(); + } else { + $message = __( 'Your license key could not be activated.', 'easy-digital-downloads' ); + } + wp_send_json_error( + array( + 'message' => wpautop( $message ), + ) + ); + } + + set_site_transient( 'update_plugins', null ); + + $pass_manager = new \EDD\Admin\Pass_Manager(); + $pass_manager->maybe_set_pass_flag( $this->license_key, $license_data ); + + // Clear the option for licensed extensions to force regeneration. + if ( ! empty( $license_data->license ) && 'valid' === $license_data->license ) { + delete_option( 'edd_licensed_extensions' ); + } + + edd_update_option( filter_input( INPUT_POST, 'key', FILTER_SANITIZE_SPECIAL_CHARS ), $this->license_key ); + $license = new License( $this->name ); + $license->save( $license_data ); + // Get the license again. + $this->license = new License( $this->name ); + $this->set_up_license_data(); + + wp_send_json_success( + array( + 'message' => $this->do_message( false ), + 'actions' => $this->get_actions( 'valid' ), + ) + ); + } + + /** + * Attempt to deactivate an extension license. + * + * @since 3.1.1 + * @return void + */ + public function deactivate() { + if ( ! $this->can_manage() ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'You do not have permission to manage this extension.', 'easy-digital-downloads' ) ), + ) + ); + } + + $this->name = filter_input( INPUT_POST, 'item_name', FILTER_SANITIZE_SPECIAL_CHARS ); + $item_id = filter_input( INPUT_POST, 'item_id', FILTER_SANITIZE_NUMBER_INT ); + $this->license = new License( $this->name ); + $this->license_key = $this->license->key; + $api_params = array( + 'edd_action' => 'deactivate_license', + 'license' => $this->license_key, + 'item_id' => urlencode( $item_id ), + 'environment' => function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production', + ); + $custom_api = filter_input( INPUT_POST, 'api', FILTER_SANITIZE_URL ); + $api = new API( $custom_api ); + $license_data = $api->make_request( $api_params ); + + $this->license->save( $license_data ); + + $pass_manager = new \EDD\Admin\Pass_Manager(); + $pass_manager->maybe_remove_pass_flag( $this->license_key ); + + wp_send_json_success( + array( + 'message' => wpautop( __( 'Your license key has been deactivated.', 'easy-digital-downloads' ) ), + 'actions' => $this->get_actions( $license_data->license ), + ) + ); + } + + /** + * Deletes an extension key and the related option. + * + * @since 3.1.1 + * @return void + */ + public function delete() { + if ( ! $this->can_manage( 'edd_licensehandler-delete' ) ) { + wp_send_json_error( + array( + 'message' => wpautop( __( 'You do not have permission to manage this extension.', 'easy-digital-downloads' ) ), + ) + ); + } + + $this->name = filter_input( INPUT_POST, 'item_name', FILTER_SANITIZE_SPECIAL_CHARS ); + $this->license = new License( $this->name ); + $this->license->delete(); + edd_delete_option( filter_input( INPUT_POST, 'key', FILTER_SANITIZE_SPECIAL_CHARS ) ); + + wp_send_json_success( + array( + 'message' => wpautop( __( 'License key deleted.', 'easy-digital-downloads' ) ), + ) + ); + } + + /** + * Whether the current user can manage the extension. + * Checks the user capabilities, tokenizer, and nonce. + * + * @since 3.1.1 + * @param string $nonce The name of the specific nonce to validate. + * @return bool + */ + protected function can_manage( $nonce = 'edd_licensehandler' ) { + if ( ! current_user_can( 'manage_shop_settings' ) ) { + return false; + } + $token = isset( $_POST['token'] ) ? sanitize_text_field( $_POST['token'] ) : ''; + $timestamp = isset( $_POST['timestamp'] ) ? sanitize_text_field( $_POST['timestamp'] ) : ''; + + if ( empty( $timestamp ) || empty( $token ) ) { + return false; + } + + return \EDD\Utils\Tokenizer::is_token_valid( $token, $timestamp ) && wp_verify_nonce( $_POST['nonce'], $nonce ); + } +} diff --git a/src/Licensing/License.php b/src/Licensing/License.php new file mode 100644 index 00000000000..344f474d57e --- /dev/null +++ b/src/Licensing/License.php @@ -0,0 +1,301 @@ +product_shortname = 'edd_' . preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $product_name ) ) ); + $this->option_name = "{$this->product_shortname}_license_active"; + + if ( 'pro' === $product_name ) { + $this->option_name = "{$this->product_shortname}_license"; + $this->single_site = false; + } elseif ( $custom_key_option && $custom_key_option !== $this->option_name ) { + $this->custom_key_option = $custom_key_option; + } + + $this->get(); + } + + /** + * Saves the license data option. + * + * @since 3.1.1 + * @param object $license_data + * @return bool + */ + public function save( $license_data ) { + if ( $this->single_site ) { + $updated = update_option( + $this->option_name, + $license_data, + false + ); + } else { + $updated = update_site_option( + $this->option_name, + $license_data + ); + } + + $this->get(); + /** + * Fires after a license is saved. + * + * @since 3.1.4 + * @param License $license The license object. + * @param object $license_data The license data. + */ + do_action( 'edd/license/saved', $this, $license_data ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + + return $updated; + } + + /** + * Deletes a license key and related license data. + * + * @since 3.1.1 + * @return void + */ + public function delete() { + if ( ! $this->single_site ) { + delete_site_option( $this->option_name ); + delete_site_option( "{$this->product_shortname}_license_key" ); + } else { + delete_option( $this->option_name ); + edd_delete_option( "{$this->product_shortname}_license_key" ); + if ( $this->custom_key_option ) { + edd_delete_option( $this->custom_key_option ); + } + } + + $this->get(); + /** + * Fires after a license is deleted. + * + * @since 3.1.4 + * @param string $product_shortname The product shortname. + * @param License $license The license object. + */ + do_action( 'edd/license/deleted', $this->product_shortname, $this ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + } + + /** + * Selectively update just one piece of the license data. + * + * @since 3.1.1 + * @param array $data + * @return bool + */ + public function update( array $data ) { + $option = $this->single_site ? get_option( $this->option_name, false ) : get_site_option( $this->option_name, false ); + $update = false; + foreach ( $data as $key => $value ) { + if ( $value !== $option->$key && in_array( $key, $this->get_editable_keys(), true ) ) { + $option->$key = $value; + $update = true; + } + } + + return $update ? $this->save( $option ) : false; + } + + /** + * Gets the license key for the license. + * + * @return string + */ + public function get_license_key() { + + $option_name = "{$this->product_shortname}_license_key"; + $option = trim( + $this->single_site ? + edd_get_option( $option_name, '' ) : + get_site_option( $option_name, '' ) + ); + + if ( ! empty( $option ) || 'edd_pro' === $this->product_shortname ) { + return $option; + } + + /** + * Allows for backwards compatibility with old license options, + * i.e. if the plugins had license key fields previously, the license + * handler will automatically pick these up and use those in lieu of the + * user having to reactivate their license. + */ + return trim( $this->custom_key_option ? edd_get_option( $this->custom_key_option, '' ) : $option ); + } + + /** + * Gets the license object mapped to the class defaults. + * + * @return EDD\Licensing\License + */ + public function get() { + $this->key = $this->get_license_key(); + if ( empty( $this->key ) ) { + return $this; + } + $option = $this->single_site ? get_option( $this->option_name, false ) : get_site_option( $this->option_name, false ); + if ( ! $option ) { + return $this; + } + + foreach ( (array) $option as $key => $value ) { + if ( property_exists( $this, $key ) ) { + $this->$key = $value; + } + } + + if ( ! $this->success && is_null( $this->error ) && 'valid' !== $this->license ) { + $this->error = $this->license; + } + + return $this; + } + + /** + * Only allow certain keys to be modified. + * + * @since 3.1.1 + * @return array + */ + private function get_editable_keys() { + return array( 'license', 'error', 'success' ); + } +} diff --git a/src/Licensing/Messages.php b/src/Licensing/Messages.php new file mode 100644 index 00000000000..5e5420960bd --- /dev/null +++ b/src/Licensing/Messages.php @@ -0,0 +1,422 @@ +license_data = wp_parse_args( + $license_data, + array( + 'status' => '', + 'expires' => '', + 'name' => '', + 'license_key' => '', + 'subscription' => false, + 'api_url' => null, + 'uri' => null, + ) + ); + $this->now = current_time( 'timestamp' ); + if ( ! empty( $this->license_data['expires'] ) && 'lifetime' !== $this->license_data['expires'] ) { + if ( ! is_numeric( $this->license_data['expires'] ) ) { + $this->expiration = strtotime( $this->license_data['expires'], $this->now ); + } else { + $this->expiration = $this->license_data['expires']; + } + } + } + + /** + * Gets the appropriate licensing message from an array of license data. + * + * @since 3.1.1 + * @return string + */ + public function get_message() { + + $message = $this->build_message(); + if ( ! $this->is_third_party_license() || empty( $this->license_data['name'] ) ) { + return $message; + } + + $name = $this->sanitize_third_party_name(); + /** + * Filters the message for a third-party license. + * + * Example: If your plugin name is "My Extension for Easy Digital Downloads" you + * would use the filter edd_licensing_third_party_message_my_extension_for_easy_digital_downloads + * @since 3.1.3 + * @param string $message The message. + * @param array $data The license data. + */ + return apply_filters( "edd_licensing_third_party_message_{$name}", $message, $this->license_data ); + } + + /** + * Builds the message based on the license data. + * + * @sinc 3.1.3 + * @return string + */ + private function build_message() { + $name = $this->license_data['name'] ?: __( 'license key', 'easy-digital-downloads' ); // phpcs:ignore WordPress.PHP.DisallowShortTernary.Found + + switch ( $this->license_data['status'] ) { + + case 'expired': + $message = $this->get_expired_message(); + break; + + case 'revoked': + case 'disabled': + $message = $this->get_disabled_message(); + break; + + case 'missing': + $message = $this->get_missing_message(); + break; + + case 'site_inactive': + $message = $this->get_inactive_message(); + break; + + case 'invalid': + case 'invalid_item_id': + case 'item_name_mismatch': + case 'key_mismatch': + $message = sprintf( + /* translators: the extension name. */ + __( 'This appears to be an invalid license key for %s.', 'easy-digital-downloads' ), + $name + ); + break; + + case 'no_activations_left': + $message = $this->get_no_activations_message(); + break; + + case 'license_not_activable': + $message = __( 'The key you entered belongs to a bundle, please use the product specific license key.', 'easy-digital-downloads' ); + break; + + case 'deactivated': + $message = __( 'Your license key has been deactivated.', 'easy-digital-downloads' ); + break; + + case 'valid': + $message = $this->get_valid_message(); + if ( $this->license_data['subscription'] && 'lifetime' !== $this->license_data['subscription'] ) { + $message .= $this->get_subscription_message(); + } + break; + + default: + $message = __( 'Unlicensed: currently not receiving updates.', 'easy-digital-downloads' ); + break; + } + + return $message; + } + + /** + * Gets the message text for a valid license. + * + * @since 3.1.1 + * @return string + */ + private function get_valid_message() { + if ( ! empty( $this->license_data['expires'] ) && 'lifetime' === $this->license_data['expires'] ) { + return __( 'License key never expires.', 'easy-digital-downloads' ); + } + + if ( ( $this->expiration > $this->now ) && ( $this->expiration - $this->now < ( DAY_IN_SECONDS * 30 ) ) ) { + return sprintf( + /* translators: the license expiration date. */ + __( 'Your license key expires soon! It expires on %s.', 'easy-digital-downloads' ), + edd_date_i18n( $this->expiration ) + ); + } + + return sprintf( + /* translators: the license expiration date. */ + __( 'Your license key expires on %s.', 'easy-digital-downloads' ), + edd_date_i18n( $this->expiration ) + ); + } + + /** + * Gets the message for a license's subscription. + * + * @since 3.1.1 + * @return string + */ + private function get_subscription_message() { + if ( 'active' === $this->license_data['subscription'] ) { + return ' ' . __( 'Your license subscription is active and will automatically renew.', 'easy-digital-downloads' ); + } + + return ' ' . sprintf( + /* translators: the license subscription status. */ + __( 'Your license subscription is %s and will not automatically renew.', 'easy-digital-downloads' ), + $this->get_subscription_status_label( $this->license_data['subscription'] ) + ); + } + + /** + * Gets the message for an expired license. + * + * @since 3.1.3 + * @return string + */ + private function get_expired_message() { + $url = $this->get_plugin_uri(); + if ( empty( $url ) && $this->is_third_party_license() ) { + if ( $this->expiration ) { + return sprintf( + /* translators: 1. license expiration date. */ + __( 'Your license key expired on %1$s. Please renew your license key.', 'easy-digital-downloads' ), + edd_date_i18n( $this->expiration ) + ); + } + + return __( 'Your license key has expired. Please renew your license key.', 'easy-digital-downloads' ); + } + + if ( empty( $url ) ) { + $args = array( + 'utm_medium' => 'license-notice', + 'utm_content' => 'expired', + ); + if ( ! empty( $this->license_data['license_key'] ) ) { + $args['license_key'] = $this->license_data['license_key']; + } + $url = edd_link_helper( + 'https://easydigitaldownloads.com/checkout/', + $args + ); + } + if ( $this->expiration ) { + return sprintf( + /* translators: 1. license expiration date; 2. opening link tag; 3. closing link tag. */ + __( 'Your license key expired on %1$s. Please %2$srenew your license key%3$s.', 'easy-digital-downloads' ), + edd_date_i18n( $this->expiration ), + '', + '' + ); + } + + return sprintf( + /* translators: 1. opening link tag; 2. closing link tag. */ + __( 'Your license key has expired. Please %1$srenew your license key%2$s.', 'easy-digital-downloads' ), + '', + '' + ); + } + + /** + * Gets the message for a disabled license. + * + * @since 3.1.3 + * @return string + */ + private function get_disabled_message() { + $url = $this->get_plugin_uri(); + if ( empty( $url ) && $this->is_third_party_license() ) { + return __( 'Your license key has been disabled.', 'easy-digital-downloads' ); + } + + if ( empty( $url ) ) { + $url = edd_link_helper( + 'https://easydigitaldownloads.com/support/', + array( + 'utm_medium' => 'license-notice', + 'utm_content' => 'revoked', + ) + ); + } + + return sprintf( + /* translators: 1. opening link tag; 2. closing link tag. */ + __( 'Your license key has been disabled. Please %1$scontact support%2$s for more information.', 'easy-digital-downloads' ), + '', + '' + ); + } + + /** + * Gets the message for a license at its activation limit. + * + * @since 3.1.3 + * @return string + */ + private function get_no_activations_message() { + $url = $this->get_plugin_uri(); + if ( empty( $url ) && $this->is_third_party_license() ) { + return __( 'Your license key has reached its activation limit.', 'easy-digital-downloads' ); + } + + if ( empty( $url ) ) { + $url = edd_link_helper( + 'https://easydigitaldownloads.com/your-account/', + array( + 'utm_medium' => 'license-notice', + 'utm_content' => 'at-limit', + ) + ); + } + + return sprintf( + /* translators: 1. opening link tag; 2 closing link tag. */ + __( 'Your license key has reached its activation limit. %1$sView possible upgrades%2$s now.', 'easy-digital-downloads' ), + '', + '' + ); + } + + /** + * Gets the message for an inactive license. + * + * @since 3.1.3 + * @return string + */ + private function get_inactive_message() { + $url = $this->get_plugin_uri(); + if ( empty( $url ) && $this->is_third_party_license() ) { + return __( 'Your license key is not active for this URL.', 'easy-digital-downloads' ); + } + + if ( empty( $url ) ) { + $url = edd_link_helper( + 'https://easydigitaldownloads.com/your-account/', + array( + 'utm_medium' => 'license-notice', + 'utm_content' => 'inactive', + ) + ); + } + + if ( empty( $this->license_data['name'] ) ) { + return sprintf( + /* translators: 1. opening link tag; 2. closing link tag. */ + __( 'Your license key is not active for this URL. Please %1$svisit your account page%2$s to manage your license keys.', 'easy-digital-downloads' ), + '', + '' + ); + } + + return sprintf( + /* translators: 1. the extension name; 2. opening link tag; 3. closing link tag. */ + __( 'Your %1$s license key is not active for this URL. Please %2$svisit your account page%3$s to manage your license keys.', 'easy-digital-downloads' ), + esc_html( $this->license_data['name'] ), + '', + '' + ); + } + + /** + * Gets the message for a missing license. + * + * @since 3.1.3 + * @return string + */ + private function get_missing_message() { + if ( $this->is_third_party_license() ) { + return __( 'Invalid license. Please verify it.', 'easy-digital-downloads' ); + } + + $url = edd_link_helper( + 'https://easydigitaldownloads.com/your-account/', + array( + 'utm_medium' => 'license-notice', + 'utm_content' => 'missing', + ) + ); + + return sprintf( + /* translators: 1. opening link tag; 2. closing link tag. */ + __( 'Invalid license. Please %1$svisit your account page%2$s and verify it.', 'easy-digital-downloads' ), + '', + '' + ); + } + + /** + * Gets the subscription status label as a translatable string. + * + * @since 3.1.1 + * @param string $status + * @return string + */ + private function get_subscription_status_label( $status ) { + $statii = array( + 'pending' => __( 'pending', 'easy-digital-downloads' ), + 'active' => __( 'active', 'easy-digital-downloads' ), + 'cancelled' => __( 'cancelled', 'easy-digital-downloads' ), + 'expired' => __( 'expired', 'easy-digital-downloads' ), + 'trialling' => __( 'trialling', 'easy-digital-downloads' ), + 'failing' => __( 'failing', 'easy-digital-downloads' ), + 'completed' => __( 'completed', 'easy-digital-downloads' ), + ); + + return array_key_exists( $status, $statii ) ? $statii[ $status ] : $status; + } + + /** + * Whether the license is a third-party license. + * + * @since 3.1.3 + * @return bool + */ + private function is_third_party_license() { + return ! empty( $this->license_data['api_url'] ); + } + + /** + * Gets the custom plugin URI for a third-party license. + * + * @since 3.1.3 + * @return string + */ + private function get_plugin_uri() { + return $this->is_third_party_license() && ! empty( $this->license_data['uri'] ) ? $this->license_data['uri'] : ''; + } + + /** + * Sanitizes the third-party license name for use as a hook. + * + * @since 3.1.4 + * @return string + */ + private function sanitize_third_party_name() { + $name = str_replace( ' ', '_', strtolower( $this->license_data['name'] ) ); + + return preg_replace( '/[^a-zA-Z0-9_]/', '', $name ); + } +} diff --git a/src/Licensing/Settings.php b/src/Licensing/Settings.php new file mode 100644 index 00000000000..20adb766892 --- /dev/null +++ b/src/Licensing/Settings.php @@ -0,0 +1,106 @@ +args = $args; + $this->license = new License( $this->args['name'], $this->args['options']['is_valid_license_option'] ); + $this->license_key = $this->license->key; + $this->name = $this->args['name']; + + $this->set_up_license_data(); + $this->do_settings_field(); + add_action( 'admin_print_footer_scripts', array( $this, 'do_script' ) ); + } + + /** + * Adds the licensing JS to the screen. + * + * @since 3.1.1 + * @return void + */ + public function do_script() { + if ( wp_script_is( 'edd-licensing' ) ) { + return; + } + wp_enqueue_script( 'edd-licensing', EDD_PLUGIN_URL . 'assets/js/edd-admin-licensing.js', array( 'jquery' ), EDD_VERSION, true ); + wp_localize_script( + 'edd-licensing', + 'EDDLicenseHandler', + array( + 'activating' => __( 'Activating', 'easy-digital-downloads' ), + 'deactivating' => __( 'Deactivating', 'easy-digital-downloads' ), + ) + ); + wp_print_scripts( 'edd-licensing' ); + ?> + + included_in_pass ) { + ?> +
    + included_in_pass ? ' readonly' : ''; ?> + args['options']['item_id'] ) ) : ?> + data-item="args['options']['item_id'] ); ?>" + + data-name="args['name'] ); ?>" + data-key="args['id'] ); ?>" + /> + args['options']['api_url'] ) ) { + ?> + + get_actions( $this->license->license, true ); + ?> +
    + do_message(); + do_action( 'edd/admin/settings/licenses/settings_field', $this->license, $this->included_in_pass ); + } +} diff --git a/src/Licensing/Traits/Controls.php b/src/Licensing/Traits/Controls.php new file mode 100644 index 00000000000..e91a04c20a4 --- /dev/null +++ b/src/Licensing/Traits/Controls.php @@ -0,0 +1,248 @@ +get_button_args( $status ); + $timestamp = time(); + if ( ! $echo ) { + ob_start(); + } + ?> +
    + + license_key ) && 'activate' === $button['action'] ) : ?> + + +
    + 'deactivate', + 'label' => __( 'Deactivate', 'easy-digital-downloads' ), + 'class' => 'secondary', + ); + } + + return array( + 'action' => 'activate', + 'label' => __( 'Activate', 'easy-digital-downloads' ), + 'class' => 'secondary', + ); + } + + /** + * Outputs the license key message. + * + * @since 3.1.1 + * @return void + */ + private function do_message( $echo = true ) { + if ( empty( $this->message ) ) { + return ''; + } + $classes = array( + 'edd-license-data', + "edd-license-{$this->class}", + $this->license_status, + ); + if ( ! $echo ) { + ob_start(); + } + ?> +
    +

    message ); ?>

    +
    + $this->license->license, + 'license_key' => $this->license_key, + 'expires' => ! empty( $this->license->expires ) ? $this->license->expires : '', + 'name' => $this->name, + ); + if ( ! empty( $this->args['options']['api_url'] ) ) { + $args['api_url'] = $this->args['options']['api_url']; + if ( ! empty( $this->args['options']['file'] ) && function_exists( 'get_plugin_data' ) ) { + $plugin_data = get_plugin_data( $this->args['options']['file'] ); + if ( ! empty( $plugin_data['PluginURI'] ) ) { + $args['uri'] = $plugin_data['PluginURI']; + } + } + } + $messages = new \EDD\Licensing\Messages( $args ); + $message = $messages->get_message(); + + if ( ! empty( $this->license ) ) { + $now = current_time( 'timestamp' ); + $expiration = ! empty( $this->license->expires ) + ? strtotime( $this->license->expires, $now ) + : false; + + // activate_license 'invalid' on anything other than valid, so if there was an error capture it + if ( false === $this->license->success ) { + $class = ! empty( $this->license->error ) ? $this->license->error : 'error'; + $license_status = "license-{$class}-notice"; + } else { + $class = 'valid'; + if ( 'lifetime' === $this->license->expires ) { + $license_status = 'license-lifetime-notice'; + } elseif ( ( $expiration > $now ) && ( $expiration - $now < ( DAY_IN_SECONDS * 30 ) ) ) { + $license_status = 'license-expires-soon-notice'; + } else { + $license_status = 'license-expiration-date-notice'; + } + } + } + + $pass_manager = $this->get_pass_manager(); + + if ( 'valid' !== $class && $pass_manager->has_pass_data && $this->is_included_in_pass() ) { + $this->included_in_pass = true; + $class = 'included-in-pass'; + /* translators: the all acess pass name. */ + $message = sprintf( __( 'Your %s gives you access to this extension.', 'easy-digital-downloads' ), '' . $pass_manager->get_pass_name() . '' ); + } + + $this->class = $class; + $this->message = $message; + $this->license_status = $license_status; + } + + /** + * Whether a given product is included in the customer's active pass. + * + * @since 3.1.1 + * @return bool + */ + private function is_included_in_pass() { + $pass_manager = $this->get_pass_manager(); + // All Access and lifetime passes can access everything. + if ( $pass_manager->hasAllAccessPass() && empty( $this->args['options']['api_url'] ) ) { + return true; + } + // If we don't know the item ID we can't assume anything. + if ( empty( $this->args['options']['item_id'] ) ) { + return false; + } + $api = new \EDD\Admin\Extensions\ExtensionsAPI(); + $api_item_id = $this->args['options']['item_id']; + $product_data = $api->get_product_data( array(), $api_item_id ); + if ( ! $product_data || empty( $product_data->categories ) ) { + return false; + } + + return (bool) $pass_manager->can_access_categories( $product_data->categories ); + } + + /** + * Gets the pass manager. + * + * @return EDD\Admin\Pass_Manager + */ + private function get_pass_manager() { + if ( $this->pass_manager ) { + return $this->pass_manager; + } + + $this->pass_manager = new Pass_Manager(); + + return $this->pass_manager; + } +} diff --git a/src/Lite/Admin/Menu.php b/src/Lite/Admin/Menu.php new file mode 100644 index 00000000000..f92bd992ad7 --- /dev/null +++ b/src/Lite/Admin/Menu.php @@ -0,0 +1,60 @@ +has_pass() ) { + + add_submenu_page( + 'edit.php?post_type=download', + esc_html__( 'Upgrade to Pro', 'easy-digital-downloads' ), + esc_html__( 'Upgrade to Pro', 'easy-digital-downloads' ), + 'manage_shop_settings', + edd_link_helper( + 'https://easydigitaldownloads.com/lite-upgrade', + array( + 'utm_medium' => 'admin-menu', + 'utm_content' => 'upgrade-to-pro', + ) + ) + ); + add_action( 'admin_head', array( $this, 'adjust_pro_menu_item_class' ) ); + } + } + + /** + * Adds the custom pro menu item class. + * + * @since 3.1.1 + * @return void + */ + public function adjust_pro_menu_item_class() { + new \EDD\Admin\Menu\LinkClass( 'https://easydigitaldownloads.com/lite-upgrade', 'edd-sidebar__upgrade-pro' ); + } +} diff --git a/src/Lite/Admin/PassHandler/Connect.php b/src/Lite/Admin/PassHandler/Connect.php new file mode 100644 index 00000000000..5fb1c2b42a3 --- /dev/null +++ b/src/Lite/Admin/PassHandler/Connect.php @@ -0,0 +1,154 @@ +handler = $handler; + $this->pass_manager = new Pass_Manager(); + } + + public static function get_subscribed_events() { + return array( + 'wp_ajax_nopriv_easydigitaldownloads_connect_process' => 'process', + ); + } + + /** + * Process EDD Connect. + * + * @since 3.1.1 + */ + public function process() { + + $error = esc_html__( 'There was an error while installing an upgrade. Please download the plugin from easydigitaldownloads.com and install it manually.', 'easy-digital-downloads' ); + + // Verify params present (oth & download link). + $post_oth = ! empty( $_REQUEST['oth'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['oth'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification + $post_url = ! empty( $_REQUEST['file'] ) ? esc_url_raw( wp_unslash( $_REQUEST['file'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification + + if ( empty( $post_oth ) || empty( $post_url ) ) { + wp_send_json_error( $error ); + } + + // Verify oth. + $oth = get_option( 'edd_connect_token' ); + + if ( empty( $post_oth ) || hash_hmac( 'sha512', $oth, wp_salt() ) !== $post_oth ) { + wp_send_json_error( $error ); + } + + // Delete so cannot replay. + delete_option( 'edd_connect_token' ); + + // Check license key. + $license_key = ! empty( $_REQUEST['key'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification + if ( empty( $license_key ) ) { + wp_send_json_error( __( 'No key provided.', 'easy-digital-downloads' ) ); + } + + if ( ! empty( $_REQUEST['license'] ) ) { + update_site_option( 'edd_pro_license_key', $license_key ); + $license_data = (object) $_REQUEST['license']; + $this->handler->update_pro_license( $license_data ); + $this->pass_manager->maybe_set_pass_flag( $license_key, $license_data ); + } + + if ( ! get_option( 'edd_pro_activation_date', false ) ) { + update_option( 'edd_pro_activation_date', time() ); + } + + // If pro is already active, return a success message. + if ( edd_is_pro() ) { + wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'easy-digital-downloads' ) ); + } + + // Set the current screen to avoid undefined notices. + set_current_screen( 'download_page_edd-settings' ); + + // Verify pro not installed. + $active = activate_plugin( 'easy-digital-downloads-pro/easy-digital-downloads.php', '', false, true ); + if ( ! is_wp_error( $active ) ) { + wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'easy-digital-downloads' ) ); + } + + // Prepare variables. + $url = esc_url_raw( + edd_get_admin_url( + array( 'page' => 'edd-settings' ) + ) + ); + $creds = request_filesystem_credentials( $url, '', false, false, null ); + + // Check for file system permissions. + if ( false === $creds || ! WP_Filesystem( $creds ) ) { + wp_send_json_error( + esc_html__( 'There was an error while installing an upgrade. Please check file system permissions and try again. Also, you can download the plugin from easydigitaldownloads.com and install it manually.', 'easy-digital-downloads' ) + ); + } + + /* + * We do not need any extra credentials if we have gotten this far, so let's install the plugin. + */ + + // Do not allow WordPress to search/download translations, as this will break JS output. + remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); + + // Create the plugin upgrader with our custom skin. + $installer = new \EDD\Admin\Installers\PluginSilentUpgrader( new \EDD\Admin\Installers\Install_Skin() ); + + // Error check. + if ( ! method_exists( $installer, 'install' ) ) { + wp_send_json_error( $error ); + } + + $installer->install( $post_url ); // phpcs:ignore + + // Flush the cache and return the newly installed plugin basename. + wp_cache_flush(); + + $plugin_basename = $installer->plugin_info(); + + if ( $plugin_basename ) { + + // Activate the plugin silently. + $activated = activate_plugin( $plugin_basename, '', false, true ); + + if ( ! is_wp_error( $activated ) ) { + wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'easy-digital-downloads' ) ); + } + + $error = esc_html__( 'Easy Digital Downloads (Pro) was installed, but needs to be activated on the Plugins page inside your WordPress admin.', 'easy-digital-downloads' ); + } + + wp_send_json_error( $error ); + } +} diff --git a/src/Lite/Admin/PassHandler/Pointer.php b/src/Lite/Admin/PassHandler/Pointer.php new file mode 100644 index 00000000000..4091bbabc6c --- /dev/null +++ b/src/Lite/Admin/PassHandler/Pointer.php @@ -0,0 +1,181 @@ + 'add_menu_item_class', + 'user_register' => 'dismiss_pointers_for_new_users', + 'edd_pointers' => 'pointers', + ); + } + + /** + * Add class to the Onboarding Wizard subpage menu item. + * + * @since 3.1.1 + */ + public function add_menu_item_class() { + new \EDD\Admin\Menu\LinkClass( 'edd-settings', 'edd-settings__menu-item' ); + } + + /** + * Maybe show an admin pointer showing a message about the new menu locations. + * + * @since 3.1.1 + * @return void + */ + public function pointers( $pointers ) { + if ( ! current_user_can( 'manage_options' ) ) { + return $pointers; + } + if ( ! $this->has_pass_no_license() ) { + return $pointers; + } + + // Add pointers that need to be registered when we are not on an EDD Admin screen. + if ( ! edd_is_admin_page( 'download' ) ) { + $pointers[] = array( + 'pointer_id' => 'edd_activate_pass_non_edd_setting_page', + 'target' => '#menu-posts-download', + 'options' => array( + 'content' => $this->get_default_pass_upgrade_content(), + 'position' => array( + 'edge' => 'left', + 'align' => 'middle', + ), + ), + ); + } + + // Add pointers that need to be registered when we are on an EDD Admin Page, but not a settings screen. + $screen = get_current_screen(); + if ( 'dashboard' !== $screen->id && edd_is_admin_page() && ! edd_is_admin_page( 'settings' ) ) { + $pointers[] = array( + 'pointer_id' => 'edd_activate_pass_edd_setting_page', + 'target' => '.edd-settings__menu-item:not(.current)', + 'options' => array( + 'content' => $this->get_default_pass_upgrade_content(), + 'position' => array( + 'edge' => 'left', + 'align' => 'middle', + ), + ), + ); + } + + // Add this pointer on the general EDD settings screen. + if ( edd_is_admin_page( 'settings', 'general' ) ) { + $pointers[] = array( + 'pointer_id' => 'edd_activate_pass_button', + 'target' => '.edd-pass-handler__action', + 'options' => array( + 'content' => sprintf( + '

    %s

    %s

    ', + __( 'Install the Pro Version!', 'easy-digital-downloads' ), + __( 'We see you already have an active pass. Click here to verify your license key and we\'ll connect you to install Easy Digital Downloads (Pro).', 'easy-digital-downloads' ) + ), + 'position' => array( + 'edge' => 'bottom', + 'align' => 'left', + ), + ), + ); + } + + return $pointers; + } + + /** + * Gets the dismissed_wp_pointers user meta. + * + * @since 3.1.1 + * @param int $user_id THe current user ID. + * @return array + */ + public function get_user_dismissals( $user_id ) { + return explode( ',', (string) get_user_meta( $user_id, 'dismissed_wp_pointers', true ) ); + } + + /** + * Dismisses the pointer notices for new users. + * + * @since 3.1.1 + * @param int $user_id The new user ID. + * @return void + */ + public function dismiss_pointers_for_new_users( $user_id ) { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + if ( $this->has_pass_no_license() ) { + return; + } + + $dismissals = $this->get_user_dismissals( $user_id ); + + if ( ! in_array( 'edd_activate_pass', $dismissals, true ) ) { + $dismissals[] = 'edd_activate_pass'; + } + + if ( ! in_array( 'edd_activate_pass_button', $dismissals, true ) ) { + $dismissals[] = 'edd_activate_pass_button'; + } + + if ( ! in_array( 'edd_activate_pass_non_edd_setting_page', $dismissals, true ) ) { + $dismissals[] = 'edd_activate_pass_non_edd_setting_page'; + } + + update_user_meta( $user_id, 'dismissed_wp_pointers', implode( ',', array_filter( $dismissals ) ) ); + } + + /** + * Checks whether the site has an active pass, but hasn't entered the pro license key yet. + * + * @since 3.1.1 + * @return bool + */ + private function has_pass_no_license() { + $pro_license = new \EDD\Licensing\License( 'pro' ); + if ( ! empty( $pro_license->key ) ) { + return false; + } + $pass_manager = new \EDD\Admin\Pass_Manager(); + + return ! empty( $pass_manager->highest_license_key ); + } + + /** + * Gets the default notice content for users with passes. + * + * @since 3.1.1.2 + * @return string + */ + private function get_default_pass_upgrade_content() { + $settings_url = edd_get_admin_url( + array( + 'page' => 'edd-settings', + ) + ); + + return sprintf( + '

    %s

    %s

    ', + __( 'You\'re eligible to install EDD (Pro)!', 'easy-digital-downloads' ), + sprintf( + /* translators: 1. opening anchor tag; 2. closing anchor tag */ + __( 'Good news! With your pass subscription, you can install the Pro version of Easy Digital Downloads. %1$sVisit the settings page%2$s to verify your license and access Pro only features.', 'easy-digital-downloads' ), + '', + '' + ) + ); + } +} diff --git a/src/Lite/Core.php b/src/Lite/Core.php new file mode 100644 index 00000000000..a86a6659dbd --- /dev/null +++ b/src/Lite/Core.php @@ -0,0 +1,60 @@ +get_lite_providers() ); + } + + /** + * Gets the admin service providers. + * + * @since 3.1.1 + * @return array + */ + protected function get_admin_providers() { + return array_merge( parent::get_admin_providers(), $this->get_lite_admin_providers() ); + } + + /** + * Gets the lite service providers. + * + * @since 3.1.1 + * @return array + */ + private function get_lite_providers() { + return array( + new Admin\PassHandler\Connect( $this->pass_handler ), + ); + } + + /** + * Gets the lite admin providers. + * + * @since 3.1.1 + * @return array + */ + private function get_lite_admin_providers() { + if ( ! is_admin() ) { + return array(); + } + + return array( + new Admin\Menu(), + new Admin\PassHandler\Pointer(), + ); + } +} diff --git a/src/Models/Notification.php b/src/Models/Notification.php new file mode 100644 index 00000000000..c8c5c1e6d8f --- /dev/null +++ b/src/Models/Notification.php @@ -0,0 +1,192 @@ + 'int', + 'remote_id' => 'int', + 'buttons' => 'array', + 'conditions' => 'array', + 'dismissed' => 'bool', + ); + + /** + * Constructor + * + * @param array $data Row from the database. + */ + public function __construct( $data = array() ) { + foreach ( $data as $property => $value ) { + if ( property_exists( $this, $property ) ) { + $this->{$property} = $this->castAttribute( $property, $value ); + } + } + } + + /** + * Casts a property to its designated type. + * + * @todo Move to trait or base class. + * + * @since 2.11.4 + * + * @param string $propertyName + * @param mixed $value + * + * @return bool|float|int|mixed|string|null + */ + private function castAttribute( $propertyName, $value ) { + if ( ! array_key_exists( $propertyName, $this->casts ) ) { + return $value; + } + + // Let null be null. + if ( is_null( $value ) ) { + return null; + } + + switch ( $this->casts[ $propertyName ] ) { + case 'array' : + return json_decode( $value, true ); + case 'bool' : + return (bool) $value; + case 'float' : + return (float) $value; + case 'int' : + return (int) $value; + case 'string' : + return (string) $value; + default : + return $value; + } + } + + /** + * Returns the icon name to use for this notification type. + * + * @since 2.11.4 + * + * @return string + */ + public function getIcon() { + switch ( $this->type ) { + case 'warning' : + return 'warning'; + case 'error' : + return 'dismiss'; + case 'info' : + return 'admin-generic'; + case 'success' : + default : + return 'yes-alt'; + } + } + + /** + * Converts this model to an array. + * + * @todo Move to trait. + * + * @since 2.11.4 + * + * @return array + */ + public function toArray() { + $data = array(); + + /* + * get_object_vars() returns non-public properties when used within the class + * so we're using a ReflectionClass to get the public properties only. + */ + $object = new \ReflectionClass( $this ); + + foreach ( $object->getProperties( \ReflectionProperty::IS_PUBLIC ) as $property ) { + if ( $property instanceof \ReflectionProperty && isset( $this->{$property->name} ) ) { + $data[ $property->name ] = $this->{$property->name}; + } + } + + $data['icon_name'] = $this->getIcon(); + + /* Translators: %s - a length of time (e.g. "1 second") */ + $data['relative_date'] = sprintf( __( '%s ago', 'easy-digital-downloads' ), human_time_diff( strtotime( $this->date_created ) ) ); + + return $data; + } + +} diff --git a/src/Orders/Number.php b/src/Orders/Number.php new file mode 100644 index 00000000000..05ecd0b8cc1 --- /dev/null +++ b/src/Orders/Number.php @@ -0,0 +1,278 @@ +sequential = edd_get_option( 'enable_sequential', false ); + + // If sequential order numbers are enabled, we need to make sure the prefix and suffix are loaded. + if ( $this->sequential ) { + $this->prefix = $this->get_prefix(); + $this->postfix = $this->get_postfix(); + } + } + + /** + * Gets the formatted order number; if sequential order numbers are enabled, + * this function also updates the last payment number in the database. + * + * @since 3.1.2 + * @return string|bool A formatted order number, or false if sequential order numbers are disabled. + */ + public function apply() { + if ( false === $this->sequential ) { + return ''; + } + + $next_order_number = $this->get_next_payment_number(); + if ( ! $next_order_number ) { + return ''; + } + + return $this->format( $next_order_number ); + } + + /** + * Gets the unformatted next order number from the database. + * + * @since 3.1.2 + * @return false|int False if sequential order numbers are disabled, otherwise the next order number to apply. + */ + public function get_next_payment_number() { + if ( false === $this->sequential ) { + return false; + } + + return (int) apply_filters( 'edd_get_next_payment_number', $this->get_next() ); + } + + /** + * Formats the order number with the sequential pre/postfixes. + * + * @since 3.1.2 + * @param int $number + * @return string|int + */ + public function format( $number ) { + + if ( ! $this->sequential || ! is_numeric( $number ) ) { + return $number; + } + + $prefix = $this->prefix; + $number = absint( $number ); + $postfix = $this->postfix; + + $formatted_number = $prefix . $number . $postfix; + + return apply_filters( 'edd_format_payment_number', $formatted_number, $prefix, $number, $postfix ); + } + + /** + * Given an order number, unformat it by removing the pre/postfix. + * + * @since 3.1.2 + * @param string $number + * @return int + */ + public function unformat( $number ) { + + if ( ! $this->sequential ) { + return $number; + } + + $prefix = $this->prefix; + $postfix = $this->postfix; + + // Remove prefix + $number = preg_replace( '/' . $prefix . '/', '', $number, 1 ); + + // Remove the postfix + $length = strlen( $number ); + $postfix_pos = strrpos( $number, strval( $postfix ) ); + if ( false !== $postfix_pos ) { + $number = substr_replace( $number, '', $postfix_pos, $length ); + } + + return apply_filters( 'edd_remove_payment_prefix_postfix', intval( $number ), $prefix, $postfix ); + } + + /** + * Gets the next order number from the database. This also updates the "next" + * order number in the database with the number which is being returned. + * + * @since 3.1.2 + * @return int + */ + private function get_next() { + global $wpdb; + $number = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name=%s", 'edd_next_order_number' ) ); + + // The next order number exists, so increment it and update the database. + if ( ! is_null( $number ) ) { + $number = (int) $number; + + // Update the option for the next order number now. + $this->update( $number + 1 ); + + return $number; + } + + // If the option is not set for the next order number, we need to get the last order number from the database. + $order_number = (int) $this->get_last(); + $next_number = $order_number + 1; + $this->insert( $next_number ); + + return $order_number; + } + + /** + * Updates the last order number in the database. + * + * This doesn't use $wpdb->update() and instead opts for using $wpdb->query() because + * in our testing we're a consistent improvment in performance. While it's measured in microseconds + * it is in the effort to remove any race condition we are running into here. + * + * @since 3.1.2 + * @param int $value + * @return bool + */ + private function update( $value ) { + global $wpdb; + + // We should never hit this....but just in case, we need to unformat it. + if ( ! is_numeric( $value ) ) { + $value = $this->unformat( $value ); + } + + $value = $wpdb->prepare( '%d', $value ); + + return $wpdb->query( + "UPDATE {$wpdb->options} SET option_value = {$value} WHERE option_name = 'edd_next_order_number'" + ); + } + + /** + * Adds the last order number to the database. + * + * @since 3.1.2 + * @param int $value + * @return bool + */ + private function insert( $value ) { + global $wpdb; + + return $wpdb->insert( + $wpdb->options, + array( + 'option_name' => 'edd_next_order_number', + 'option_value' => $value, + ), + array( '%s', '%d' ) + ); + } + + /** + * Gets the last payment number from the database, or from the option. + * + * @return string + */ + private function get_last() { + // If this was the first order after switching to useing the 'next' order number option, we need to get the last order number from the database. + $last_payment_number = $this->get_last_payment_number(); + + if ( ! is_null( $last_payment_number ) ) { + return $last_payment_number + 1; + } + + // If they enabled sequential order numbers after having orders, we need to get the last order number from the database. + $last_order = edd_get_orders( + array( + 'number' => 1, + 'orderby' => 'id', + 'order' => 'DESC', + ) + ); + + if ( ! empty( $last_order ) ) { + $last_order = reset( $last_order ); + + if ( $last_order instanceof EDD\Orders\Order && ! empty( $last_order->order_number ) ) { + return $this->unformat( $last_order->order_number ); + } + } + + // If all else fails, just get the starting number from the setting. + return $this->get_start(); + } + + /** + * Gets the EDD sequential starting number. + * Used when the last order number cannot otherwise be found. + * + * @return int + */ + private function get_start() { + return (int) edd_get_option( 'sequential_start', 1 ); + } + + /** + * Gets the sequential prefix. + * + * @since 3.1.2 + * @return string + */ + private function get_prefix() { + return (string) edd_get_option( 'sequential_prefix' ); + } + + /** + * Gets the sequential postfix. + * + * @since 3.1.2 + * @return string + */ + private function get_postfix() { + return (string) edd_get_option( 'sequential_postfix' ); + } + + /** + * Gets the last payment number from the database. + * + * @since 3.1.2 + * + * @return int + */ + private function get_last_payment_number() { + global $wpdb; + return $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name=%s", 'edd_last_payment_number' ) ); + } +} diff --git a/src/Structured_Data.php b/src/Structured_Data.php new file mode 100644 index 00000000000..8b1b43b30b3 --- /dev/null +++ b/src/Structured_Data.php @@ -0,0 +1,377 @@ +hooks(); + } + + /** + * Register the hooks. + * + * @since 3.0 + */ + private function hooks() { + add_action( 'wp_footer', array( $this, 'output_structured_data' ) ); + } + + /** + * Get raw data. This data is not formatted in any way. + * + * @access public + * @since 3.0 + * + * @return array Raw data. + */ + public function get_data() { + return $this->data; + } + + /** + * Reset the data. + * + * @since 3.1.4 + * @return void + */ + public function reset() { + $this->data = array(); + } + + /** + * Set structured data. This is then output in `wp_footer`. + * + * @access private + * @since 3.0 + * + * @param array $data JSON-LD structured data. + * + * @return bool True if data was set, false otherwise. + */ + private function set_data( $data = null ) { + if ( is_null( $data ) || empty( $data ) || ! is_array( $data ) ) { + return false; + } + + // Ensure the type exists and matches the format expected. + if ( ! isset( $data['@type'] ) || ! preg_match( '|^[a-zA-Z]{1,20}$|', $data['@type'] ) ) { + return false; + } + + $this->data[] = $data; + + return true; + } + + /** + * Generate the structured data for a given context. + * + * @access public + * @since 3.0 + * + * @param mixed string|false $context Default empty as the class figures out what the context is automatically. + * @param mixed $args Arguments that can be passed to the generators. + * + * @return string + */ + public function generate_structured_data( $context = false, $args = null ) { + if ( is_singular( 'download' ) || 'download' === $context ) { + $this->generate_download_data( $args ); + } + + /** + * Allow actions to fire here to allow for different types of structured data. + * + * @since 3.0 + * + * @param EDD_Structured_Data Instance of the object. + * @param mixed string|bool $context Context. + */ + do_action( 'edd_generate_structured_data', $this, $context ); + } + + /** + * Generate structured data for a download. + * + * @access public + * @since 3.0 + * + * @param mixed int|EDD_Download Download ID or EDD_Download object to generate data for. + * + * @return bool True if data generated successfully, false otherwise. + */ + public function generate_download_data( $download = false ) { + if ( false === $download || is_null( $download ) ) { + global $post; + $download = edd_get_download( $post->ID ); + } elseif ( is_int( $download ) ) { + $download = edd_get_download( $download ); + } else { + return false; + } + + // Return false if a download object could not be retrieved. + if ( ! $download instanceof \EDD_Download ) { + return false; + } + + $data = array( + '@type' => 'Product', + 'name' => $download->post_title, + 'url' => $this->get_url( $download ), + 'brand' => array( + '@type' => 'https://schema.org/Brand', + 'name' => get_bloginfo( 'name' ), + ), + 'sku' => '-' === $download->get_sku() + ? $download->ID + : $download->get_sku(), + 'image' => $this->get_image( $download ), + ); + + // Add description if it is not blank. + if ( '' !== $download->post_excerpt ) { + $data['description'] = $download->post_excerpt; + } + + $data['offers'] = $this->get_offers( $download, $data ); + + $download_categories = wp_get_post_terms( $download->ID, 'download_category' ); + if ( is_array( $download_categories ) && ! empty( $download_categories ) ) { + $download_categories = wp_list_pluck( $download_categories, 'name' ); + $data['category'] = implode( ', ', $download_categories ); + } + + /** + * Filter the structured data for a download. + * + * @since 3.0 + * + * @param array $data Structured data for a download. + * @param EDD_Download $download Download object (added in 3.1.4). + */ + $data = apply_filters( 'edd_generate_download_structured_data', $data, $download ); + + $this->set_data( $data ); + + return true; + } + + /** + * Sanitize the structured data. + * + * @access private + * @since 3.0 + * + * @param array $data Data to be sanitized. + * + * @return array Sanitized data. + */ + private function sanitize_data( $data ) { + $sanitized = array(); + + // Bail with an empty array if data does not exist. + if ( ! $data || ! is_array( $data ) ) { + return $sanitized; + } + + foreach ( $data as $key => $value ) { + $key = sanitize_text_field( $key ); + $sanitized[ $key ] = is_array( $value ) + ? $this->sanitize_data( $value ) + : sanitize_text_field( $value ); + } + + return $sanitized; + } + + /** + * Encode the data, ready for output. + * + * @access private + * @since 3.0 + */ + private function encoded_data() { + $this->generate_structured_data(); + + // Bail if no data was generated. + $data = $this->get_data(); + if ( empty( $data ) ) { + return; + } + + $structured_data = $this->sanitize_data( $data ); + foreach ( $structured_data as $k => $v ) { + $structured_data[ $k ]['@context'] = 'https://schema.org/'; + } + + return wp_json_encode( $structured_data ); + } + + /** + * Output the structured data. + * + * @access public + * @since 3.0 + * + * @return bool True by default, false if structured data does not exist. + */ + public function output_structured_data() { + + $output_data = $this->encoded_data(); + if ( empty( $output_data ) ) { + return false; + } + + echo ''; + + return true; + } + + /** + * Gets the offer(s) for a download. + * + * @since 3.1.4 + * @param EDD_Download $download Download object. + * @return array + */ + private function get_offers( $download ) { + if ( $download->has_variable_prices() ) { + $variable_prices = $download->get_prices(); + + $offers = array(); + foreach ( $variable_prices as $key => $price ) { + $offers[] = $this->get_variable_price_offer( $price, $download, $key ); + } + + return $offers; + } + + return $this->get_single_price_offer( $download ); + } + + /** + * Gets the offer data for a variable price. + * + * @since 3.1.4 + * @param array $price + * @param EDD_Download $download + * @param int $key + * @return array + */ + private function get_variable_price_offer( $price, $download, $key ) { + $price_id = isset( $price['index'] ) ? $price['index'] : $key; + $offer = array( + '@type' => 'Offer', + 'price' => $price['amount'], + 'priceCurrency' => edd_get_currency(), + 'priceValidUntil' => date( 'c', time() + YEAR_IN_SECONDS ), + 'itemOffered' => edd_get_download_name( $download->ID, $price_id ), + 'url' => $this->get_url( $download ), + 'availability' => 'https://schema.org/InStock', + 'seller' => array( + '@type' => 'Organization', + 'name' => get_bloginfo( 'name' ), + ), + ); + + /** + * Filter the structured data for a variable price offer. + * + * @since 3.1.4 + * @param array $offer Structured data for a variable price offer. + * @param EDD_Download $download Download object. + * @param array $price Price data. + */ + return apply_filters( 'edd_generate_download_structured_data_variable_price_offer', $offer, $download, $price ); + } + + /** + * Gets the offer data for a single price product. + * + * @param EDD_Download $download + * @return array + */ + private function get_single_price_offer( $download ) { + $offer = array( + '@type' => 'Offer', + 'price' => $download->get_price(), + 'priceCurrency' => edd_get_currency(), + 'priceValidUntil' => null, + 'url' => $this->get_url( $download ), + 'availability' => 'https://schema.org/InStock', + 'seller' => array( + '@type' => 'Organization', + 'name' => get_bloginfo( 'name' ), + ), + ); + + /** + * Filter the structured data for a single price offer. + * + * @since 3.1.4 + * @param array $offer Structured data for a single price offer. + * @param EDD_Download $download Download object. + */ + return apply_filters( 'edd_generate_download_structured_data_offer', $offer, $download ); + } + + /** + * Gets the download URL. + * + * @param EDD_Download $download + * @return string + */ + private function get_url( $download ) { + return get_permalink( $download->ID ); + } + + /** + * Gets the download image URL. + * + * @since 3.1.4 + * @param EDD_Download $download + * @return string + */ + private function get_image( $download ) { + if ( ! has_post_thumbnail( $download->ID ) ) { + return ''; + } + + $image_url = wp_get_attachment_image_url( get_post_thumbnail_id( $download->ID ) ); + if ( false !== $image_url ) { + return esc_url( $image_url ); + } + + return ''; + } +} diff --git a/src/Telemetry/Data.php b/src/Telemetry/Data.php new file mode 100644 index 00000000000..8cfd9c68ffe --- /dev/null +++ b/src/Telemetry/Data.php @@ -0,0 +1,72 @@ + $this->get_id(), + ); + + $classes = array( + 'environment' => new Environment(), + 'integrations' => new Integrations(), + 'licenses' => new Licenses(), + 'sales' => new Orders(), + 'refunds' => new Orders( 'refund' ), + 'settings' => new Settings(), + 'stats' => new Stats(), + ); + + foreach ( $classes as $key => $class ) { + $data[ $key ] = $class->get(); + } + + return $data; + } + + /** + * Gets the unique site ID. + * This is generated from the home URL and two random pieces of data + * to create a hashed site ID that anonymizes the site data. + * + * @since 3.1.1 + * @return string + */ + private function get_id() { + $this->id = get_option( 'edd_telemetry_uuid' ); + if ( $this->id ) { + return $this->id; + } + $home_url = get_home_url(); + $uuid = wp_generate_uuid4(); + $today = gmdate( 'now' ); + $this->id = md5( $home_url . $uuid . $today ); + + update_option( 'edd_telemetry_uuid', $this->id, false ); + + return $this->id; + } +} diff --git a/src/Telemetry/Environment.php b/src/Telemetry/Environment.php new file mode 100644 index 00000000000..f040511cb98 --- /dev/null +++ b/src/Telemetry/Environment.php @@ -0,0 +1,75 @@ + phpversion(), + 'wp_version' => $this->get_wp_version(), + 'edd_version' => EDD_VERSION, + 'edd_pro' => (int) (bool) edd_is_pro(), + 'locale' => get_locale(), + 'active_theme' => $this->get_active_theme(), + 'multisite' => (int) (bool) is_multisite(), + 'is_ssl' => (int) (bool) is_ssl(), + 'stripe_connect' => (int) (bool) edd_get_option( 'stripe_connect_account_id' ), + ); + $server = $this->parse_server(); + + return array_merge( $data, $server ); + } + + /** + * Adds the server data to the array of data. + * + * @since 3.1.1 + * @return array + */ + private function parse_server() { + $server = ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : 'unknown' ); + $server = explode( '/', $server ); + + $data = array( + 'server' => $server[0], + ); + if ( isset( $server[1] ) ) { + $data['server_version'] = $server[1]; + } + + return $data; + } + + /** + * Gets the WordPress version. + * + * @since 3.1.1 + * @return string + */ + private function get_wp_version() { + $version = get_bloginfo( 'version' ); + $version = explode( '-', $version ); + + return reset( $version ); + } + + /** + * Gets the active theme name. + * + * @since 3.1.1 + * @return string + */ + private function get_active_theme() { + $active_theme = wp_get_theme(); + + return $active_theme->name; + } +} diff --git a/src/Telemetry/Integrations.php b/src/Telemetry/Integrations.php new file mode 100644 index 00000000000..3a21bb6ff92 --- /dev/null +++ b/src/Telemetry/Integrations.php @@ -0,0 +1,85 @@ +get_all_plugins() as $basename => $details ) { + if ( ! $this->should_log_integration( $basename, $details ) ) { + continue; + } + $data[] = array( + 'name' => $details['Name'], + 'type' => $this->is_core_integration( $basename, $details ) ? 'core' : 'external', + 'version' => $details['Version'], + ); + } + + return $data; + } + + /** + * Gets all plugins on the site. + * + * @since 3.1.1 + * @return array + */ + private function get_all_plugins() { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + return get_plugins(); + } + + /** + * Whether the integration should be included in the data. + * + * @since 3.1.1 + * @param string $basename + * @param array $details + * @return bool + */ + private function should_log_integration( $basename, $details ) { + if ( ! is_plugin_active( $basename ) ) { + return false; + } + + return true; + } + + /** + * Whether the integration is an EDD or third party integration. + * + * @since 3.1.1 + * @param string $basename + * @param array $details + * @return bool + */ + private function is_core_integration( $basename, $details ) { + if ( 'Easy Digital Downloads' === $details['Author'] ) { + return true; + } + if ( in_array( untrailingslashit( $details['AuthorURI'] ), array( 'https://easydigitaldownloads.com', 'https://sandhillsdev.com' ), true ) ) { + return false !== strpos( $details['PluginURI'], 'easydigitaldownloads.com' ); + } + + return false; + } +} diff --git a/src/Telemetry/Licenses.php b/src/Telemetry/Licenses.php new file mode 100644 index 00000000000..1b999761a49 --- /dev/null +++ b/src/Telemetry/Licenses.php @@ -0,0 +1,88 @@ +get_extensions(); + $pro_license = $this->get_pro_license(); + if ( $pro_license ) { + $data[] = $pro_license; + } + + return $data; + } + + /** + * Gets the pro license status. + * + * @since 3.1.1 + * @return array + */ + private function get_pro_license() { + if ( ! edd_is_pro() ) { + return false; + } + $pro_license = new License( 'pro' ); + + return array( + 'extension' => 'edd_pro', + 'status' => $this->get_license_status( $pro_license ), + ); + } + + /** + * Gets licensed extensions' statuses. + * + * @since 3.1.1 + * @return array + */ + private function get_extensions() { + $data = array(); + $extensions = \EDD\Extensions\get_licensed_extension_slugs(); + foreach ( $extensions as $slug ) { + $shortname = str_replace( 'edd_', '', $slug ); + $license = new License( $shortname ); + $data[] = array( + 'extension' => $slug, + 'status' => $this->get_license_status( $license ), + ); + } + + return $data; + } + + /** + * Gets the license status. + * + * @since 3.1.2 + * @param \EDD\Licensing\License $license + * @return string + */ + private function get_license_status( $license ) { + if ( ! empty( $license->license ) ) { + return $license->license; + } + if ( ! empty( $license->error ) ) { + return $license->error; + } + + return 'missing'; + } +} diff --git a/src/Telemetry/Orders.php b/src/Telemetry/Orders.php new file mode 100644 index 00000000000..cd1aee9da1c --- /dev/null +++ b/src/Telemetry/Orders.php @@ -0,0 +1,166 @@ +type = $type; + } + + /** + * Gets the gateway data. + * + * @return array + */ + public function get() { + $data = array( + 'all_gateways' => $this->get_totals(), + ); + foreach ( $this->get_date_ranges() as $type => $start ) { + foreach ( $this->get_totals_by_gateway( $start ) as $gateway => $currency ) { + foreach ( $currency as $code => $amounts ) { + $data[ $gateway ][ $code ][ $type ] = $amounts; + } + } + } + + return $data; + } + + /** + * Gets the store order totals for all currencies and gateways. + * + * @since 3.1.1 + * @return array + */ + private function get_totals() { + $data = array(); + foreach ( $this->get_date_ranges() as $type => $start ) { + $results = $this->get_totals_by_date( $start ); + foreach ( $results as $result ) { + $data['all_currencies'][ $type ] = array( + 'count' => $result->sales, + 'total' => $result->earnings, + ); + } + } + + return $data; + } + + /** + * Gets the order count/total for a given date range. + * + * @since 3.1.1 + * @param string $start The start date (optional). + * @return array + */ + private function get_totals_by_gateway( $start = '' ) { + $data = array(); + foreach ( $this->get_results_by_date( $start ) as $total ) { + $gateway = $total->gateway ? $total->gateway : 'unknown'; + $currency = $total->currency ? $total->currency : 'unknown'; + $data[ $gateway ][ $currency ] = array( + 'count' => $total->sales, + 'total' => $total->earnings, + ); + } + + return $data; + } + + /** + * Gets order totals by date. + * + * @since 3.1.1 + * @param string $start + * @return array + */ + private function get_totals_by_date( $start = '' ) { + global $wpdb; + + return $wpdb->get_results( + "SELECT COUNT(*) as sales, SUM(total) as earnings + FROM {$wpdb->edd_orders} + WHERE type = '{$this->type}' + {$this->get_status_query()} + {$this->get_date_query( $start )} + LIMIT 0, 99999;" + ); + } + + /** + * Gets orders grouped by gateway and currency. + * + * @since 3.1.1 + * @param string $start + * @return array + */ + private function get_results_by_date( $start = '' ) { + global $wpdb; + + return $wpdb->get_results( + "SELECT gateway, currency, COUNT(*) as sales, SUM(total) as earnings + FROM {$wpdb->edd_orders} + WHERE type = '{$this->type}' + {$this->get_status_query()} + {$this->get_date_query( $start )} + GROUP BY gateway, currency + LIMIT 0, 99999;" + ); + } + + /** + * Gets the status query string. + * + * @since 3.1.1 + * @return string + */ + private function get_status_query() { + return "AND status IN ('" . implode( "', '", edd_get_gross_order_statuses() ) . "')"; + } + + /** + * Gets the date query string. + * + * @since 3.1.1 + * @param string $start + * @return string + */ + private function get_date_query( $start = '' ) { + return $start ? sprintf( + "AND ( date_completed >= '%s' AND date_completed <= '%s' )", + gmdate( 'Y-m-d 00:00:00', strtotime( $start ) ), + gmdate( 'Y-m-d 00:00:00', strtotime( 'today' ) ) + ) : ''; + } + + /** + * Gets the date ranges for each query. + * + * @since 3.1.1 + * @return array + */ + private function get_date_ranges() { + return array( + 'lifetime' => '', + 'week' => '-1 week', + 'month' => '-30 days', + ); + } +} diff --git a/src/Telemetry/Settings.php b/src/Telemetry/Settings.php new file mode 100644 index 00000000000..67092f40aee --- /dev/null +++ b/src/Telemetry/Settings.php @@ -0,0 +1,229 @@ + $tab_contents ) { + $tab_sections = edd_get_settings_tab_sections( $tab_key ); + foreach ( $tab_sections as $section_key => $section_title ) { + $section_setting_types = edd_get_registered_settings_types( $tab_key, $section_key ); + if ( ! empty( $settings[ $tab_key ] ) && ! empty( $settings[ $tab_key ][ $section_key ] ) ) { + $section_settings = $settings[ $tab_key ][ $section_key ]; + foreach ( $section_settings as $setting_key => $setting ) { + $value = $this->get_setting_value( $tab_key, $section_key, $setting_key ); + // If the value is null, it's a skipped setting. + if ( is_null( $value ) ) { + continue; + } + $setting_id = isset( $setting['id'] ) ? $setting['id'] : sanitize_title( $setting['name'] ); + if ( is_array( $value ) ) { + foreach ( $value as $v ) { + $data[ "{$setting_id}_{$v}" ] = 1; + } + } else { + $data[ $setting_id ] = $value; + } + } + } + } + } + + return $data; + } + + /** + * Gets the id and value for an individual setting. + * + * @param string $tab_key + * @param string $section_key + * @param string $setting_key + * @return mixed + */ + private function get_setting_value( $tab_key, $section_key, $setting_key ) { + $setting = edd_get_registered_setting_details( $tab_key, $section_key, $setting_key ); + if ( ! $this->can_include_setting( $setting ) ) { + return null; + } + + $default = isset( $setting['std'] ) ? $setting['std'] : ''; + $value = edd_get_option( $setting['id'], $default ); + if ( in_array( $setting['type'], array( 'checkbox', 'checkbox_description' ), true ) ) { + return (int) (bool) $value; + } + if ( empty( $value ) && 'currency' === $setting['id'] ) { + return edd_get_currency(); + } + if ( in_array( $setting['type'], $this->text_settings(), true ) ) { + return $this->anonymize_setting( $value ); + } + if ( $this->should_populate_array( $setting ) ) { + return $this->update_setting_value_array( $value, $setting ); + } + + return $value; + } + + /** + * Evaluates whether a setting can be included in the telemetry data. + * + * @since 3.1.1 + * @param array $setting + * @return bool + */ + private function can_include_setting( $setting ) { + + if ( empty( $setting ) ) { + return false; + } + + // If the setting is marked readonly then it's not really a setting. + if ( ! empty( $setting['args']['readonly'] ) ) { + return false; + } + + // Certain types of settings should always be skipped. + if ( in_array( $setting['type'], $this->skipped_settings_types(), true ) ) { + return false; + } + + // Settings known to be PII are excluded. + if ( in_array( $setting['id'], $this->sensitive_settings(), true ) ) { + return false; + } + + // Text settings are always excluded unless specifically included. + if ( in_array( $setting['type'], $this->text_settings(), true ) && ! in_array( $setting['id'], $this->allowed_text_settings(), true ) ) { + return false; + } + + return true; + } + + /** + * These settings types are either not settings or nearly always full of sensitive data/PII. + * + * @since 3.1.1 + * @return array + */ + private function skipped_settings_types() { + + return array_merge( + edd_get_non_setting_types(), + array( + 'rich_editor', + 'upload', + 'color', + 'recapture', + ) + ); + } + + /** + * These settings are known to be sensitive/PII and are not otherwise excluded. + * + * @since 3.1.1 + * @return array + */ + private function sensitive_settings() { + return array( + 'base_state', + 'paypal_live_client_id', + 'paypal_live_client_secret', + 'paypal_sandbox_client_id', + 'paypal_sandbox_client_secret', + ); + } + + /** + * We assume that any text field should be excluded unless it's in this array. + * + * @since 3.1.1 + * @return array + */ + private function allowed_text_settings() { + return array(); + } + + /** + * Settings types which will be strings and which should be evaluated for PII. + * + * @since 3.1.1 + * @return array + */ + private function text_settings() { + return array( + 'text', + 'textarea', + 'email', + ); + } + + /** + * Whether an array of settings should be populated, due to the setting type. + * + * @since 3.1.1 + * @param array $setting + * @return bool + */ + private function should_populate_array( $setting ) { + $settings = array( 'gateways', 'accepted_cards' ); + + return 'multicheck' === $setting['type'] || in_array( $setting['id'], $settings, true ); + } + + /** + * Attempts to anonymize a setting value. + * @todo check how we want to replace--values or empty strings? + * + * @since 3.1.1 + * @param string $value + * @return string + */ + private function anonymize_setting( $value ) { + $admin_email = get_bloginfo( 'admin_email' ); + $value = str_replace( $admin_email, 'email@website.dev', $value ); + $site_name = get_bloginfo( 'name' ); + $value = str_replace( $site_name, 'Site Name', $value ); + $home_url = get_home_url(); + $value = str_replace( $home_url, 'website.dev', $value ); + + return $value; + } + + /** + * Updates the an array setting value to include all options. + * + * @since 3.1.1 + * @param mixed $saved_value The actual saved value (can be empty). + * @param array $setting The setting definition. + * @return array + */ + private function update_setting_value_array( $saved_value, $setting ) { + $value = array(); + foreach ( $setting['options'] as $key => $label ) { + if ( is_array( $saved_value ) && ! empty( $saved_value[ $key ] ) ) { + $value[] = $key; + } + } + + return $value; + } +} diff --git a/src/Telemetry/Stats.php b/src/Telemetry/Stats.php new file mode 100644 index 00000000000..377ce395d01 --- /dev/null +++ b/src/Telemetry/Stats.php @@ -0,0 +1,126 @@ + $this->convert_timestamp( edd_get_activation_date() ), + 'pro_activated' => $this->convert_timestamp( get_option( 'edd_pro_activation_date' ) ), + 'first_order' => $this->get_first_order_date(), + 'onboarding_started' => get_option( 'edd_onboarding_started' ), + 'onboarding_completed' => get_option( 'edd_onboarding_completed' ), + 'products' => $this->get_product_count(), + 'pass_id' => $this->get_pass_id(), + ); + } + + /** + * Gets the date of the first completed order. + * + * @since 3.1.1 + * @return string + */ + private function get_first_order_date() { + $orders = edd_get_orders( + array( + 'mode' => 'live', + 'status__in' => edd_get_complete_order_statuses(), + 'number' => 1, + 'fields' => 'date_completed', + 'orderby' => 'id', + 'order' => 'ASC', + ), + ); + + return ! empty( $orders ) ? reset( $orders ) : ''; + } + + /** + * Converts a timestamp value to a date string for consistent dates. + * + * @since 3.1.1 + * @param string $timestamp + * @return string + */ + private function convert_timestamp( $timestamp = '' ) { + return $timestamp ? gmdate( 'Y-m-d H:i:s', $timestamp ) : ''; + } + + /** + * Gets the site pass ID. + * + * @since 3.1.1 + * @return int|string + */ + private function get_pass_id() { + $pass_manager = new Pass_Manager(); + + return $pass_manager->highest_pass_id; + } + + /** + * Gets the number of published products on the website. + * + * @since 3.1.1 + * @return int + */ + private function get_product_count() { + if ( $this->product_count ) { + return $this->product_count; + } + $query = new \WP_Query( + array( + 'post_type' => 'download', + 'status' => 'publish', + 'nopaging' => true, + ) + ); + $this->product_count = $query->found_posts; + + return $this->product_count; + } + + /** + * Gets the average total earnings per product. + * + * @since 3.1.1 + * @return float + */ + private function get_average_per_product() { + global $wpdb; + + $results = $wpdb->get_results( + "SELECT SUM(total) as earnings + FROM {$wpdb->edd_orders} + WHERE type = 'sale' + AND status IN ('" . implode( "', '", edd_get_gross_order_statuses() ) . "') + LIMIT 0, 99999;" + ); + if ( empty( $results ) ) { + return 0; + } + $results = reset( $results ); + $products = $this->get_product_count(); + + return $results->earnings / $products; + } +} diff --git a/src/Utils/Countries.php b/src/Utils/Countries.php new file mode 100644 index 00000000000..9af0df1c312 --- /dev/null +++ b/src/Utils/Countries.php @@ -0,0 +1,54 @@ +states ) { + $this->states = include EDD_PLUGIN_DIR . 'i18n/states.php'; + } + + if ( $country_code ) { + return array_key_exists( $country_code, $this->states ) ? $this->states[ $country_code ] : array(); + } + + return $this->states; + } + + /** + * Given a country and state code, return the state name. + * + * @since 3.1.4 + * @param string $country_code The ISO Code for the country + * @param string $state_code The ISO Code for the state + * + * @return string + */ + public function get_state_name( $country_code = '', $state_code = '' ) { + if ( empty( $state_code ) ) { + return $state_code; + } + $states = $this->get_states( $country_code ); + + return array_key_exists( $state_code, $states ) ? $states[ $state_code ] : $state_code; + } +} diff --git a/src/Utils/EnvironmentChecker.php b/src/Utils/EnvironmentChecker.php new file mode 100644 index 00000000000..7248c8727e1 --- /dev/null +++ b/src/Utils/EnvironmentChecker.php @@ -0,0 +1,227 @@ + 'isFree', + 'ala-carte' => 'hasIndividualLicense', + 'pass-personal' => 'hasPersonalPass', + 'pass-extended' => 'hasExtendedPass', + 'pass-professional' => 'hasProfessionalPass', + 'pass-all-access' => 'hasAllAccessPass', + 'pass-any' => 'has_pass', + ); + + /** + * Constructor. + */ + public function __construct() { + $this->passManager = new Pass_Manager(); + } + + /** + * Checks to see if this environment meets the specified condition. + * + * @since 2.11.4 + * + * @param string $condition Condition to check. Can either be a type of license/pass or a version number. + * + * @return bool + * @throws \InvalidArgumentException + */ + public function meetsCondition( $condition ) { + if ( array_key_exists( $condition, $this->validLicenseConditions ) ) { + return $this->hasLicenseType( $condition ); + } elseif ( $this->isPaymentGateway( $condition ) ) { + return $this->paymentGatewayMatch( array_keys( edd_get_enabled_payment_gateways() ), $condition ); + } elseif ( $this->isVersionNumber( $condition ) ) { + return $this->versionNumbersMatch( EDD_VERSION, $condition ); + } + + throw new \InvalidArgumentException( 'Invalid condition. Must either be a type of license or a version number.' ); + } + + /** + * Checks to see if this environment meets all the specified conditions. If any one condition + * is not met then this returns false. + * + * @since 2.11.4 + * + * @param array $conditions + * + * @return bool + */ + public function meetsConditions( $conditions ) { + foreach ( $conditions as $condition ) { + if ( ! $this->meetsCondition( $condition ) ) { + return false; + } + } + + return true; + } + + /** + * Determines if the site has the specified pass condition. + * + * @see EnvironmentChecker::$validLicenseConditions + * + * @since 2.11.4 + * + * @param string $passLevel License type that we're checking to see if the system has. + * + * @return bool + * @throws \InvalidArgumentException + */ + protected function hasLicenseType( $passLevel ) { + $method = isset( $this->validLicenseConditions[ $passLevel ] ) + ? $this->validLicenseConditions[ $passLevel ] + : false; + + if ( ! $method || ! method_exists( $this->passManager, $method ) ) { + throw new \InvalidArgumentException( sprintf( 'Method %s not found in Pass_Manager.', $method ) ); + } + + return call_user_func( array( $this->passManager, $method ) ); + } + + /** + * Determines if the provided condition is a payment gateway. + * + * @since 2.11.4 + * + * @param string $condition + * + * @return bool + */ + protected function isPaymentGateway( $condition ) { + return 'gateway-' === substr( $condition, 0, 8 ); + } + + /** + * Determines if the supplied gateway condition is applicable to this site. + * Will return `true` if the condition is the slug of a payment gateway (potentially with a `gateway-` prefix) + * that's enabled on this site. + * + * @since 2.11.4 + * + * @param array $enabledGateways Gateways that are enabled on this site. + * @param string $condition Gateway we're checking to see if it's enabled. + * + * @return bool True if the gateway is enabled, false if not. + */ + public function paymentGatewayMatch( $enabledGateways, $condition ) { + $gatewayToCheck = str_replace( 'gateway-', '', $condition ); + + return in_array( $gatewayToCheck, $enabledGateways, true ); + } + + /** + * Determines if the provided condition is a version number. + * + * @since 2.11.4 + * + * @param string $condition + * + * @return bool + */ + protected function isVersionNumber( $condition ) { + // First character should always be numeric. + if ( ! is_numeric( substr( $condition, 0, 1 ) ) ) { + return false; + } + + // Must contain at least one `.` or `-`. + return false !== strpos( $condition, '.' ) || false !== strpos( $condition, '-' ); + } + + /** + * Determines if two version numbers match, or if the `$currentVersion` falls within the wildcard + * range specified by `$compareVersion`. + * + * @since 2.11.4 + * + * @param string $currentVersion Version number currently in use. This must be a full, exact version number. + * @param string $compareVersion Version to compare with. This can either be an exact version number or a + * wildcard (e.g. `2.11.3` or `2.x`). Hyphens are also accepted in lieu of + * full stops (e.g. `2-11-3` or `2-x`). + * + * @return bool + * @throws \InvalidArgumentException + */ + public function versionNumbersMatch( $currentVersion, $compareVersion ) { + $currentVersionPieces = explode( '.', $currentVersion ); + + if ( false !== strpos( $compareVersion, '.' ) ) { + $compareVersionPieces = explode( '.', $compareVersion ); + } else if ( false !== strpos( $compareVersion, '-' ) ) { + $compareVersionPieces = explode( '-', $compareVersion ); + } else { + throw new \InvalidArgumentException( sprintf( + 'Invalid version number: %s', + $compareVersion + ) ); + } + + $numberCurrentVersionParts = count( $currentVersionPieces ); + $numberCompareVersionParts = count( $compareVersionPieces ); + + /* + * Normalize the two parts so that they have the same lengths and + * wildcards (`x`) are removed. + */ + for ( $i = 0; $i < $numberCurrentVersionParts || $i < $numberCompareVersionParts; $i ++ ) { + if ( isset( $compareVersionPieces[ $i ] ) && 'x' === strtolower( $compareVersionPieces[ $i ] ) ) { + unset( $compareVersionPieces[ $i ] ); + } + + if ( ! isset( $currentVersionPieces[ $i ] ) ) { + unset( $compareVersionPieces[ $i ] ); + } elseif ( ! isset( $compareVersionPieces[ $i ] ) ) { + unset( $currentVersionPieces[ $i ] ); + } + } + + // Now make sure all the numbers match. + foreach ( $compareVersionPieces as $index => $versionPiece ) { + if ( ! isset( $currentVersionPieces[ $index ] ) || $currentVersionPieces[ $index ] !== $versionPiece ) { + return false; + } + } + + return true; + } + +} diff --git a/src/Utils/ListHandler.php b/src/Utils/ListHandler.php new file mode 100644 index 00000000000..9cf992191ae --- /dev/null +++ b/src/Utils/ListHandler.php @@ -0,0 +1,136 @@ +array = $array; + } + + /** + * Gets the key of the array with the searched value. + * + * @since 3.1.2 + * @param mixed $search The value to search for. + * @param string $type The type of search to perform. Currently supports 'min' or 'max'. + * @return int|string|false The key of the array with the searched value. + */ + public function search( $search, $type = 'min' ) { + if ( empty( $this->array ) || ! is_array( $this->array ) ) { + return false; + } + + if ( $this->is_array_associative() ) { + return array_search( $search, $this->array ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict + } + + if ( in_array( $type, array( 'min', 'max' ), true ) ) { + $plucked = $this->pluck( $search ); + if ( empty( $plucked ) ) { + return false; + } + $search = call_user_func( $type, $plucked ); + } + + foreach ( $this->array as $key => $value ) { + $result = array_search( $search, $value ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict + if ( $result ) { + return $key; + } + } + + return false; + } + + /** + * Checks if the array is associative. + * + * @since 3.1.2 + * @return bool + */ + private function is_array_associative() { + return count( $this->array ) === count( $this->array, COUNT_RECURSIVE ); + } + + /** + * Plucks a certain field out of each element in the input array. + * + * This has the same functionality and prototype of + * array_column() (PHP 5.5) but also supports objects. + * + * This is a near copy of the pluck() method from the WP_List_Util class, but + * that errors if a nonexistent field is passed. This version does not. + * + * @since 3.1.2 + * @param int|string $field Field to fetch from the object or array. + * @param int|string $index_key Optional. Field from the element to use as keys for the new array. + * @return array Array of found values. If `$index_key` is set, an array of found values with keys + * corresponding to `$index_key`. If `$index_key` is null, array keys from the original + * `$list` will be preserved in the results. + */ + private function pluck( $field, $index_key = null ) { + $newlist = array(); + + if ( ! is_string( $field ) && ! is_int( $field ) ) { + return $newlist; + } + + if ( ! $index_key ) { + /* + * This is simple. Could at some point wrap array_column() + * if we knew we had an array of arrays. + */ + foreach ( $this->array as $key => $value ) { + if ( is_object( $value ) ) { + $newlist[ $key ] = $value->$field; + } elseif ( is_array( $value ) && isset( $value[ $field ] ) ) { + $newlist[ $key ] = $value[ $field ]; + } + } + + return $newlist; + } + + /* + * When index_key is not set for a particular item, push the value + * to the end of the stack. This is how array_column() behaves. + */ + foreach ( $this->array as $value ) { + if ( is_object( $value ) ) { + if ( isset( $value->$index_key ) ) { + $newlist[ $value->$index_key ] = $value->$field; + } else { + $newlist[] = $value->$field; + } + } elseif ( is_array( $value ) && isset( $value[ $field ] ) ) { + if ( isset( $value[ $index_key ] ) ) { + $newlist[ $value[ $index_key ] ] = $value[ $field ]; + } else { + $newlist[] = $value[ $field ]; + } + } + } + + return $newlist; + } +} diff --git a/src/Utils/NotificationImporter.php b/src/Utils/NotificationImporter.php new file mode 100644 index 00000000000..f492b43bbe3 --- /dev/null +++ b/src/Utils/NotificationImporter.php @@ -0,0 +1,251 @@ +environmentChecker = new EnvironmentChecker(); + } + + /** + * Fetches notifications from the API and imports them locally. + * + * @since 2.11.4 + */ + public function run() { + $request_timeout = get_option( 'edd_notification_req_timeout', false ); + if ( false !== $request_timeout && current_time( 'timestamp' ) < $request_timeout ) { + edd_debug_log( 'Skipping notifications API request, timeout not reached' ); + return; + } + + edd_debug_log( 'Fetching notifications via ' . $this->getApiEndpoint() ); + + try { + $notifications = $this->fetchNotifications(); + + // If successful, make it so we don't request for another 23 hours. + update_option( 'edd_notification_req_timeout', current_time( 'timestamp' ) + ( HOUR_IN_SECONDS * 23 ), false ); + } catch ( \Exception $e ) { + edd_debug_log( sprintf( 'Notification fetch exception: %s', $e->getMessage() ) ); + + // If for some reason our request failed, delay for 4 hours. + update_option( 'edd_notification_req_timeout', current_time( 'timestamp' ) + ( HOUR_IN_SECONDS * 4 ), false ); + return; + } + + foreach ( $notifications as $notification ) { + $notificationId = isset( $notification->id ) ? $notification->id : 'unknown'; + + edd_debug_log( sprintf( 'Processing notification ID %s', $notificationId ) ); + + try { + $this->validateNotification( $notification ); + + $existingId = $this->get_column_by( 'id', 'remote_id', $notification->id ); + if ( $existingId ) { + edd_debug_log( '-- Updating existing notification.' ); + + $this->updateExistingNotification( $existingId, $notification ); + } else { + edd_debug_log( '-- Inserting new notification.' ); + + $this->insertNewNotification( $notification ); + } + } catch ( \Exception $e ) { + edd_debug_log( sprintf( '-- Notification processing failure for ID %s: %s', $notificationId, $e->getMessage() ) ); + } + } + } + + /** + * Returns the API endpoint to query. + * + * @since 2.11.4 + * + * @return string + */ + protected function getApiEndpoint() { + if ( defined( 'EDD_NOTIFICATIONS_API_URL' ) ) { + return EDD_NOTIFICATIONS_API_URL; + } + + return 'https://plugin.easydigitaldownloads.com/wp-content/notifications.json'; + } + + /** + * Retrieves notifications from the remote API endpoint. + * + * @since 2.11.4 + * + * @return array + * @throws \Exception + */ + public function fetchNotifications() { + $response = wp_remote_get( $this->getApiEndpoint() ); + + if ( is_wp_error( $response ) ) { + throw new \Exception( $response->get_error_message() ); + } + + $notifications = wp_remote_retrieve_body( $response ); + + return ! empty( $notifications ) ? json_decode( $notifications ) : array(); + } + + /** + * Validates the notification from the remote API to make sure we actually + * want to save it. + * + * @since 2.11.4 + * + * @param object $notification + * + * @throws \Exception + */ + public function validateNotification( $notification ) { + // Make sure we have all the required data. + $requiredProperties = array( + 'id', + 'title', + 'content', + ); + + $missing = array_diff( $requiredProperties, array_keys( get_object_vars( $notification ) ) ); + if ( $missing ) { + throw new \Exception( sprintf( 'Missing required properties: %s', json_encode( array_values( $missing ) ) ) ); + } + + // Don't save the notification if it has expired. + if ( ! empty( $notification->end ) && time() > strtotime( $notification->end ) ) { + throw new \Exception( 'Notification has expired.' ); + } + + // Ignore if notification was created before EDD was installed. + if ( ! empty( $notification->start ) && edd_get_activation_date() > strtotime( $notification->start ) ) { + throw new \Exception( 'Notification created prior to EDD activation.' ); + } + + if ( + ! empty( $notification->type ) && + is_array( $notification->type ) && + ! $this->environmentChecker->meetsConditions( $notification->type ) + ) { + throw new \Exception( 'Condition(s) not met.' ); + } + } + + /** + * Retrieves the array of notification data to insert into the database. + * Use in both inserts and updates. + * + * @since 2.11.4 + * + * @param object $notification + * + * @return array + */ + protected function getNotificationData( $notification ) { + return array( + 'remote_id' => $notification->id, + 'title' => $notification->title, + 'content' => $notification->content, + 'buttons' => $this->parseButtons( $notification ), + 'type' => ! empty( $notification->notification_type ) ? $notification->notification_type : 'success', + 'conditions' => ! empty( $notification->type ) ? $notification->type : null, + 'start' => ! empty( $notification->start ) ? $notification->start : null, + 'end' => ! empty( $notification->end ) ? $notification->end : null, + ); + } + + /** + * Parses and formats buttons from the remote notification object. + * + * @since 2.11.4 + * + * @param object $notification + * + * @return array|null + */ + protected function parseButtons( $notification ) { + if ( empty( $notification->btns ) ) { + return null; + } + + $buttons = array(); + + foreach ( (array) $notification->btns as $buttonType => $buttonInfo ) { + if ( empty( $buttonInfo->url ) || empty( $buttonInfo->text ) ) { + continue; + } + + $buttons[] = array( + 'type' => ( 'main' === $buttonType ) ? 'primary' : 'secondary', + 'url' => $buttonInfo->url, + 'text' => $buttonInfo->text, + ); + } + + return ! empty( $buttons ) ? $buttons : null; + } + + /** + * Inserts a new notification into the database. + * + * @since 2.11.4 + * + * @param object $notification + * @throws \Exception + */ + protected function insertNewNotification( $notification ) { + $result = EDD()->notifications->insert( $this->getNotificationData( $notification ) ); + + if ( ! $result ) { + throw new \Exception( 'Failed to insert into database.' ); + } + } + + /** + * Updates an existing notification. + * + * @since 2.11.4 + * + * @param int $existingId + * @param object $notification + */ + protected function updateExistingNotification( $existingId, $notification ) { + EDD()->notifications->update( $existingId, wp_parse_args( $this->getNotificationData( $notification ), array( + 'date_updated' => gmdate( 'Y-m-d H:i:s' ), + ) ) ); + } + + /** + * Retrieve a specific column's value by the the specified column / value + * + * @since 3.1.1 + * @return string + */ + private function get_column_by( $column, $column_where, $column_value ) { + global $wpdb; + $column_where = esc_sql( $column_where ); + $column = esc_sql( $column ); + return $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM {$wpdb->edd_notifications} WHERE $column_where = %s LIMIT 1;", $column_value ) ); + } +} diff --git a/src/Utils/StatusBadge.php b/src/Utils/StatusBadge.php new file mode 100644 index 00000000000..3085b90a19b --- /dev/null +++ b/src/Utils/StatusBadge.php @@ -0,0 +1,128 @@ +args = wp_parse_args( + $args, + array( + 'status' => 'default', + 'label' => '', + 'icon' => '', + 'color' => '', + 'dashicon' => true, + 'class' => '', + ) + ); + } + + /** + * Gets the badge. + * + * @since 3.1.4 + * @param string $icon Optional. Icon markup to use. Default is empty. + * @return string + */ + public function get( $icon = false ) { + if ( empty( $this->args['label'] ) ) { + return ''; + } + + return sprintf( + '%s%s', + $this->get_class_string( $this->get_classes() ), + esc_html( $this->args['label'] ), + ! empty( $icon ) ? $icon : $this->get_icon() + ); + } + + /** + * Gets the icon HTML markup. + * + * @since 3.1.4 + * @return string + */ + public function get_icon() { + if ( empty( $this->args['icon'] ) ) { + return ''; + } + + $classes = array( + 'edd-status-badge__icon', + ); + if ( ! empty( $this->args['dashicon'] ) ) { + $classes[] = 'dashicons'; + $classes[] = "dashicons-{$this->args['icon']}"; + } else { + $classes[] = $this->args['icon']; + } + + return sprintf( + '', + esc_attr( $this->get_class_string( $classes ) ) + ); + } + + /** + * Gets the classes for the badge. + * + * @since 3.1.4 + * @return array + */ + private function get_classes() { + return array( + 'edd-status-badge', + "edd-status-badge--{$this->args['status']}", + $this->args['class'], + $this->get_color_class(), + ); + } + + /** + * Gets a class string from an array of classes. + * + * @since 3.1.4 + * @param array $classes + * @return string + */ + private function get_class_string( array $classes ) { + return implode( ' ', array_filter( array_map( 'sanitize_html_class', $classes ) ) ); + } + + /** + * Gets the color class. + * + * @since 3.1.4 + * @return string + */ + private function get_color_class() { + if ( ! empty( $this->args['color'] ) && false === strpos( $this->args['color'], '#' ) ) { + return "edd-status-badge--{$this->args['color']}"; + } + + return ''; + } +} diff --git a/templates/account-pending.php b/templates/account-pending.php new file mode 100644 index 00000000000..2a27f8975de --- /dev/null +++ b/templates/account-pending.php @@ -0,0 +1,9 @@ + + + + diff --git a/templates/checkout_cart.php b/templates/checkout_cart.php new file mode 100755 index 00000000000..8c2dfb9f6f9 --- /dev/null +++ b/templates/checkout_cart.php @@ -0,0 +1,133 @@ + +> + + + + + + + + + + + + + + $item ) : ?> + + + + + + + + + + + + + $fee ) : ?> + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + > + + + + + + + > + + + + + + + + + + + + + +
    + '; + echo get_the_post_thumbnail( $item['id'], apply_filters( 'edd_checkout_image_size', array( 25,25 ) ) ); + echo ''; + } + $item_title = edd_get_cart_item_name( $item ); + echo '' . esc_html( $item_title ) . ''; + + /** + * Runs after the item in cart's title is echoed + * @since 2.6 + * + * @param array $item Cart Item + * @param int $key Cart key + */ + do_action( 'edd_checkout_cart_item_title_after', $item, $key ); + ?> + + + + + + + + + + +
    + + + + +
    diff --git a/templates/emails/body-default.php b/templates/emails/body-default.php new file mode 100644 index 00000000000..5e6d4600f96 --- /dev/null +++ b/templates/emails/body-default.php @@ -0,0 +1,16 @@ + Settings > Emails + +?> +{email} diff --git a/templates/emails/body.php b/templates/emails/body.php new file mode 100644 index 00000000000..5e6d4600f96 --- /dev/null +++ b/templates/emails/body.php @@ -0,0 +1,16 @@ + Settings > Emails + +?> +{email} diff --git a/templates/emails/footer-default.php b/templates/emails/footer-default.php new file mode 100644 index 00000000000..7870629f600 --- /dev/null +++ b/templates/emails/footer-default.php @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/emails/footer.php b/templates/emails/footer.php new file mode 100644 index 00000000000..259cad36c2d --- /dev/null +++ b/templates/emails/footer.php @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/templates/emails/header-default.php b/templates/emails/header-default.php new file mode 100644 index 00000000000..f5717182fc5 --- /dev/null +++ b/templates/emails/header-default.php @@ -0,0 +1,108 @@ +emails->get_heading(); +?> + + + + + <?php echo get_bloginfo( 'name' ); ?> + + +
    + + +
    + +
    + ' . get_bloginfo( 'name' ) . '

    '; ?> +
    + + + + + + + + +
    + + + + + +
    +

    +
    + +
    + + + +
    + + + +
    +
    \ No newline at end of file diff --git a/templates/emails/header.php b/templates/emails/header.php new file mode 100644 index 00000000000..b37ef44e0db --- /dev/null +++ b/templates/emails/header.php @@ -0,0 +1,20 @@ + + + + + + <?php echo get_bloginfo( 'name' ); ?> + + \ No newline at end of file diff --git a/templates/fonts/check.svg b/templates/fonts/check.svg new file mode 100644 index 00000000000..df1c773e2a8 --- /dev/null +++ b/templates/fonts/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/fonts/lock.svg b/templates/fonts/lock.svg new file mode 100644 index 00000000000..ff53931e49f --- /dev/null +++ b/templates/fonts/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/history-downloads.php b/templates/history-downloads.php new file mode 100755 index 00000000000..d75e1f64f2a --- /dev/null +++ b/templates/history-downloads.php @@ -0,0 +1,136 @@ + + + $customer->id, + 'number' => 20, + 'offset' => 20 * ( intval( $page ) - 1 ), + 'type' => 'sale', + 'status__not_in' => array( 'trash', 'refunded', 'abandoned' ), + ) + ); +} else { + $orders = array(); +} + +if ( $orders ) : + do_action( 'edd_before_download_history' ); ?> + + + + + + + + + + + + get_items_with_bundles() as $key => $item ) : + ?> + + + product_name; + $price_id = $item->price_id; + $download_files = edd_get_download_files( $item->product_id, $price_id ); + + do_action( 'edd_download_history_row_start', $order->id, $item->product_id ); + ?> + + + + + id, $item->product_id ); + ?> + + +
    + is_deliverable() ) : + + if ( $download_files ) : + + foreach ( $download_files as $filekey => $file ) : + + $download_url = edd_get_download_file_url( $order, $order->email, $filekey, $item->product_id, $price_id ); + ?> + +
    + + + +
    + + + + status ) ) + ); + ?> + + +
    + id ) ) { + $count = edd_count_orders( + array( + 'customer_id' => $customer->id, + 'type' => 'sale', + 'status__not_in' => array( 'trash', 'refunded', 'abandoned' ), + ) + ); + echo edd_pagination( + array( + 'type' => 'download_history', + 'total' => ceil( $count / 20 ), // 20 items per page + ) + ); + } + ?> + + +

    + diff --git a/templates/history-purchases.php b/templates/history-purchases.php new file mode 100755 index 00000000000..bee2b8ce688 --- /dev/null +++ b/templates/history-purchases.php @@ -0,0 +1,104 @@ + + + $user_id, + 'number' => 20, + 'offset' => 20 * ( intval( $page ) - 1 ), + 'type' => 'sale', + 'status__not_in' => array( 'trash' ), + ) +); + +/** + * Fires before the order history, whether or not orders have been found. + * + * @since 3.0 + * @param array $orders The array of order objects for the current user. + * @param int $user_id The current user ID. + */ +do_action( 'edd_pre_order_history', $orders, $user_id ); + +if ( $orders ) : + do_action( 'edd_before_order_history', $orders ); + ?> + + + + + + + + + + + + + + + + + + + + + +
    #get_number() ); ?>utils->date( $order->date_created, null, true )->toDateTimeString() ) ); ?> + total, $order->currency ) ); ?> + + status, array( 'complete', 'partially_refunded' ), true ) ) : ?> + status ) ); ?> + get_recovery_url(); + if ( $recovery_url ) : + ?> + — + + + + +
    + get_current_user_id(), + 'type' => 'sale', + ) + ); + echo edd_pagination( + array( + 'type' => 'purchase_history', + 'total' => ceil( $count / 20 ), // 20 items per page + ) + ); + do_action( 'edd_after_order_history', $orders ); + ?> + +

    + +

    + here.', 'easy-digital-downloads' ) ), esc_url( edd_get_success_page_uri() ) ); + ?> +

    + + +
    diff --git a/templates/shortcode-content-cart-button.php b/templates/shortcode-content-cart-button.php new file mode 100755 index 00000000000..f7abafe6597 --- /dev/null +++ b/templates/shortcode-content-cart-button.php @@ -0,0 +1,3 @@ +
    + get_the_ID() ) ); ?> +
    diff --git a/templates/shortcode-content-excerpt.php b/templates/shortcode-content-excerpt.php new file mode 100755 index 00000000000..04d15c37e72 --- /dev/null +++ b/templates/shortcode-content-excerpt.php @@ -0,0 +1,17 @@ + + +
    + +
    diff --git a/templates/shortcode-content-full.php b/templates/shortcode-content-full.php new file mode 100755 index 00000000000..5e62ce9721b --- /dev/null +++ b/templates/shortcode-content-full.php @@ -0,0 +1,17 @@ + + +
    + +
    diff --git a/templates/shortcode-content-image.php b/templates/shortcode-content-image.php new file mode 100755 index 00000000000..e2fb9e95474 --- /dev/null +++ b/templates/shortcode-content-image.php @@ -0,0 +1,7 @@ + +
    + + + +
    + diff --git a/templates/shortcode-content-price.php b/templates/shortcode-content-price.php new file mode 100755 index 00000000000..9ae1c0c12d8 --- /dev/null +++ b/templates/shortcode-content-price.php @@ -0,0 +1,7 @@ + +
    +
    + +
    +
    + diff --git a/templates/shortcode-content-title.php b/templates/shortcode-content-title.php new file mode 100755 index 00000000000..0086973f4b4 --- /dev/null +++ b/templates/shortcode-content-title.php @@ -0,0 +1 @@ +

    diff --git a/templates/shortcode-download.php b/templates/shortcode-download.php new file mode 100644 index 00000000000..9a17509780f --- /dev/null +++ b/templates/shortcode-download.php @@ -0,0 +1,54 @@ + + +
    + +
    + + + +
    + +
    diff --git a/templates/shortcode-login.php b/templates/shortcode-login.php new file mode 100644 index 00000000000..524fe662603 --- /dev/null +++ b/templates/shortcode-login.php @@ -0,0 +1,43 @@ + +
    +
    + + + + + + +

    + + + +

    + +
    +
    + + + + + diff --git a/templates/shortcode-profile-editor.php b/templates/shortcode-profile-editor.php new file mode 100755 index 00000000000..7d26d5b5627 --- /dev/null +++ b/templates/shortcode-profile-editor.php @@ -0,0 +1,240 @@ +display_name; + $address = edd_get_customer_address( $user_id ); + $states = edd_get_shop_states( $address['country'] ); + $state = $address['state']; + + if ( edd_is_cart_saved() ): ?> + 'restore_cart', 'edd_cart_token' => urlencode( edd_get_cart_token() ) ), edd_get_checkout_uri() ); ?> +
    + : + click here to restore it.', 'easy-digital-downloads' ) ), esc_url( $restore_url ) ); + ?> +
    + + + +
    :
    + + + + + + +
    + + + +
    + + + +

    + + +

    + +

    + + +

    + +

    + + + +

    + + + +

    + + + id > 0 ) : ?> + + emails ) ) : ?> + + + emails = array_reverse( $customer->emails, true ); + + foreach ( $customer->emails as $email ) { + $emails[ $email ] = $email; + } + + $email_select_args = array( + 'options' => $emails, + 'name' => 'edd_email', + 'id' => 'edd_email', + 'selected' => $customer->email, + 'show_option_none' => false, + 'show_option_all' => false, + ); + + echo EDD()->html->select( $email_select_args ); + ?> + + + + + + +

    + + id > 0 && count( $customer->emails ) > 1 ) : ?> +

    + +

      + emails as $email ) : ?> + email ) { continue; } ?> +
    • + + + rawurlencode( $email ), + 'edd_action' => 'profile-remove-email', + 'redirect' => esc_url( edd_get_current_page_url() ), + ) + ), + 'edd-remove-customer-email' + ); + ?> + + +
    • + +
    +

    + + + + +
    + + + +
    + + + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + + + + + + + +

    + + + +
    + + + +
    + + + +

    + + +

    + +

    + + + +

    + + + +
    + + + +
    + +

    + + + + +

    + +
    + + + +
    + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subtotal > 0 ) : ?> + + + + + + + get_discounts(); + if ( $order_discounts ) : + $label = _n( 'Discount', 'Discounts', count( $order_discounts ), 'easy-digital-downloads' ); + ?> + + + + description; + if ( 'percent' === edd_get_discount_type( $order_discount->type_id ) ) { + $rate = edd_format_discount_rate( 'percent', edd_get_discount_amount( $order_discount->type_id ) ); + $label .= " ({$rate})"; + } + ?> + + + + + + + + + + get_fees(); + if ( ! empty( $fees ) ) : + ?> + + + + description ) ) { + $label = $fee->description; + } + ?> + + + + + + + + tax > 0 ) : ?> + + + + + + get_credits(); + if ( $credits ) { + ?> + + + + description ) ) { + $label = $credit->description; + } + ?> + + + + + + + + + + + + + + + +
    :get_number() ); ?>
    :status ) ); ?>
    :payment_key ); ?>
    :gateway ) ); ?>
    :utils->date( $order->date_created, null, true )->toDateTimeString() ) ); ?>
    : + id ) ); ?> +
    :
    total ), $order->currency ) ); ?>
    :
    subtotal, $order->currency ) ); ?>
    :id ) ); ?>
    :
    total ), $order->currency ) ); ?>
    :
    + +get_items(); +if ( empty( $order_items ) ) { + return; +} +?> + +

    + + + + + + + + + + + + + + + $item ) : ?> + + + + + + + + + + + + + + + +
    + product_id, $item->price_id ); ?> + +
    + product_name ); + + if ( ! empty( $item->status ) && 'complete' !== $item->status ) { + echo ' – ' . esc_html( edd_get_status_label( $item->status ) ); + } + ?> +
    + product_id ); + if ( ! empty( $notes ) ) : ?> +
    + + + status && edd_receipt_show_download_files( $item->product_id, $edd_receipt_args, $item ) ) : ?> +
      + $file ) : + ?> +
    • + +
    • + product_id The product ID. + * @param int $order->id The order ID. + */ + do_action( 'edd_order_receipt_files', $filekey, $file, $item->product_id, $order->id ); + endforeach; + elseif ( edd_is_bundled_product( $item->product_id ) ) : + $bundled_products = edd_get_bundled_products( $item->product_id, $item->price_id ); + + foreach ( $bundled_products as $bundle_item ) : + ?> + +
    • + +
        + $file ) : + ?> +
      • + +
      • + product_id The product ID. + * @param array $bundle_item The array of information about the bundled item. + * @param int $order->id The order ID. + */ + do_action( 'edd_order_receipt_bundle_files', $filekey, $file, $item->product_id, $bundle_item, $order->id ); + endforeach; + else : + echo '
      • ' . esc_html__( 'No downloadable files found for this bundled item.', 'easy-digital-downloads' ) . '
      • '; + endif; + ?> +
      +
    • + ' . esc_html( apply_filters( 'edd_receipt_no_files_found_text', __( 'No downloadable files found.', 'easy-digital-downloads' ), $item->product_id ) ) . ''; + endif; + ?> +
    + + + +
    product_id ) ); ?>quantity ); ?> + total, $order->currency ) ); ?> +
    diff --git a/templates/shortcode-register.php b/templates/shortcode-register.php new file mode 100644 index 00000000000..9f292a21639 --- /dev/null +++ b/templates/shortcode-register.php @@ -0,0 +1,59 @@ + + + + +
    + + +
    + + + + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + + + + +

    + + + + +

    + + +
    + + +
    + + + + + + diff --git a/templates/widget-cart-checkout.php b/templates/widget-cart-checkout.php new file mode 100755 index 00000000000..c39c3877caf --- /dev/null +++ b/templates/widget-cart-checkout.php @@ -0,0 +1,6 @@ + +
  • " . edd_currency_filter( edd_format_amount( edd_get_cart_subtotal() ) ); ?>
  • +
  • + +
  • +
  • diff --git a/templates/widget-cart-empty.php b/templates/widget-cart-empty.php new file mode 100755 index 00000000000..ddd66975586 --- /dev/null +++ b/templates/widget-cart-empty.php @@ -0,0 +1,7 @@ +
  • + + + + + + diff --git a/templates/widget-cart-item.php b/templates/widget-cart-item.php new file mode 100755 index 00000000000..a333405cc05 --- /dev/null +++ b/templates/widget-cart-item.php @@ -0,0 +1,5 @@ +
  • + {item_title} + - {item_quantity} @ ' : ''; ?>{item_amount} - + +
  • diff --git a/templates/widget-cart.php b/templates/widget-cart.php new file mode 100644 index 00000000000..1b004d9488b --- /dev/null +++ b/templates/widget-cart.php @@ -0,0 +1,26 @@ + 0 ? '' : ' style="display:none;"'; +?> +

    >:

    +
      + + + $item ) : ?> + + + + + + + + + + + + +
    diff --git a/tests/README.md b/tests/README.md new file mode 100755 index 00000000000..3ce757c4bdc --- /dev/null +++ b/tests/README.md @@ -0,0 +1,24 @@ +# Easy Digital Downloads Unit Tests [![Build status](https://badge.buildkite.com/e318e1649f3a28f4029272231b926126d5664837d575cd507a.svg)](https://buildkite.com/sandhills-development-llc/easy-digital-downloads) + + +This folder contains all the tests for Easy Digital Downloads. + +## Running Tests +### How to run the tests locally +The ideal way to run the unit tests locally is by using the included Docker Compose files for the stack and for PHPUnit. + +Requirements: +- Docker (19.03.13 or newer) +- Docker Compose (1.25.5 or newer) + +To run the tests, use the following command: +``` +bin/run-tests-local.sh -p 7.4 -w latest +``` + +The flags `-p` and `-w` control the PHP and WordPress versions for the tests, respectively. + +This command sends the `phpunit` command to the `wordpress_phpunit` container. + +## Writing Tests +For more information on how to write PHPUnit Tests, see [PHPUnit's Website](http://www.phpunit.de/manual/3.6/en/writing-tests-for-phpunit.html). diff --git a/tests/ajax.php b/tests/ajax.php new file mode 100755 index 00000000000..c27189f486f --- /dev/null +++ b/tests/ajax.php @@ -0,0 +1,247 @@ +factory->post->create_many( 5 ); + + error_reporting( 0 & ~E_WARNING ); + } + + public function tearDown(): void { + parent::tearDown(); + $_POST = array(); + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); + remove_action( 'clear_auth_cookie', array( $this, 'logout' ) ); + set_current_screen( 'front' ); + EDD()->session->set( 'edd_cart', null ); + } + + public function logout() { + unset( $GLOBALS['current_user'] ); + $cookies = array(AUTH_COOKIE, SECURE_AUTH_COOKIE, LOGGED_IN_COOKIE, USER_COOKIE, PASS_COOKIE); + foreach ( $cookies as $c ) { + unset( $_COOKIE[ $c ] ); + } + } + + protected function _setRole( $role ) { + $post = $_POST; + $user_id = $this->factory->user->create( array( 'role' => $role ) ); + wp_set_current_user( $user_id ); + $_POST = array_merge( $_POST, $post ); + } + + protected function _handleAjax($action) { + // Start output buffering + ini_set( 'implicit_flush', false ); + ob_start(); + + // Build the request + $_POST['action'] = $action; + $_REQUEST = $_POST; + + // Call the hooks + do_action( 'wp_ajax_' . $_REQUEST['action'] ); + + // Save the output + $buffer = ob_get_clean(); + + if ( ! empty( $buffer ) ) { + $this->_last_response = $buffer; + } + + return $buffer; + } + + public function test_add_item_to_cart() { + $post_id = $this->factory->post->create( array( 'post_title' => 'Test Download', 'post_type' => 'download', 'post_status' => 'publish' ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 20 + ), + array( + 'name' => 'Advanced', + 'amount' => 100 + ) + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0 + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all' + ) + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 129.43, + '_edd_download_sales' => 59, + '_edd_download_limit_override_1' => 1 + ); + foreach( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + $this->_setRole( 'administrator' ); + + $_POST = array( + 'download_id' => $post_id, + 'variable_price' => 'yes', + 'price_mode' => 'single', + 'price_ids' => array( + 1 + ) + ); + + $this->_handleAjax( 'edd_add_to_cart' ); + $this->markTestIncomplete( 'This test needs to be rewritten per #600.'); + } + + public function test_remove_item_from_cart() { + $this->_setRole( 'administrator' ); + + $_POST = array( + 'cart_item' => 0 + ); + + $this->assertEquals( '{"removed":1,"subtotal":"$0.00","total":"$0.00"}', $this->_handleAjax( 'edd_remove_from_cart' ) ); + } + + public function test_checkout_register_fields() { + $this->_handleAjax( 'nopriv_checkout_register' ); + $this->markTestIncomplete( 'This test needs to be rewritten per #600.'); + } + + public function test_get_download_title() { + $post_id = $this->factory->post->create( array( 'post_title' => 'Test Download', 'post_type' => 'download', 'post_status' => 'publish' ) ); + $_POST = array( + 'download_id' => $post_id + ); + $this->_handleAjax( 'edd_get_download_title' ); + $this->assertEquals( 'Test Download', $this->_last_response ); + } + + public function test_check_for_download_price_variations() { + $post_id = $this->factory->post->create( array( 'post_title' => 'Test Download', 'post_type' => 'download', 'post_status' => 'draft' ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 20 + ), + array( + 'name' => 'Advanced', + 'amount' => 100 + ) + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0 + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all' + ) + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 129.43, + '_edd_download_sales' => 59, + '_edd_download_limit_override_1' => 1 + ); + foreach( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + $_POST = array( + 'download_id' => $post_id + ); + //$this->_handleAjax( 'edd_check_for_download_price_variations' ); + + $expected = ''; + //$this->assertEquals( $expected, $this->_last_response ); + } + + public function test_edd_test_ajax_works() { + $this->markTestIncomplete( 'Needs to be reworked per #3475' ); + $this->assertTrue( edd_test_ajax_works() ); + + $this->assertNotEmpty( get_transient( '_edd_ajax_works' ) ); + + // Now test for Airplane Mode plugin + + delete_transient( '_edd_ajax_works' ); + + class Airplane_Mode_Core { + function __construct() {} + public function enabled() { return true; } + } + + global $Airplane_Mode_Core; + $Airplane_Mode_Core = new Airplane_Mode_Core; + + // Should return true but should not set a transient + $this->assertTrue( edd_test_ajax_works() ); + $this->assertEmpty( get_transient( '_edd_ajax_works' ) ); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000000..604c4066f8b --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,87 @@ +components; + +foreach ( $components as $component ) { + $thing = $component->get_interface( 'table' ); + + if ( $thing instanceof \EDD\Database\Table ) { + if ( $thing->exists() ) { + $thing->uninstall(); + } + + $thing->install(); + } + + $thing = $component->get_interface( 'meta' ); + + if ( $thing instanceof \EDD\Database\Table ) { + if ( $thing->exists() ) { + $thing->uninstall(); + } + + $thing->install(); + } +} + +function _disable_reqs( $status = false, $args = array(), $url = '') { +} +add_filter( 'pre_http_request', function( $status = false, $args = array(), $url = '' ) { + return new \WP_Error( 'no_reqs_in_unit_tests', __( 'HTTP Requests disabled for unit tests', 'easy-digital-downloads' ) ); +} ); + +require_once 'helpers/shims.php'; + +remove_all_actions( 'send_headers' ); diff --git a/tests/customers/tests-customer-addresses.php b/tests/customers/tests-customer-addresses.php new file mode 100644 index 00000000000..cf0a4038660 --- /dev/null +++ b/tests/customers/tests-customer-addresses.php @@ -0,0 +1,788 @@ +customer_address->create_many( 5 ); + } + + /** + * @covers ::edd_update_customer_address + */ + public function test_update_should_return_true() { + $success = edd_update_customer_address( + self::$customer_addresses[0], + array( + 'address' => 'Address Line 1', + ) + ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_update_customer_address + */ + public function test_address_object_after_update_should_return_true() { + edd_update_customer_address( self::$customer_addresses[0], array( + 'address' => 'Address Line 1', + ) ); + + $customer_address = edd_fetch_customer_address( self::$customer_addresses[0] ); + + $this->assertSame( 'Address Line 1', $customer_address->address ); + } + + /** + * @covers ::edd_update_customer_address + */ + public function test_update_without_id_should_fail() { + $success = edd_update_customer_address( null, array( + 'email' => 'eddtest@edd.test', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_should_return_true() { + $success = edd_delete_customer_address( self::$customer_addresses[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_customer_address( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_number_should_return_true() { + $orders = edd_get_customer_addresses( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_offset_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_id_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->id < $customer_addresses[1]->id ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_id_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->id > $customer_addresses[1]->id ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_id__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_customer_id_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'customer_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->customer_id < $customer_addresses[1]->customer_id ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_customer_id_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'customer_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->customer_id > $customer_addresses[1]->customer_id ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_customer_id__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'customer_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_customer_id__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'customer_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_type_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'type', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->type < $customer_addresses[1]->type ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_type_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'type', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->type > $customer_addresses[1]->type ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_type__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'type__in' => array( + 'type' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_type__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'type__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_status_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'status', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->status < $customer_addresses[1]->status ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_status_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'status', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->status > $customer_addresses[1]->status ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_status__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'status__in' => array( + 'status' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_status__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'status__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_address_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'address', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->address < $customer_addresses[1]->address ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_address_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'address', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->address > $customer_addresses[1]->address ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_address__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'address__in' => array( + 'address' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_address__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'address__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_address2_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'address2', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->address2 < $customer_addresses[1]->address2 ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_address2_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'address2', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->address2 > $customer_addresses[1]->address2 ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_address2__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'address2__in' => array( + 'address2' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_address2__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'address2__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_city_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'city', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->city < $customer_addresses[1]->city ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_city_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'city', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->city > $customer_addresses[1]->city ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_city__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'city__in' => array( + 'city' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_city__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'city__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_region_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'region', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->region < $customer_addresses[1]->region ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_region_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'region', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->region > $customer_addresses[1]->region ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_region__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'region__in' => array( + 'region' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_region__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'region__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_postal_code_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'postal_code', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->postal_code < $customer_addresses[1]->postal_code ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_postal_code_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'postal_code', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->postal_code > $customer_addresses[1]->postal_code ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_postal_code__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'postal_code__in' => array( + 'postal_code' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_postal_code__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'postal_code__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_country_and_order_asc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'country', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_addresses[0]->country < $customer_addresses[1]->country ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_orderby_country_and_order_desc_should_return_true() { + $customer_addresses = edd_get_customer_addresses( array( + 'orderby' => 'country', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_addresses[0]->country > $customer_addresses[1]->country ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_country__in_should_return_1() { + $customer_addresses = edd_get_customer_addresses( array( + 'country__in' => array( + 'country' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_country__not_in_should_return_5() { + $customer_addresses = edd_get_customer_addresses( array( + 'country__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_addresses ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_id_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_customer_id_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'customer_id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_type_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'type' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_status_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'status' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_address_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'address' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_address2_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'address2' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_city_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'city' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_region_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'region' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_postal_code_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'postal_code' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_country_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'country' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_date_created_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'date_created' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_addresses + */ + public function test_get_customer_addresses_with_invalid_date_modified_should_return_0() { + $transactions = edd_get_customer_addresses( array( + 'date_modified' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_demote_customer_primary_addresses + */ + public function test_adding_new_primary_customer_address_demotes_previous_primary() { + $old_primary_address = parent::edd()->customer_address->create_and_get( array( + 'is_primary' => true + ) ); + + $this->assertEquals( '1', $old_primary_address->is_primary ); + + $number_primary_addresses = edd_count_customer_addresses( array( + 'is_primary' => true, + 'customer_id' => $old_primary_address->customer_id + ) ); + + $this->assertEquals( 1, $number_primary_addresses ); + + // Add another primary address. Old one should be demoted. + $new_primary_address = parent::edd()->customer_address->create_and_get( array( + 'is_primary' => true, + 'customer_id' => $old_primary_address->customer_id + ) ); + + $this->assertEquals( '1', $new_primary_address->is_primary ); + + $number_primary_addresses = edd_count_customer_addresses( array( + 'is_primary' => true, + 'customer_id' => $new_primary_address->customer_id + ) ); + + $this->assertEquals( 1, $number_primary_addresses ); + + // Re-fetch old address to ensure it's been demoted. + $old_primary_address = parent::edd()->customer_address->get_object_by_id( $old_primary_address->id ); + $this->assertEquals( '0', $old_primary_address->is_primary ); + } + + /** + * @covers ::edd_demote_customer_primary_addresses + */ + public function test_adding_new_non_primary_customer_address_doesnt_demote_primary() { + $primary_address = parent::edd()->customer_address->create_and_get( array( + 'is_primary' => true + ) ); + + $this->assertEquals( '1', $primary_address->is_primary ); + + // Add another, non-primary address. + $non_primary_address = parent::edd()->customer_address->create_and_get( array( + 'is_primary' => false, + 'customer_id' => $primary_address->customer_id + ) ); + + $this->assertEquals( '0', $non_primary_address->is_primary ); + + // Ensure primary address is still a primary. + $primary_address = parent::edd()->customer_address->get_object_by_id( $primary_address->id ); + $this->assertEquals( '1', $primary_address->is_primary ); + } +} diff --git a/tests/customers/tests-customer-email-addresses.php b/tests/customers/tests-customer-email-addresses.php new file mode 100644 index 00000000000..7008c0544d5 --- /dev/null +++ b/tests/customers/tests-customer-email-addresses.php @@ -0,0 +1,420 @@ +customer_email_address->create_many( 5 ); + } + + /** + * @covers ::edd_update_customer_email_address + */ + public function test_update_should_return_true() { + $success = edd_update_customer_email_address( self::$customer_email_addresses[0], array( + 'email' => 'eddtest@edd.test', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_update_customer_email_address + */ + public function test_email_address_object_after_update_should_return_true() { + edd_update_customer_email_address( self::$customer_email_addresses[0], array( + 'email' => 'eddtest@edd.test', + ) ); + + $customer_email_address = edd_get_customer_email_address( self::$customer_email_addresses[0] ); + + $this->assertSame( 'eddtest@edd.test', $customer_email_address->email ); + } + + /** + * @covers ::edd_update_customer_email_address + */ + public function test_update_without_id_should_fail() { + $success = edd_update_customer_email_address( null, array( + 'email' => 'eddtest@edd.test', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_should_return_true() { + $success = edd_delete_customer_email_address( self::$customer_email_addresses[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_customer_email_address( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_number_should_return_true() { + $orders = edd_get_customer_email_addresses( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_offset_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_id_and_order_asc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->id < $customer_email_addresses[1]->id ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_id_and_order_desc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->id > $customer_email_addresses[1]->id ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_id__not_in_should_return_5() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_customer_id_and_order_asc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'customer_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->customer_id < $customer_email_addresses[1]->customer_id ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_customer_id_and_order_desc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'customer_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->customer_id > $customer_email_addresses[1]->customer_id ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_customer_id__in_should_return_1() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'customer_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_customer_id__not_in_should_return_5() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'customer_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_type_and_order_asc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'type', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->type < $customer_email_addresses[1]->type ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_type_and_order_desc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'type', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->type > $customer_email_addresses[1]->type ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_type__in_should_return_1() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'type__in' => array( + 'type' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_type__not_in_should_return_5() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'type__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_status_and_order_asc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'status', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->status < $customer_email_addresses[1]->status ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_status_and_order_desc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'status', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->status > $customer_email_addresses[1]->status ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_status__in_should_return_1() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'status__in' => array( + 'status' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_status__not_in_should_return_5() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'status__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_email_and_order_asc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'email', + 'order' => 'asc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->email < $customer_email_addresses[1]->email ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_orderby_email_and_order_desc_should_return_true() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'orderby' => 'email', + 'order' => 'desc', + ) ); + + $this->assertTrue( $customer_email_addresses[0]->email > $customer_email_addresses[1]->email ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_email__in_should_return_1() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'email__in' => array( + 'user' . \WP_UnitTest_Generator_Sequence::$incr . '@edd.test', + ), + ) ); + + $this->assertCount( 1, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_email__not_in_should_return_5() { + $customer_email_addresses = edd_get_customer_email_addresses( array( + 'email__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $customer_email_addresses ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_invalid_id_should_return_0() { + $transactions = edd_get_customer_email_addresses( array( + 'id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_invalid_customer_id_should_return_0() { + $transactions = edd_get_customer_email_addresses( array( + 'customer_id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_invalid_type_should_return_0() { + $transactions = edd_get_customer_email_addresses( array( + 'type' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_invalid_status_should_return_0() { + $transactions = edd_get_customer_email_addresses( array( + 'status' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_invalid_email_should_return_0() { + $transactions = edd_get_customer_email_addresses( array( + 'email' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_invalid_date_created_should_return_0() { + $transactions = edd_get_customer_email_addresses( array( + 'date_created' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_customer_email_addresses + */ + public function test_get_customer_email_addresses_with_invalid_date_modified_should_return_0() { + $transactions = edd_get_customer_email_addresses( array( + 'date_modified' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } +} diff --git a/tests/customers/tests-customer-meta.php b/tests/customers/tests-customer-meta.php new file mode 100644 index 00000000000..428ed8beb42 --- /dev/null +++ b/tests/customers/tests-customer-meta.php @@ -0,0 +1,88 @@ +customer->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'customer', 'meta' )->truncate(); + } + + /** + * @covers \EDD\Database\Queries\Notes::add_meta() + * @covers Note::add_meta() + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( self::$customer->add_meta( '', '' ) ); + } + + public function test_add_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( self::$customer->add_meta( 'test_key', '' ) ); + } + + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( self::$customer->add_meta( 'test_key', '1' ) ); + } + + public function test_update_metadata_with_empty_key_value_should_return_false() { + $this->assertEmpty( self::$customer->update_meta( '', '' ) ); + } + + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( self::$customer->update_meta( 'test_key_2', '' ) ); + } + + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( self::$customer->update_meta( 'test_key_2', '1' ) ); + } + + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertEmpty( self::$customer->get_meta() ); + } + + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertEmpty( self::$customer->get_meta( 'key_that_does_not_exist', true ) ); + self::$customer->update_meta( 'test_key_2', '1' ); + $this->assertEquals( '1', self::$customer->get_meta( 'test_key_2', true ) ); + $this->assertIsArray( self::$customer->get_meta( 'test_key_2', false ) ); + } + + public function test_get_metadata_after_update_should_return_1_and_be_of_type_array() { + self::$customer->update_meta( 'test_key_2', '1' ); + + $this->assertEquals( '1', self::$customer->get_meta( 'test_key_2', true ) ); + $this->assertIsArray( self::$customer->get_meta( 'test_key_2', false ) ); + } + + public function test_delete_metadata_after_update() { + self::$customer->update_meta( 'test_key', '1' ); + + $this->assertTrue( self::$customer->delete_meta( 'test_key' ) ); + $this->assertFalse( self::$customer->delete_meta( 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/customers/tests-customer-stats.php b/tests/customers/tests-customer-stats.php new file mode 100644 index 00000000000..5cf35c5c050 --- /dev/null +++ b/tests/customers/tests-customer-stats.php @@ -0,0 +1,46 @@ +customer->create_many( + 5, + array( + 'purchase_count' => wp_rand( 1, 100 ), + ) + ); + self::$stats = new \EDD\Stats(); + } + + public function test_get_customer_count_ignore_purchase_value_returns_five() { + $this->assertEquals( 5, self::$stats->get_customer_count() ); + } + + public function test_get_customer_count_include_purchase_value_returns_same_amount() { + $original_customer_count = self::$stats->get_customer_count(); + $ignored_customer = edd_add_customer( + array( + 'email' => 'totallynewcustomer@edd.local', + 'name' => 'Totally New Customer', + ) + ); + $this->assertEquals( $original_customer_count, self::$stats->get_customer_count( array( 'purchase_count' => true ) ) ); + } +} diff --git a/tests/customers/tests-customers-db.php b/tests/customers/tests-customers-db.php new file mode 100644 index 00000000000..84c84299662 --- /dev/null +++ b/tests/customers/tests-customers-db.php @@ -0,0 +1,351 @@ +customer->create_many( 5 ); + } + + public function test_installed() { + $this->assertTrue( edd_get_component_interface( 'customer', 'table' )->exists() ); + } + + public function test_insert_with_no_email_supplied_should_return_false() { + $this->assertFalse( edd_add_customer( array( + 'name' => 'John Smith', + ) ) ); + } + + public function test_insert_with_invalid_data_should_return_false() { + $this->assertFalse( edd_add_customer() ); + } + + public function test_update_should_return_false_if_no_row_id_supplied() { + $this->assertFalse( edd_update_customer( 0 ) ); + } + + public function test_customer_object_after_update_should_return_false() { + $this->assertSame( 1, edd_update_customer( self::$customers[0], array( + 'name' => 'John Smith' + ) ) ); + + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( self::$customers[0] ); + + $this->assertSame( 'John Smith', $customer->name ); + } + + public function test_delete_should_return_false_if_no_row_id_supplied() { + $this->assertFalse( edd_delete_customer( 0 ) ); + } + + public function test_delete_should_return_false() { + $this->assertSame( 1, edd_delete_customer( self::$customers[0] ) ); + + $customer = edd_get_customer( self::$customers[0] ); + + $this->assertFalse( $customer ); + } + + public function test_get_customers_should_return_5() { + $customers = edd_get_customers(); + + $this->assertCount( 5, $customers ); + } + + public function test_get_customers_with_number_should_return_5() { + $customers = edd_get_customers( array( + 'number' => 10 + ) ); + + $this->assertCount( 5, $customers ); + } + + public function test_get_customers_with_offset_should_return_true() { + $customers = edd_get_customers( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $customers ); + } + + public function test_get_customers_with_orderby_name_and_order_asc_should_return_true() { + $customers = edd_get_customers( array( + 'orderby' => 'name', + 'order' => 'asc' + ) ); + + $this->assertTrue( $customers[0]->name < $customers[1]->name ); + } + + public function test_get_customers_with_orderby_name_and_order_desc_should_return_true() { + $customers = edd_get_customers( array( + 'orderby' => 'name', + 'order' => 'desc' + ) ); + + $this->assertTrue( $customers[0]->name > $customers[1]->name ); + } + + public function test_get_customers_with_orderby_email_and_order_asc_should_return_true() { + $customers = edd_get_customers( array( + 'orderby' => 'email', + 'order' => 'asc' + ) ); + + $this->assertTrue( $customers[0]->email < $customers[1]->email ); + } + + public function test_get_customers_with_orderby_email_and_order_desc_should_return_true() { + $customers = edd_get_customers( array( + 'orderby' => 'email', + 'order' => 'desc' + ) ); + + $this->assertTrue( $customers[0]->email > $customers[1]->email ); + } + + public function test_get_customers_with_id_should_be_1() { + $customers = edd_get_customers( array( + 'id' => self::$customers[4] + ) ); + + $this->assertCount( 1, $customers ); + } + + public function test_get_customers_with_email_should_be_1() { + $customers = edd_get_customers( array( + 'email' => 'user' . \WP_UnitTest_Generator_Sequence::$incr . '@edd.test' + ) ); + + $this->assertCount( 1, $customers ); + } + + public function test_get_customers_by_invalid_id_should_be_0() { + $customers = edd_get_customers( array( + 'id' => 99999, + ) ); + + $this->assertCount( 0, $customers ); + } + + public function test_count_should_be_5() { + $this->assertSame( 5, edd_count_customers() ); + } + + public function test_count_with_id_should_be_1() { + $this->assertSame( 1, edd_count_customers( array( + 'id' => self::$customers[2] + ) ) ); + } + + public function test_count_with_email_should_be_1() { + $this->assertSame( 1, edd_count_customers( array( + 'email' => 'user' . \WP_UnitTest_Generator_Sequence::$incr . '@edd.test' + ) ) ); + } + + public function test_get_customer_by() { + $customer = edd_get_customer_by( 'email', 'user' . \WP_UnitTest_Generator_Sequence::$incr . '@edd.test' ); + + $this->assertSame( 'user' . \WP_UnitTest_Generator_Sequence::$incr . '@edd.test', $customer->email ); + } + + public function test_update_customer_email_on_user_update() { + $user_id = wp_insert_user( array( + 'user_login' => 'user' . ( \WP_UnitTest_Generator_Sequence::$incr - 1 ), + 'user_email' => 'user' . ( \WP_UnitTest_Generator_Sequence::$incr - 1 ) . '@edd.test', + 'user_pass' => wp_generate_password() + ) ); + + edd_update_customer( self::$customers[3], array( + 'user_id' => $user_id + ) ); + + wp_update_user( array( + 'ID' => $user_id, + 'user_email' => 'user' . \WP_UnitTest_Generator_Sequence::$incr . '-updated@edd.test', + ) ); + + $updated_customer = new \EDD_Customer( 'user' . \WP_UnitTest_Generator_Sequence::$incr . '-updated@edd.test' ); + + $this->assertEquals( self::$customers[3], $updated_customer->id ); + } + + public function test_querying_customer_by_secondary_email_should_return_customer() { + $customer_id = self::$customers[4]; + edd_add_customer_email_address( + array( + 'customer_id' => $customer_id, + 'email' => 'totallynewemail@edd.test', + 'type' => 'secondary', + ) + ); + + $customers = edd_get_customers( + array( + 'email' => 'totallynewemail@edd.test', + ) + ); + $customer = reset( $customers ); + + $this->assertEquals( $customer_id, $customer->id ); + } + + public function test_querying_customer_by_secondary_email_in_should_return_customers() { + edd_add_customer_email_address( + array( + 'customer_id' => self::$customers[3], + 'email' => 'totallynewemail3@edd.test', + 'type' => 'secondary', + ) + ); + + edd_add_customer_email_address( + array( + 'customer_id' => self::$customers[4], + 'email' => 'totallynewemail4@edd.test', + 'type' => 'secondary', + ) + ); + + $customers = edd_get_customers( + array( + 'email__in' => array( 'totallynewemail3@edd.test', 'totallynewemail4@edd.test' ), + ) + ); + + $found_customer_ids = array_map( 'absint', wp_list_pluck( $customers, 'id' ) ); + + $this->assertTrue( in_array( self::$customers[3], $found_customer_ids, true ) ); + $this->assertTrue( in_array( self::$customers[4], $found_customer_ids, true ) ); + } + + public function test_querying_customer_by_secondary_email_not_in_should_not_return_customer() { + $customer_id = self::$customers[4]; + $customer = edd_get_customer( $customer_id ); + + edd_add_customer_email_address( + array( + 'customer_id' => $customer_id, + 'email' => 'totallynewemail@edd.test', + 'type' => 'secondary', + ) + ); + + $customers = edd_get_customers( + array( + 'email__not_in' => array( 'totallynewemail@edd.test', $customer->email ), + ) + ); + + $found_customer_ids = array_map( 'absint', wp_list_pluck( $customers, 'id' ) ); + + $this->assertFalse( in_array( $customer_id, $found_customer_ids, true ) ); + } + + public function test_legacy_attach_payment_should_return_true() { + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + + // Legacy method that should be handled by Customer back-compat class. + EDD()->customers->attach_payment( self::$customers[0], $payment_id ); + + $customer = edd_get_customer( self::$customers[0] ); + $payment_ids = array_map( 'absint', explode( ',', $customer->payment_ids ) ); + + $this->assertTrue( in_array( $payment_id, $payment_ids ) ); + } + + public function test_legacy_remove_payment_should_return_false() { + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + + // Legacy method that should be handled by Customer back-compat class. + EDD()->customers->attach_payment( self::$customers[0], $payment_id ); + EDD()->customers->remove_payment( self::$customers[0], $payment_id ); + + $customer = edd_get_customer( self::$customers[0] ); + $payment_ids = array_map( 'absint', explode( ',', $customer->payment_ids ) ); + + $this->assertFalse( in_array( $payment_id, $payment_ids ) ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_purchase_count + * @expectEDDeprecated EDD_Customer::increase_value + */ + public function test_legacy_increment_stats_purchase_value_should_return_10() { + EDD()->customers->increment_stats( self::$customers[0], 10 ); + + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( self::$customers[0] ); + + $this->assertSame( 10.0, $customer->purchase_value ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_purchase_count + * @expectEDDeprecated EDD_Customer::increase_value + */ + public function test_legacy_increment_stats_purchase_count_should_return_1() { + EDD()->customers->increment_stats( self::$customers[0], 10 ); + + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( self::$customers[0] ); + + $this->assertSame( 1, $customer->purchase_count ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_purchase_count + * @expectEDDeprecated EDD_Customer::increase_value + * @expectEDDeprecated EDD_Customer::decrease_purchase_count + * @expectEDDeprecated EDD_Customer::decrease_value + */ + public function test_legacy_decrement_stats_purchase_value_should_return_90() { + EDD()->customers->increment_stats( self::$customers[0], 100 ); + EDD()->customers->decrement_stats( self::$customers[0], 10 ); + + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( self::$customers[0] ); + + $this->assertSame( 90.0, $customer->purchase_value ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_purchase_count + * @expectEDDeprecated EDD_Customer::increase_value + * @expectEDDeprecated EDD_Customer::decrease_purchase_count + * @expectEDDeprecated EDD_Customer::decrease_value + */ + public function test_legacy_decrement_stats_purchase_count_should_return_0() { + EDD()->customers->increment_stats( self::$customers[0], 10 ); + EDD()->customers->decrement_stats( self::$customers[0], 10 ); + + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( self::$customers[0] ); + + $this->assertSame( 0, $customer->purchase_count ); + } +} diff --git a/tests/customers/tests-customers.php b/tests/customers/tests-customers.php new file mode 100644 index 00000000000..cd22bf9aa56 --- /dev/null +++ b/tests/customers/tests-customers.php @@ -0,0 +1,441 @@ +customer->create_many( 5 ); + + foreach ( $customers as $customer ) { + self::$customers[] = edd_get_customer( $customer ); + } + + self::$user = 1; + self::$order = Helpers\EDD_Helper_Payment::create_simple_payment(); + + // Don't trigger the updateing of the customer during this part of the setup. + remove_action( 'edd_customer_updated', 'edd_process_customer_updated', 10, 3 ); + edd_update_customer( self::$customers[0], array( + 'user_id' => self::$user, + ) ); + add_action( 'edd_customer_updated', 'edd_process_customer_updated', 10, 3 ); + + self::$customers[0]->attach_payment( self::$order ); + self::$customers[0] = edd_get_customer( $customers[0] ); + + edd_update_payment_status( self::$order, 'complete' ); + } + + public function test_create_customer_from_EDD_Customer_should_be_greater_than_0() { + $test_email = 'testaccount@domain.com'; + + $customer = new \EDD_Customer( $test_email ); + $this->assertEquals( 0, $customer->id ); + + $customer_id = $customer->create( array( 'email' => 'testaccount@domain.com' ) ); + + $this->assertGreaterThan( 0, $customer->id ); + $this->assertSame( $customer_id, $customer->id ); + $this->assertSame( $test_email, $customer->email ); + } + + public function test_update_customer_from_EDD_Customer_should_be_true() { + $data = array( + 'email' => 'testaccountupdated@domain.com', + 'name' => 'Test Account', + ); + + self::$customers[1]->update( $data ); + + $this->assertSame( $data['email'], self::$customers[1]->email ); + $this->assertSame( $data['name'], self::$customers[1]->name ); + } + + public function test_update_customer_from_EDD_Customer_with_no_data_should_return_false() { + $this->assertFalse( self::$customers[0]->update() ); + } + + public function test_attach_payment_to_customer_should_return_true() { + $order_ids = array_map( 'absint', explode( ',', self::$customers[0]->payment_ids ) ); + + $this->assertTrue( in_array( self::$order, $order_ids ) ); + } + + public function test_attach_payment_with_invalid_data_should_return_false() { + $this->assertFalse( self::$customers[0]->attach_payment() ); + } + + public function test_attach_payment_twice_should_return_true() { + self::$customers[2]->attach_payment( self::$order ); + + $expected_purchase_count = self::$customers[2]->purchase_count; + $expected_purchase_value = self::$customers[2]->purchase_value; + + self::$customers[2]->attach_payment( self::$order ); + + $this->assertSame( $expected_purchase_count, self::$customers[2]->purchase_count ); + $this->assertSame( $expected_purchase_value, self::$customers[2]->purchase_value ); + } + + public function test_remove_payment_should_return_true() { + self::$customers[2]->attach_payment( self::$order ); + self::$customers[2]->remove_payment( self::$order ); + + $order_ids = array_map( 'absint', explode( ',', self::$customers[2]->payment_ids ) ); + + $this->assertFalse( in_array( self::$order, $order_ids ) ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_value + */ + public function test_increase_value_should_return_10() { + self::$customers[3]->increase_value( 10 ); + + $this->assertSame( 10.0, self::$customers[3]->purchase_value ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_purchase_count + */ + public function test_increase_purchase_count_should_return_1() { + self::$customers[3]->increase_purchase_count(); + + $this->assertSame( 1, self::$customers[3]->purchase_count ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_value + * @expectEDDeprecated EDD_Customer::decrease_value + */ + public function test_decrease_value_should_return_90() { + self::$customers[4]->increase_value( 100 ); + self::$customers[4]->decrease_value( 10 ); + + $this->assertSame( 90.0, self::$customers[4]->purchase_value ); + } + + /** + * @expectEDDeprecated EDD_Customer::increase_purchase_count + * @expectEDDeprecated EDD_Customer::decrease_purchase_count + */ + public function test_decrease_purchase_count_should_return_0() { + self::$customers[3]->increase_purchase_count(); + self::$customers[3]->decrease_purchase_count(); + + $this->assertSame( 0, self::$customers[1]->purchase_count ); + } + + public function test_add_customer_note_should_return_1() { + self::$customers[0]->add_note( 'Test Note' ); + $this->assertSame( 1, self::$customers[0]->get_notes_count() ); + } + + public function test_paged_notes_should_return_1() { + self::$customers[0]->add_note( 'Test Note 1' ); + self::$customers[0]->add_note( 'Test Note 2' ); + + $this->assertCount( 1, self::$customers[0]->get_notes( 1, 2 ) ); + } + + public function test_get_payment_ids_of_customer_should_return_1() { + self::$customers[0]->attach_payment( self::$order ); + + $this->assertCount( 1, self::$customers[0]->get_payment_ids() ); + } + + public function test_get_payment_ids_of_customer_with_no_payments_should_return_0() { + $this->assertCount( 0, parent::edd()->customer->create_and_get()->get_payment_ids() ); + } + + public function test_add_email_should_return_id() { + $this->assertGreaterThanOrEqual( 1, self::$customers[1]->add_email( 'added-email@edd.test' ) ); + + /** @var $customer \EDD_Customer */ + $customer = edd_get_customer( self::$customers[1]->id ); + + $this->assertTrue( in_array( 'added-email@edd.test', $customer->emails ) ); + } + + public function test_add_email_with_primary_parameter_should_return_id() { + $this->assertGreaterThanOrEqual( 1, self::$customers[2]->add_email( 'added-email2@edd.test', true ) ); + $this->assertSame( 'added-email2@edd.test', self::$customers[2]->email ); + } + + public function test_remove_email_should_return_false() { + self::$customers[1]->add_email( 'added-email@edd.test' ); + + $this->assertTrue( self::$customers[1]->remove_email( 'added-email@edd.test' ) ); + $this->assertFalse( in_array( 'added-email@edd.test', self::$customers[1]->emails, true ) ); + } + + public function test_validate_username_should_return_true() { + $this->assertTrue( edd_validate_username( 'easydigitaldownloads' ) ); + } + + public function test_validate_username_with_invalid_characters_should_return_false() { + $this->assertFalse( edd_validate_username( 'edd12345$%&+-!@£%^&()(*&^%$£@!' ) ); + } + + public function test_get_users_purchases_should_return_1() { + $this->assertCount( 1, edd_get_users_purchases( self::$user ) ); + } + + public function test_get_users_purchases_with_invalid_user_id_should_return_false() { + $this->assertFalse( edd_get_users_purchases( 0 ) ); + } + + public function test_edd_get_users_purchases_new_email_equals_1() { + $customer = self::$customers[0]; + $email = 'totallynewemail@edd.local'; + edd_add_customer_email_address( + array( + 'customer_id' => $customer->id, + 'type' => 'primary', + 'email' => $email, + ) + ); + $purchases = edd_get_users_purchases( $email ); + if ( is_array( $purchases ) ) { + $purchases = count( $purchases ); + } + + $this->assertEquals( 1, $purchases ); + } + + public function test_user_has_purchases_not_logged_in_should_return_false() { + $current_user = get_current_user_id(); + wp_set_current_user( 0 ); + edd_update_order( + self::$order, + array( + 'user_id' => 0, + ) + ); + + $this->assertFalse( edd_has_purchases() ); + wp_set_current_user( $current_user ); + } + + public function test_user_has_purchases_should_return_true() { + self::$customers[0]->attach_payment( self::$order ); + + $this->assertTrue( edd_has_purchases( self::$user ) ); + } + + public function test_count_purchases_of_user_should_return_1() { + $this->assertEquals( 1, edd_count_purchases_of_customer( self::$user ) ); + } + + public function test_count_purchases_of_user_with_no_args_should_return_0() { + $this->assertEquals( 0, edd_count_purchases_of_customer() ); + } + + public function test_users_purchased_product_pending_should_be_false() { + $this->assertFalse( edd_get_users_purchased_products( self::$user, 'pending' ) ); + } + + public function test_user_has_purchased_with_invalid_user_and_download_id_should_return_false() { + $this->assertFalse( edd_has_user_purchased( 0, 888 ) ); + } + + public function test_user_has_purchased_with_valid_user_and_download_id_should_return_true() { + $this->assertTrue( edd_has_user_purchased( self::$user, edd_get_payment( self::$order )->downloads[0]['id'] ) ); + } + + public function test_user_has_purchased_with_valid_user_and_invalid_download_id_should_return_false() { + $this->assertFalse( edd_has_user_purchased( self::$user, 99999 ) ); + } + + public function test_edd_add_past_purchases_to_new_user() { + $order_id = Helpers\EDD_Helper_Payment::create_simple_guest_payment(); + + $userdata = array( + 'user_login' => 'guest', + 'user_email' => 'guest@example.org', + 'user_pass' => 'guest_pass' + ); + $user_id = wp_insert_user( $userdata ); + + $orders = edd_get_payments( array( 's' => $userdata['user_email'], 'output' => 'payments' ) ); + $order = $orders[0]; + + $this->assertSame( $order->ID, $order_id ); + } + + public function test_user_verification_base_url() { + $original_purchase_history_page = edd_get_option( 'purchase_history_page', 0 ); + $purchase_history_page = get_permalink( $original_purchase_history_page ); + $this->assertEquals( $purchase_history_page, edd_get_user_verification_page() ); + + edd_update_option( 'purchase_history_page', 0 ); + $home_url = home_url(); + $this->assertEquals( $home_url, edd_get_user_verification_page() ); + + edd_update_option( 'purchase_history_page', $original_purchase_history_page ); + } + + public function test_set_user_to_verified_with_no_user_id_should_return_false() { + $this->assertFalse( edd_set_user_to_verified() ); + } + + public function test_set_user_to_pending_with_no_user_id_should_return_false() { + $this->assertFalse( edd_set_user_to_pending() ); + } + + public function test_set_active_user_to_verified_should_return_false() { + $this->assertFalse( edd_set_user_to_verified( 1 ) ); + } + + public function test_active_user_is_pending_should_return_false() { + $this->assertFalse( edd_user_pending_verification( 1 ) ); + } + + public function test_set_user_to_pending_should_return_true() { + $this->assertTrue( edd_set_user_to_pending( 1 ) ); + $this->assertEquals( '1', get_user_meta( 1, '_edd_pending_verification', true ) ); + $this->assertTrue( edd_user_pending_verification( 1 ) ); + } + + public function test_set_user_to_verified_should_return_true() { + edd_set_user_to_pending( 1 ); + + $this->assertTrue( edd_set_user_to_verified( 1 ) ); + $this->assertEmpty( get_user_meta( 1, '_edd_pending_verification', true ) ); + $this->assertFalse( edd_user_pending_verification( 1 ) ); + } + + public function test_get_user_verification_url_with_no_id_should_return_false() { + $this->assertFalse( edd_get_user_verification_url() ); + } + + public function test_get_user_verification_url_should_return_true() { + $url = edd_get_user_verification_url( 1 ); + + $this->assertStringContainsString( 'edd_action=verify_user', $url ); + $this->assertStringContainsString( 'user_id=1', $url ); + $this->assertStringContainsString( 'ttl', $url ); + $this->assertStringContainsString( 'token', $url ); + } + + public function test_get_user_verification_request_url_should_return_true() { + $url = edd_get_user_verification_request_url( 1 ); + + $this->assertStringContainsString( 'edd_action=send_verification_email', $url ); + } + + public function test_validate_user_verification_token_with_valid_url_should_true() { + $url = edd_get_user_verification_url( 1 ); + + $this->assertTrue( edd_validate_user_verification_token( $url ) ); + } + + public function test_validate_user_verification_token_with_invalid_url_should_false() { + $url = edd_get_user_verification_url( 1 ); + + $this->assertFalse( edd_validate_user_verification_token( substr( $url, -1 ) ) ); + $this->assertFalse( edd_validate_user_verification_token( remove_query_arg( 'token', $url ) ) ); + } + + public function test_get_purchase_total_of_user_should_return_120() { + self::$customers[0]->attach_payment( self::$order ); + + $purchase_total = edd_purchase_total_of_user( self::$user ); + + $this->assertSame( '120.00', $purchase_total ); + } + + public function test_get_payment_ids_with_invalid_customer_should_be_empty() { + $customer_id = edd_add_customer( array( + 'email' => 'test_user@example.com' + ) ); + $customer = new \EDD_Customer( $customer_id ); + + $this->assertEmpty( $customer->get_payment_ids() ); + } + + public function test_get_payments_with_invalid_customer_should_be_empty() { + $customer = new \EDD_Customer( 'test_user@example.com' ); + + $this->assertEmpty( $customer->get_payments() ); + } + + public function test_get_users_purchased_products_should_return_2() { + $this->assertCount( 2, (array) edd_get_users_purchased_products( self::$user ) ); + } + + public function test_get_purchase_stats_by_user_should_return_true() { + $stats = edd_get_purchase_stats_by_user( self::$user ); + + $this->assertSame( 1, $stats['purchases'] ); + $this->assertSame( '120.00', $stats['total_spent'] ); + } + + public function test_get_customer_payment_ids_should_return_1() { + $this->assertCount( 1, self::$customers[0]->get_payment_ids() ); + } + + public function test_get_customer_payments_should_return_1() { + $this->assertCount( 1, self::$customers[0]->get_payments() ); + } + + public function test_get_customer_pending_payments_should_be_empty() { + $this->assertEmpty( self::$customers[0]->get_payments( 'pending' ) ); + } + + public function test_customer_and_user_order_lookup_success() { + self::$customers[0]->attach_payment( self::$order ); + + $customers_orders = edd_get_orders( array( 'customer_id' => self::$customers[0]->id, 'number' => 9999 ) ); + $users_orders = edd_get_orders( array( 'user_id' => self::$customers[0]->user_id, 'number' => 9999 ) ); + + $this->assertEquals( $customers_orders, $users_orders ); + } + + public function test_customer_and_user_order_lookup_success_after_user_id_change() { + self::$customers[0]->attach_payment( self::$order ); + + self::$customers[0]->update( array( 'user_id' => 2 ) ); + $this->assertSame( '2', self::$customers[0]->user_id ); + + $customers_orders = edd_get_orders( array( 'customer_id' => self::$customers[0]->id, 'number' => 9999 ) ); + $users_orders = edd_get_orders( array( 'user_id' => self::$customers[0]->user_id, 'number' => 9999 ) ); + + $this->assertEquals( $customers_orders, $users_orders ); + } +} diff --git a/tests/discounts/tests-discount-meta.php b/tests/discounts/tests-discount-meta.php new file mode 100644 index 00000000000..cbdd5b6112f --- /dev/null +++ b/tests/discounts/tests-discount-meta.php @@ -0,0 +1,141 @@ +discount->create_object( array( + 'name' => '20 Percent Off', + 'code' => '20OFF', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '20', + 'use_count' => 54, + 'max_uses' => 10, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59' + ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::add_meta() + * @covers EDD_Discount::add_meta() + */ + public function test_add_metadata_with_empty_key_value_should_be_null() { + $this->assertFalse( edd_add_adjustment_meta( self::$discount_id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::add_meta() + * @covers EDD_Discount::add_meta() + */ + public function test_add_metadata_with_empty_value_should_be_empty() { + $this->assertNotEmpty( edd_add_adjustment_meta( self::$discount_id, 'test_key', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::add_meta() + * @covers EDD_Discount::add_meta() + */ + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_adjustment_meta( self::$discount_id, 'test_key', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::update_meta() + * @covers EDD_Discount::update_meta() + */ + public function test_update_metadata_with_empty_key_value_should_be_empty() { + $this->assertEmpty( edd_update_adjustment_meta( self::$discount_id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::update_meta() + * @covers EDD_Discount::update_meta() + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_adjustment_meta( self::$discount_id, 'test_key_2', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::update_meta() + * @covers EDD_Discount::update_meta() + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_adjustment_meta( self::$discount_id, 'test_key_2', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::get_meta() + * @covers EDD_Discount::get_meta() + */ + public function test_get_metadata_with_no_args_should_return_array() { + $this->assertSame( 1, count( edd_get_adjustment_meta( self::$discount_id ) ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::get_meta() + * @covers EDD_Discount::get_meta() + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertEmpty( edd_get_adjustment_meta( self::$discount_id, 'key_that_does_not_exist', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::get_meta() + * @covers EDD_Discount::get_meta() + */ + public function test_get_metadata_after_update_should_return_that_value() { + edd_update_adjustment_meta( self::$discount_id, 'test_key_2', '1' ); + $this->assertEquals( '1', edd_get_adjustment_meta( self::$discount_id, 'test_key_2', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::delete_meta() + * @covers EDD_Discount::delete_meta() + */ + public function test_delete_metadata_with_valid_key_should_return_true() { + edd_update_adjustment_meta( self::$discount_id, 'test_key', '1' ); + $this->assertTrue( edd_delete_adjustment_meta( self::$discount_id, 'test_key' ) ); + } + + /** + * @covers \EDD\Database\Queries\Adjustment::delete_meta() + * @covers EDD_Discount::delete_meta() + */ + public function test_delete_metadata_with_invalid_key_should_return_false() { + $this->assertFalse( edd_delete_adjustment_meta( self::$discount_id, 'key_that_does_not_exist' ) ); + } + + /** + * @covers \edd_get_discount_product_condition() + */ + public function test_discount_product_condition() { + $this->assertSame( 'all', edd_get_discount_product_condition( self::$discount_id ) ); + } +} diff --git a/tests/discounts/tests-discounts-db.php b/tests/discounts/tests-discounts-db.php new file mode 100644 index 00000000000..91a3158a957 --- /dev/null +++ b/tests/discounts/tests-discounts-db.php @@ -0,0 +1,471 @@ + '$10 Off', + 'code' => '10FLAT', + 'status' => 'active', + 'type' => 'flat', + 'amount' => '10', + 'max_uses' => 10, + 'use_count' => 54, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59' + ), + array( + 'name' => '20 Percent Off', + 'code' => '20OFF', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '20', + 'max_uses' => 10, + 'use_count' => 54, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59' + ) + ); + + foreach ( $args as $discount ) { + edd_add_discount( $discount ); + self::$discounts[] = edd_get_discount_by_code( $discount['code'] ); + } + } + + /** + * @covers ::insert() + */ + public function test_insert() { + $id = edd_add_discount( array( + 'code' => 'TEST', + 'status' => 'active', + ) ); + + $this->assertTrue( $id > 0 ); + } + + /** + * @covers ::update() + */ + public function test_update() { + $success = (bool) edd_update_discount( self::$discounts[0]->id, array( + 'code' => 'NEWCODE', + ) ); + + $this->assertTrue( $success ); + } + + /** + * @covers ::update() + */ + public function test_update_without_id_should_fail() { + $success = (bool) edd_update_discount( null, array( + 'code' => 'NEWCODE', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::delete() + */ + public function test_delete_should_return_false_because_use_count_greater_than_1() { + $retval = (bool) edd_delete_discount( self::$discounts[0]->id ); + + $this->assertFalse( $retval ); + } + + /** + * @covers ::delete() + */ + public function test_delete_should_return_false_because_use_count_is_0() { + edd_update_discount( self::$discounts[0]->id, array( + 'use_count' => 0, + ) ); + + $success = (bool) edd_delete_discount( self::$discounts[0]->id ); + + $this->assertTrue( $success ); + } + + /** + * @covers ::delete() + */ + public function test_delete_without_id_should_fail() { + $success = (bool) edd_delete_discount(); + + $this->assertFalse( $success ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts() { + $discounts = edd_get_discounts(); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_returns_expected_discount() { + $discounts = edd_get_discounts(); + + $this->assertEquals( '20OFF', $discounts[0]->code ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_number_should_return_true() { + $discounts = edd_get_discounts( array( + 'number' => 10, + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_offset_should_return_true() { + $discounts = edd_get_discounts( array( + 'offset' => 1, + ) ); + + $this->assertCount( 1, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_offset_order_asc() { + $discounts = edd_get_discounts( array( + 'offset' => 1, + 'order' => 'asc', + ) ); + + $this->assertCount( 1, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_search_by_code() { + $discounts = edd_get_discounts( array( + 'search' => '10FLAT', + ) ); + + $this->assertEquals( '10FLAT', $discounts[0]->code ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_search_by_name() { + $discounts = edd_get_discounts( array( + 'search' => '$10 Off', + ) ); + + $this->assertTrue( '10FLAT' === $discounts[0]->code ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_order_asc() { + $discounts = edd_get_discounts( array( + 'order' => 'asc', + ) ); + + $this->assertTrue( $discounts[0]->id < $discounts[1]->id ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_order_desc() { + $discounts = edd_get_discounts( array( + 'order' => 'desc', + ) ); + + $this->assertTrue( $discounts[0]->id > $discounts[1]->id ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_orderby() { + $discounts = edd_get_discounts( array( + 'orderby' => 'code', + 'order' => 'asc', + ) ); + + $this->assertTrue( strcmp( $discounts[0]->code, $discounts[1]->code ) < 0 ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_type() { + $discounts = edd_get_discounts( array( + 'type' => 'percent', + ) ); + + $this->assertCount( 1, $discounts ); + + $discounts = edd_get_discounts( array( + 'type' => 'flat', + ) ); + + $this->assertCount( 1, $discounts ); + + $discounts = edd_get_discounts( array( + 'type' => array( + 'percent', + 'flat', + ), + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_status() { + $discounts = edd_get_discounts( array( + 'status' => 'active', + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_date_created() { + $discounts = edd_get_discounts( array( + 'date_created_query' => array( + 'year' => date( 'Y', strtotime( 'now' ) ), + 'month' => date( 'm', strtotime( 'now' ) ), + 'day' => date( 'd', strtotime( 'now' ) ), + ) + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_created_start_date() { + $discounts = edd_get_discounts( array( + 'date_created_query' => array( + 'year' => date( 'Y', strtotime( 'now' ) ), + 'month' => date( 'm', strtotime( 'now' ) ), + 'day' => date( 'd', strtotime( 'now' ) ), + ) + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_created_end_date() { + $discounts = edd_get_discounts( array( + 'date_created_query' => array( + 'year' => date( 'Y', strtotime( 'now' ) ), + 'month' => date( 'm', strtotime( 'now' ) ), + 'day' => date( 'd', strtotime( 'now' ) ), + ) + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_end_date() { + $discounts = edd_get_discounts( array( + 'end_date' => '2050-12-31 23:59:59', + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::get_discounts() + */ + public function test_get_discounts_with_start_date() { + $discounts = edd_get_discounts( array( + 'start_date' => '2010-12-12 00:00:00', + ) ); + + $this->assertCount( 2, $discounts ); + } + + /** + * @covers ::count() + */ + public function test_count() { + $this->assertEquals( 2, edd_get_discount_count() ); + } + + /** + * @covers ::count() + */ + public function test_count_with_search() { + $discounts = count( edd_get_discounts( array( + 'search' => '20OFF', + ) ) ); + + $this->assertEquals( 1, $discounts ); + + $discounts = count( edd_get_discounts( array( + 'search' => 'FREE', + ) ) ); + + $this->assertEquals( 0, $discounts ); + } + + /** + * @covers ::count() + */ + public function test_count_with_active_status() { + $this->assertEquals( 2,count( edd_get_discounts( array( + 'status' => 'active', + ) ) ) ); + } + + /** + * @covers ::count() + */ + public function test_count_with_expired_status() { + $discount_id = Helpers\EDD_Helper_Discount::created_expired_flat_discount(); + + $this->assertSame( 1,count( edd_get_discounts( array( + 'status' => 'expired', + ) ) ) ); + + edd_delete_discount( $discount_id ); + } + + /** + * @covers ::count() + */ + public function test_count_with_type() { + $this->assertSame( 1,count( edd_get_discounts( array( + 'type' => 'percent', + ) ) ) ); + } + + /** + * @covers ::count() + */ + public function test_count_with_date_created() { + $this->assertSame( 2, count( edd_get_discounts( array( + 'date_created_query' => array( + 'year' => date( 'Y', strtotime( 'now' ) ), + 'month' => date( 'm', strtotime( 'now' ) ), + 'day' => date( 'd', strtotime( 'now' ) ), + ) + ) ) ) ); + } + + /** + * @covers ::count() + */ + public function test_count_with_start_date() { + $this->assertSame( 2, count( edd_get_discounts( array( + 'start_date_query' => array( + array( + 'year' => 2010, + 'month' => 12, + 'day' => 12, + ), + 'hour' => 0, + 'minute' => 0, + 'second' => 0, + ) + ) ) ) ); + } + + /** + * @covers ::count() + */ + public function test_count_with_end_date() { + $discounts = edd_get_discounts( array( + 'end_date_query' => array( + array( + 'year' => 2050, + 'month' => 12, + 'day' => 12, + ), + 'hour' => 23, + 'minute' => 59, + 'second' => 59, + ) + ) ); + + $this->assertSame( 2, count( $discounts ) ); + } + + /** + * @covers ::counts_by_status() + */ + public function test_counts_by_status_active_discounts() { + $counts = edd_get_discount_counts(); + + $this->assertSame( 2, $counts['active'] ); + } + + /** + * @covers ::counts_by_status() + */ + public function test_counts_by_status_inactive_discounts() { + $counts = edd_get_discount_counts(); + + $this->assertSame( false, isset( $counts['inactive'] ) ); + } + + /** + * @covers ::counts_by_status() + */ + public function test_counts_by_status_expired_discounts() { + $counts = edd_get_discount_counts(); + + $this->assertSame( false, isset( $counts['expired'] ) ); + } +} diff --git a/tests/discounts/tests-discounts.php b/tests/discounts/tests-discounts.php new file mode 100755 index 00000000000..8d1577728cf --- /dev/null +++ b/tests/discounts/tests-discounts.php @@ -0,0 +1,1157 @@ +assertGreaterThan( 0, self::$discount->id ); + } + + /** + * @covers ::setup_discount() + */ + public function test_id_is_0_when_no_id_is_passed() { + $d = new \EDD_Discount(); + + $this->assertTrue( 0 === $d->id ); + } + + /** + * @covers ::setup_discount() + */ + public function test_discount_id_matches_id() { + $this->assertEquals( self::$discount->id, self::$discount_id ); + } + + /** + * @covers ::setup_discount() + */ + public function test_discount_id_matches_capital_ID() { + $this->assertEquals( self::$discount->ID, self::$discount_id ); + } + + /** + * @covers ::setup_discount() + */ + public function test_get_discount_name() { + $this->assertEquals( '20 Percent Off', self::$discount->name ); + } + + /** + * @covers ::setup_discount() + */ + public function test_get_discount_name_by_property() { + $this->assertEquals( '20OFF', self::$discount->code ); + } + + /** + * @covers ::get_code() + */ + public function test_get_discount_name_by_method() { + $this->assertEquals( '20OFF', self::$discount->get_code() ); + } + + /** + * @covers ::get_status() + */ + public function test_get_discount_status_by_property() { + $this->assertEquals( 'active', self::$discount->status ); + } + + /** + * @covers ::get_status() + */ + public function test_get_discount_status_by_method() { + $this->assertEquals( 'active', self::$discount->get_status() ); + } + + /** + * @covers ::get_expiration() + */ + public function test_get_discount_expiration_by_property_backcompat() { + $this->assertEquals( date( 'Y-m-d', time() ) . ' 23:59:59', self::$discount->expiration ); + } + + /** + * @covers ::get_expiration() + */ + public function test_get_discount_expiration_by_method_backcompat() { + $this->assertEquals( date( 'Y-m-d', time() ) . ' 23:59:59', self::$discount->get_expiration() ); + } + + /** + * @covers ::end_date + */ + public function test_get_discount_end_date_by_property() { + $this->assertEquals( date( 'Y-m-d', time() ) . ' 23:59:59', self::$discount->end_date ); + } + + /** + * @covers ::get_uses() + */ + public function test_get_discount_uses_by_property() { + $this->assertEquals( 54, self::$discount->uses ); + } + + /** + * @covers ::get_uses() + */ + public function test_get_discount_uses_by_method() { + $this->assertEquals( 54, self::$discount->get_uses() ); + } + + /** + * @covers ::get_max_uses() + */ + public function test_get_discount_max_uses_by_property() { + $this->assertEquals( 10, self::$discount->max_uses ); + } + + /** + * @covers ::get_max_uses() + */ + public function test_get_discount_max_uses_by_method() { + $this->assertEquals( 10, self::$discount->get_max_uses() ); + } + + /** + * @covers ::get_min_price() + */ + public function test_get_discount_min_price_by_property() { + $this->assertEquals( 128, self::$discount->min_charge_amount ); + } + + /** + * @covers ::get_min_price() + */ + public function test_get_discount_min_price_by_method() { + $this->assertEquals( 128, self::$discount->get_min_price() ); + } + + /** + * @covers ::get_is_single_use() + */ + public function test_get_discount_is_single_use_should_return_false() { + $this->assertFalse( self::$discount->get_is_single_use() ); + } + + /** + * @covers ::get_once_per_customer() + */ + public function test_get_discount_is_once_per_customer_should_return_false() { + $this->assertFalse( self::$discount->get_once_per_customer() ); + } + + /** + * @covers ::exists() + */ + public function test_discount_exists_should_return_true() { + $this->assertTrue( self::$discount->exists() ); + } + + /** + * @covers ::get_type() + */ + public function test_get_discount_type_by_property() { + $this->assertEquals( 'percent', self::$discount->type ); + } + + /** + * @covers ::get_type() + */ + public function test_get_discount_type_by_method() { + $this->assertEquals( 'percent', self::$discount->get_type() ); + } + + /** + * @covers ::get_type() + */ + public function test_get_discount_type_of_flat_discount() { + $d = new \EDD_Discount( self::$flatdiscount_id ); + $this->assertEquals( 'flat', $d->type ); + } + + /** + * @covers ::get_amount() + */ + public function test_get_discount_amount_by_property() { + $this->assertEquals( '20', self::$discount->amount ); + } + + /** + * @covers ::get_amount() + */ + public function test_get_discount_amount_by_method() { + $this->assertEquals( '20', self::$discount->get_amount() ); + } + + /** + * @covers ::get_product_reqs() + */ + public function test_get_discount_product_requirements_by_method() { + $this->assertSame( array(), self::$discount->get_product_reqs() ); + } + + /** + * @covers ::get_product_reqs() + */ + public function test_get_discount_product_requirements_by_property() { + $this->assertSame( array(), self::$discount->product_reqs ); + } + + /** + * @covers ::get_excluded_products() + */ + public function test_get_discount_excluded_products_by_method() { + $this->assertSame( array(), self::$discount->get_excluded_products() ); + } + + /** + * @covers ::get_excluded_products() + */ + public function test_get_discount_excluded_products_by_property() { + $this->assertSame( array(), self::$discount->excluded_products ); + } + + /** + * @covers ::save() + * @covers ::add() + */ + public function test_discount_save() { + $discount = new \EDD_Discount(); + $discount->code = '30FLAT'; + $discount->name = '$30 Off'; + $discount->type = 'flat'; + $discount->amount = '30'; + + $discount->save(); + + $this->assertGreaterThan( 0, (int) $discount->id ); + } + + /** + * @covers ::add() + * @covers ::sanitize_columns() + * @covers ::convert_legacy_args() + */ + public function test_discount_add() { + $args = array( + 'code' => '30FLAT', + 'name' => '$30 Off', + 'type' => 'flat', + 'amount' => 30, + ); + + $discount = new \EDD_Discount(); + $discount->add( $args ); + + $this->assertGreaterThan( 0, $discount->id ); + } + + /** + * @covers ::update() + * @covers ::sanitize_columns() + * @covers ::convert_legacy_args() + */ + public function test_discount_update_type() { + $args = array( + 'type' => 'flat', + 'amount' => 50, + ); + + self::$discount->update( $args ); + + $this->assertEquals( 'flat', self::$discount->type ); + } + + /** + * @covers ::update() + * @covers ::sanitize_columns() + * @covers ::convert_legacy_args() + */ + public function test_discount_update_amount() { + $args = array( + 'amount' => 50, + ); + + self::$discount->update( $args ); + + $this->assertEquals( 50.0, self::$discount->amount ); + } + + /** + * @covers ::update_status() + * @covers ::get_status() + */ + public function test_discount_update_status_with_no_args() { + self::$discount->update_status(); + + $this->assertEquals( 'active', self::$discount->status ); + } + + /** + * @covers ::update_status() + * @covers ::get_status() + */ + public function test_discount_update_status_to_inactive() { + self::$discount->update_status( 'inactive' ); + + $this->assertEquals( 'inactive', self::$discount->status ); + } + + /** + * @covers ::is_product_requirements_met() + */ + public function test_discount_is_product_requirements_met() { + $args = array( + 'product_reqs' => array( self::$download->ID ), + ); + + edd_update_discount( self::$discount_id, $args ); + + edd_add_to_cart( self::$download->ID ); + + $this->assertTrue( self::$discount->is_product_requirements_met() ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_product_requirements_any_all_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => $products, + 'product_condition' => 'any', + 'max_uses' => 10000, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertTrue( edd_validate_discount( self::$discount_id, $products ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_product_requirements_any_none_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => $products, + 'product_condition' => 'any', + 'max_uses' => 10000, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertFalse( edd_validate_discount( self::$discount_id, array( 123 ) ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_product_requirements_any_one_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => $products, + 'product_condition' => 'any', + 'max_uses' => 10000, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertTrue( edd_validate_discount( self::$discount_id, array( self::$download->ID ) ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_product_requirements_all_all_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => $products, + 'product_condition' => 'all', + 'max_uses' => 10000, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertTrue( edd_validate_discount( self::$discount_id, $products ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_product_requirements_all_none_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => $products, + 'product_condition' => 'all', + 'max_uses' => 10000, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertFalse( edd_validate_discount( 123 ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_product_requirements_all_one_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => $products, + 'product_condition' => 'all', + 'max_uses' => 10000, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertFalse( edd_validate_discount( self::$discount_id, array( self::$download->ID ) ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_excluded_products_all_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => array(), + 'max_uses' => 10000, + 'excluded_products' => $products, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertFalse( edd_validate_discount( self::$discount_id, $products ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_excluded_products_none_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => array(), + 'max_uses' => 10000, + 'excluded_products' => $products, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertTrue( edd_validate_discount( self::$discount_id, array( 546 ) ) ); + } + + /** + * @covers edd_validate_discount + */ + public function test_edd_validate_discount_excluded_products_one_in_array() { + $products = array( self::$download->ID, 100 ); + $args = array( + 'product_reqs' => array(), + 'max_uses' => 10000, + 'excluded_products' => $products, + ); + + edd_update_discount( self::$discount_id, $args ); + $this->assertFalse( edd_validate_discount( self::$discount_id, array( self::$download->ID ) ) ); + } + + /** + * @covers ::edit_url() + */ + public function test_discount_edit_url() { + $this->assertStringContainsString( 'edit.php?post_type=download&page=edd-discounts', self::$discount->edit_url() ); + } + + /** + * @covers ::update_meta() + */ + public function test_discount_update_meta() { + edd_update_adjustment_meta( self::$discount->id, 'test_meta_key', 'test_meta_value' ); + + $this->assertEquals( 'test_meta_value', edd_get_adjustment_meta( self::$discount->id, 'test_meta_key', true ) ); + } + + /** + * @covers ::delete_meta() + */ + public function test_discount_delete_meta_with_no_meta_key_should_be_false() { + $this->assertFalse( edd_delete_adjustment_meta( self::$download->ID, '' ) ); + } + + /* + * Legacy tests + * + * All tests below are from before EDD 3.0 when discounts were stored as wp_posts. + * EDD 3.0 stores them in a custom table. + * The below tests are left here to help ensure the backwards compatibility layers work properly + */ + public function test_discount_created() { + $this->assertIsInt( self::$discount_id ); + } + + public function test_addition_of_negative_discount() { + $this->assertIsInt( self::$negativediscount_id ); + } + + public function test_addition_of_flat_discount() { + $this->assertIsInt( self::$flatdiscount_id ); + } + + /** + * @covers \edd_store_discount() + */ + public function test_updating_discount_code() { + $post = array( + 'name' => '20 Percent Off', + 'type' => 'percent', + 'amount' => '20', + 'code' => '20OFF', + 'product_condition' => 'all', + 'start' => date( 'm/d/Y', time() ) . ' 00:00:00', + 'expiration' => date( 'm/d/Y', time() ) . ' 23:59:59', + 'max' => 10, + 'uses' => 54, + 'min_price' => 128, + 'status' => 'active' + ); + + $updated_id = edd_store_discount( $post, self::$discount_id ); + $this->assertEquals( $updated_id, self::$discount_id ); + } + + /** + * @covers \edd_update_discount_status() + */ + public function test_discount_status_update_inactive() { + $this->assertTrue( edd_update_discount_status( self::$discount_id, 'inactive' ) ); + $discount = edd_get_discount( self::$discount_id ); + $this->assertEquals( 'inactive', $discount->status ); + + $this->assertTrue( edd_update_discount_status( self::$discount_id, 'active' ) ); + $discount = edd_get_discount( self::$discount_id ); + $this->assertEquals( 'active', $discount->status ); + } + + /** + * @covers \edd_update_discount_status() + */ + public function test_discount_status_update() { + $this->assertTrue( edd_update_discount_status( self::$discount_id, 'active' ) ); + } + + /** + * @covers \edd_update_discount_status() + */ + public function test_discount_status_update_fail() { + $this->assertFalse( edd_update_discount_status( -1 ) ); + } + + /** + * @covers \edd_has_active_discounts() + */ + public function test_discounts_exists() { + edd_update_discount_status( self::$discount_id, 'active' ); + + $this->assertTrue( edd_has_active_discounts() ); + } + + /** + * @covers \edd_update_discount_status() + * @covers \edd_is_discount_active() + * @covers \edd_store_discount() + */ + public function test_is_discount_active() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + edd_update_discount_status( self::$discount_id, 'active' ); + + $this->assertTrue( edd_is_discount_active( self::$discount_id, true ) ); + $this->assertTrue( edd_is_discount_active( self::$discount_id, false ) ); + + $post = array( + 'name' => '20 Percent Off', + 'type' => 'percent', + 'amount' => '20', + 'code' => '20OFFEXPIRED', + 'product_condition' => 'all', + 'start' => date( 'm/d/Y', time() - DAY_IN_SECONDS*5 ) . ' 00:00:00', + 'expiration' => date( 'm/d/Y', time() - DAY_IN_SECONDS*5 ) . ' 23:59:59', + 'max' => 10, + 'uses' => 54, + 'min_price' => 128, + 'status' => 'active' + ); + + $expired_discount_id = edd_store_discount( $post ); + + $this->assertFalse( edd_is_discount_active( $expired_discount_id, true ) ); + + $this->assertEquals( 'expired', get_post_meta( $expired_discount_id, '_edd_discount_status', true ) ); + } + + /** + * @covers \edd_discount_exists() + */ + public function test_discount_exists_helper() { + $this->assertTrue( edd_discount_exists( self::$discount_id ) ); + } + + /** + * @covers \edd_update_discount_status() + * @covers \edd_get_discount() + */ + public function test_get_discount() { + edd_update_discount_status( self::$discount_id, 'active' ); + + $discount = edd_get_discount( self::$discount_id ); + + $this->assertEquals( self::$discount_id, $discount->id ); + $this->assertEquals( '20 Percent Off', $discount->post_title ); + $this->assertEquals( 'active', $discount->post_status ); + } + + /** + * @covers \edd_get_discount_code() + */ + public function test_get_discount_code() { + $this->assertSame( '20OFF', edd_get_discount_code( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_start_date() + */ + public function test_discount_start_date() { + $this->assertSame( date( 'Y-m-d', time() ) . ' 00:00:00', edd_get_discount_start_date( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_expiration() + */ + public function test_discount_expiration_date() { + $this->assertSame( date( 'Y-m-d', time() ) . ' 23:59:59', edd_get_discount_expiration( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_max_uses() + */ + public function test_discount_max_uses() { + $this->assertSame( 10, edd_get_discount_max_uses( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_uses() + */ + public function test_discount_uses() { + $this->assertSame( 54, edd_get_discount_uses( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_min_price() + */ + public function test_discount_min_price() { + $this->assertSame( '128.00', edd_get_discount_min_price( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_amount() + */ + public function test_discount_amount() { + $this->assertSame( 20.0, edd_get_discount_amount( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_amount() + */ + public function test_discount_amount_negative() { + $this->assertSame( -100.0, edd_get_discount_amount( self::$negativediscount_id ) ); + } + + /** + * @covers \edd_get_discount_type() + */ + public function test_discount_type() { + $this->assertSame( 'percent', edd_get_discount_type( self::$discount_id ) ); + } + + /** + * @covers \edd_is_discount_not_global() + */ + public function test_discount_is_not_global() { + $this->assertFalse( edd_is_discount_not_global( self::$discount_id ) ); + } + + /** + * @covers \edd_discount_is_single_use() + */ + public function test_discount_is_single_use() { + $this->assertFalse( edd_discount_is_single_use( self::$discount_id ) ); + } + + /** + * @covers \edd_is_discount_started() + */ + public function test_discount_is_started() { + $this->assertTrue( edd_is_discount_started( self::$discount_id ) ); + } + + /** + * @covers \edd_is_discount_expired() + */ + public function test_discount_is_expired() { + $this->assertFalse( edd_is_discount_expired( self::$discount_id ) ); + } + + public function test_discount_is_expired_timezone_change() { + update_option( 'gmt_offset', 25 ); + $this->assertFalse( edd_is_discount_expired( self::$discount_id ) ); + update_option( 'gmt_offset', 0 ); + } + + /** + * @covers \edd_is_discount_maxed_out() + */ + public function test_discount_is_maxed_out() { + $this->assertTrue( edd_is_discount_maxed_out( self::$discount_id ) ); + } + + /** + * @covers \edd_discount_is_min_met() + */ + public function test_discount_is_min_met() { + $this->assertFalse( edd_discount_is_min_met( self::$discount_id ) ); + } + + /** + * @covers \edd_is_discount_used() + * @covers ::is_used() + */ + public function test_discount_is_used() { + $this->assertFalse( edd_is_discount_used( '20OFF' ) ); + } + + /** + * @covers ::setup_discount() + * @covers ::get_is_single_use() + * @covers ::is_used() + */ + public function test_is_used_case_insensitive() { + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + $payment = edd_get_payment( $payment_id ); + $payment->discounts = '20off'; + $payment->status = 'publish'; + $payment->save(); + + $discount = new \EDD_Discount( '20OFF', true ); + $discount->is_single_use = true; + $this->assertTrue( $discount->is_used( 'admin@example.org', false ) ); + $discount->is_single_use = false; + + Helpers\EDD_Helper_Payment::delete_payment( $payment_id ); + } + + /** + * @covers \edd_is_discount_valid() + * @covers ::is_valid() + */ + public function test_discount_is_valid_when_purchasing() { + $this->assertFalse( edd_is_discount_valid( '20OFF' ) ); + } + + /** + * @covers \edd_get_discount_id_by_code() + *@covers \edd_get_discount_id_by() + */ + public function test_discount_id_by_code() { + $id = edd_get_discount_id_by_code( '20OFF' ); + $discount = edd_get_discount_by( 'code', '20OFF' ); + + $this->assertSame( $discount->id, $id ); + } + + + /** + * @covers \edd_get_discounted_amount() + * @covers ::get_discounted_amount() + */ + public function test_get_discounted_amount() { + $this->assertEquals( '432', edd_get_discounted_amount( '20OFF', '540' ) ); + $this->assertEquals( '150', edd_get_discounted_amount( 'DOUBLE', '75' ) ); + $this->assertEquals( '10', edd_get_discounted_amount( '10FLAT', '20' ) ); + + // Test that an invalid Code returns the base price + $this->assertEquals( '10', edd_get_discounted_amount( 'FAKEDISCOUNT', '10' ) ); + } + + /** + * @covers \edd_get_discount_id_by_code() + * @covers \edd_get_discount_uses() + * @covers \edd_increase_discount_usage() + * @covers ::increase_usage() + */ + public function test_increase_discount_usage() { + $id = edd_get_discount_id_by_code( '20OFF' ); + $uses = edd_get_discount_uses( $id ); + + $increased = edd_increase_discount_usage( '20OFF' ); + $this->assertequals( $increased, (int) $uses + 1 ); + + // Test missing codes + $this->assertFalse( edd_increase_discount_usage( 'INVALIDDISCOUNTCODE' ) ); + } + + /** + * @covers _edd_discount_update_meta_backcompat() + * @covers \edd_get_discount_code() + * @covers \edd_increase_discount_usage() + */ + public function test_discount_inactive_at_max() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + $this->setExpectedIncorrectUsage( 'add_post_meta()/update_post_meta()' ); + + update_post_meta( self::$discount_id, '_edd_discount_status', 'active' ); + + $code = edd_get_discount_code( self::$discount_id ); + + update_post_meta( self::$discount_id, '_edd_discount_max', 10 ); + update_post_meta( self::$discount_id, '_edd_discount_uses', 9 ); + + edd_increase_discount_usage( $code ); + + $this->assertEquals( 'inactive', get_post_meta( self::$discount_id, '_edd_discount_status', true ) ); + } + + /** + * @covers _edd_discount_update_meta_backcompat() + * @covers \edd_get_discount_code() + * @covers \edd_increase_discount_usage() + * @covers ::decrease_usage() + */ + public function test_discount_active_after_decreasing_at_max() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + $this->setExpectedIncorrectUsage( 'add_post_meta()/update_post_meta()' ); + + update_post_meta( self::$discount_id, '_edd_discount_max', 10 ); + update_post_meta( self::$discount_id, '_edd_discount_uses', 10 ); + update_post_meta( self::$discount_id, '_edd_discount_status', 'inactive' ); + + $code = edd_get_discount_code( self::$discount_id ); + + edd_decrease_discount_usage( $code ); + + $this->assertEquals( 'active', get_post_meta( self::$discount_id, '_edd_discount_status', true ) ); + } + + /** + * @covers \edd_get_discount_id_by_code() + * @covers \edd_get_discount_uses() + * @covers \edd_decrease_discount_usage() + */ + public function test_decrease_discount_usage() { + $id = edd_get_discount_id_by_code( '20OFF' ); + $uses = edd_get_discount_uses( $id ); + + $decreased = edd_decrease_discount_usage( '20OFF' ); + $this->assertSame( $decreased, (int) $uses - 1 ); + + // Test missing codes + $this->assertFalse( edd_decrease_discount_usage( 'INVALIDDISCOUNTCODE' ) ); + } + + /** + * @covers _edd_discount_post_meta_bc_filter() + * @covers \edd_format_discount_rate() + */ + public function test_formatted_discount_amount() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $rate = get_post_meta( self::$discount_id, '_edd_discount_amount', true ); + $this->assertSame( '20.00%', edd_format_discount_rate( 'percent', $rate ) ); + } + + /** + * @covers \edd_get_discount_by() + */ + public function test_edd_get_discount_by() { + $discount = edd_get_discount_by( 'id', self::$discount_id ); + + $this->assertEquals( $discount->id, self::$discount_id ); + $this->assertEquals( '20 Percent Off', edd_get_discount_by( 'code', '20OFF' )->post_title ); + $this->assertEquals( $discount->id, edd_get_discount_by( 'code', '20OFF' )->id ); + $this->assertEquals( $discount->id, edd_get_discount_by( 'name', '20 Percent Off' )->id ); + } + + /** + * @covers \edd_get_discount_amount() + * @covers \edd_format_discount_rate() + */ + public function test_formatted_discount_amount_negative() { + $amount = edd_get_discount_amount( self::$negativediscount_id ); + $this->assertSame( '-100.00%', edd_format_discount_rate( 'percent', $amount ) ); + } + + /** + * @covers \edd_get_discount_amount() + * @covers \edd_format_discount_rate() + */ + public function test_formatted_discount_amount_flat() { + $amount = edd_get_discount_amount( self::$flatdiscount_id ); + + $this->assertSame( '$10.00', edd_format_discount_rate( 'flat', $amount ) ); + } + + /** + * @covers \edd_get_discount_excluded_products() + * @covers ::get_excluded_products() + */ + public function test_discount_excluded_products() { + $this->assertIsArray( edd_get_discount_excluded_products( self::$discount_id ) ); + } + + /** + * @covers \edd_get_discount_product_reqs() + * @covers ::get_product_reqs() + */ + public function test_discount_product_reqs() { + $this->assertIsArray( edd_get_discount_product_reqs( self::$discount_id ) ); + } + + /** + * @covers \edd_delete_discount() + * @covers \edd_get_discount() + */ + public function test_deletion_of_discount_should_be_false_because_use_count_greater_than_1() { + edd_delete_discount( self::$discount_id ); + + $this->assertInstanceOf( 'EDD_Discount', edd_get_discount( self::$discount_id ) ); + + edd_delete_discount( self::$negativediscount_id ); + + $this->assertInstanceOf( 'EDD_Discount', edd_get_discount( self::$negativediscount_id ) ); + } + + /** + * @covers \edd_set_cart_discount() + * @covers \edd_get_discount_code() + */ + public function test_set_discount() { + EDD()->session->set( 'cart_discounts', null ); + + edd_add_to_cart( self::$download->ID ); + + $this->assertEquals( '20.00', edd_get_cart_total() ); + + edd_set_cart_discount( edd_get_discount_code( self::$discount_id ) ); + $this->assertEquals( '16.00', edd_get_cart_total() ); + } + + /** + * @covers \edd_set_cart_discount() + */ + public function test_set_multiple_discounts() { + $this->setExpectedIncorrectUsage( 'add_post_meta()/update_post_meta()' ); + + EDD()->session->set( 'cart_discounts', null ); + + edd_update_option( 'allow_multiple_discounts', true ); + + edd_add_to_cart( self::$download->ID ); + + $this->assertEquals( '20.00', edd_get_cart_total() ); + + // Test a single discount code + $discounts = edd_set_cart_discount( self::$discount->code ); + + $this->assertIsArray( $discounts ); + $this->assertTrue( 1 === count( $discounts ) ); + $this->assertEquals( '16.00', edd_get_cart_total() ); + + // Test a single discount code again but with lower case + $discounts = edd_set_cart_discount( strtolower( self::$discount->code ) ); + + $this->assertIsArray( $discounts ); + $this->assertTrue( 1 === count( $discounts ) ); + $this->assertEquals( '16.00', edd_get_cart_total() ); + + // Test a new code + $code_id = Helpers\EDD_Helper_Discount::create_simple_percent_discount(); + update_post_meta( $code_id, '_edd_discount_code', 'SECONDcode' ); + + $discounts = edd_set_cart_discount( 'SECONDCODE' ); + + $this->assertIsArray( $discounts ); + $this->assertTrue( 2 === count( $discounts ) ); + $this->assertEquals( '12.00', edd_get_cart_total() ); + } + + /** + * @covers \edd_store_discount() + * @covers \edd_get_cart_discountable_subtotal() + */ + public function test_discountable_subtotal() { + $download_1 = Helpers\EDD_Helper_Download::create_simple_download(); + $download_2 = Helpers\EDD_Helper_Download::create_simple_download(); + edd_add_to_cart( $download_1->ID ); + edd_add_to_cart( $download_2->ID ); + + $discount = Helpers\EDD_Helper_Discount::create_simple_flat_discount(); + $post = array( + 'name' => 'Excludes', + 'amount' => '1', + 'code' => 'EXCLUDES', + 'product_condition' => 'all', + 'start' => date( 'm/d/Y H:i:s', time() ), + 'expiration' => date( 'm/d/Y H:i:s', time() + HOUR_IN_SECONDS ), + 'min_price' => 23, + 'status' => 'active', + 'excluded-products' => array( $download_2->ID ), + ); + edd_store_discount( $post, $discount ); + + $this->assertEquals( '20', edd_get_cart_discountable_subtotal( $discount ) ); + + $download_3 = Helpers\EDD_Helper_Download::create_simple_download(); + edd_add_to_cart( $download_3->ID ); + + $this->assertEquals( '40', edd_get_cart_discountable_subtotal( $discount ) ); + + Helpers\EDD_Helper_Download::delete_download( $download_1->ID ); + Helpers\EDD_Helper_Download::delete_download( $download_2->ID ); + Helpers\EDD_Helper_Download::delete_download( $download_3->ID ); + Helpers\EDD_Helper_Discount::delete_discount( $discount ); + } + + /** + * @covers \edd_discount_is_min_met() + * @covers \edd_is_discount_valid() + */ + public function test_discount_min_excluded_products() { + edd_empty_cart(); + $download_1 = Helpers\EDD_Helper_Download::create_simple_download(); + $download_2 = Helpers\EDD_Helper_Download::create_simple_download(); + $discount = Helpers\EDD_Helper_Discount::create_simple_flat_discount(); + + $post = array( + 'name' => 'Excludes', + 'amount' => '1', + 'code' => 'EXCLUDES', + 'product_condition' => 'all', + 'start' => date( 'm/d/Y H:i:s', time() ), + 'expiration' => date( 'm/d/Y H:i:s', time() + HOUR_IN_SECONDS ), + 'min_price' => 23, + 'status' => 'active', + 'excluded-products' => array( $download_2->ID ), + ); + + edd_store_discount( $post, $discount ); + + edd_add_to_cart( $download_1->ID ); + edd_add_to_cart( $download_2->ID ); + $this->assertFalse( edd_discount_is_min_met( $discount ) ); + + $download_3 = Helpers\EDD_Helper_Download::create_simple_download(); + edd_add_to_cart( $download_3->ID ); + $this->assertTrue( edd_discount_is_min_met( $discount ) ); + + edd_empty_cart(); + edd_add_to_cart( $download_2->ID ); + $discount_obj = edd_get_discount( $discount ); + $this->assertFalse( edd_is_discount_valid( $discount_obj->code ) ); + + Helpers\EDD_Helper_Download::delete_download( $download_1->ID ); + Helpers\EDD_Helper_Download::delete_download( $download_2->ID ); + Helpers\EDD_Helper_Download::delete_download( $download_3->ID ); + } + + /** + * @covers \edd_get_discounts() + */ + public function test_edd_get_discounts() { + $found_discounts = edd_get_discounts( array( + 'posts_per_page' => 3, + ) ); + + $this->assertTrue( 3 === count( $found_discounts ) ); + } + + /** + * @covers _edd_discounts_bc_wp_count_posts() + */ + public function test_edd_discounts_bc_wp_count_posts() { + $counts = wp_count_posts( 'edd_discount' ); + + $this->assertEquals( 3, (int) $counts->active ); + $this->assertEquals( 0, (int) $counts->inactive ); + } +} diff --git a/tests/downloads/tests-download-functions.php b/tests/downloads/tests-download-functions.php new file mode 100644 index 00000000000..5ba54c29c9b --- /dev/null +++ b/tests/downloads/tests-download-functions.php @@ -0,0 +1,62 @@ +simple_download = Helpers\EDD_Helper_Download::create_simple_download(); + $this->variable_download = Helpers\EDD_Helper_Download::create_variable_download(); + } + + public function tearDown(): void { + parent::tearDown(); + + Helpers\EDD_Helper_Download::delete_download( $this->simple_download->ID ); + Helpers\EDD_Helper_Download::delete_download( $this->variable_download->ID ); + } + + /** + * @covers edd_get_download_name + */ + public function test_get_download_name_simple_download_returns_name() { + $name = edd_get_download_name( $this->simple_download->ID ); + + $this->assertSame( 'Test Download Product', $name ); + } + + /** + * @covers edd_get_download_name + */ + public function test_get_download_name_variable_download_returns_name() { + $name = edd_get_download_name( $this->variable_download->ID, 0 ); + + $this->assertSame( 'Variable Test Download Product — Simple', $name ); + } + + /** + * @covers edd_get_download_name + */ + public function test_get_download_name_invalid_download_returns_false() { + $this->assertFalse( edd_get_download_name( 54657 ) ); + } + + /** + * @covers edd_get_download_name + */ + public function test_get_download_name_invalid_download_id_returns_false() { + $this->assertFalse( edd_get_download_name( 'test' ) ); + } +} diff --git a/tests/downloads/tests-download-sales-earnings.php b/tests/downloads/tests-download-sales-earnings.php new file mode 100644 index 00000000000..9e3a38e9fc7 --- /dev/null +++ b/tests/downloads/tests-download-sales-earnings.php @@ -0,0 +1,476 @@ +variable_download = Helpers\EDD_Helper_Download::create_variable_download(); + $this->simple_download = Helpers\EDD_Helper_Download::create_simple_download(); + } + + public function tearDown(): void { + + parent::tearDown(); + + Helpers\EDD_Helper_Download::delete_download( $this->variable_download->ID ); + Helpers\EDD_Helper_Download::delete_download( $this->simple_download->ID ); + } + + public function test_simple_download_no_earnings_sales() { + edd_recalculate_download_sales_earnings( $this->simple_download->ID ); + $download = edd_get_download( $this->simple_download->ID ); + + $this->assertEquals( 0.00, $download->get_earnings() ); + $this->assertEquals( 0, $download->get_sales() ); + $this->assertEmpty( get_post_meta( $download->ID, '_edd_download_gross_sales', true ) ); + $this->assertEmpty( get_post_meta( $download->ID, '_edd_download_gross_earnings', true ) ); + } + + public function test_download_sales_net_gross_equal_after_one_sale() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 20, + ) + ); + + edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( get_post_meta( $download->ID, '_edd_download_gross_sales', true ), $download->get_sales() ); + + edd_delete_order( $order_id ); + } + + public function test_download_earnings_net_gross_equal_after_one_sale() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 20, + ) + ); + + edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( (float) get_post_meta( $download->ID, '_edd_download_gross_earnings', true ), (float) $download->get_earnings() ); + + edd_delete_order( $order_id ); + } + + public function test_download_earnings_net_gross_not_equal_after_full_refund() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 20, + ) + ); + + edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + + $refund_id = edd_refund_order( $order_id ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( 0, $download->get_earnings() ); + $this->assertNotEquals( $download->earnings, get_post_meta( $download->ID, '_edd_download_gross_earnings', true ) ); + + edd_delete_order( $order_id ); + edd_delete_order( $refund_id ); + } + + public function test_download_sales_net_gross_not_equal_after_full_refund() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 20, + ) + ); + + $order_item = edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + + $refund_id = edd_refund_order( $order_id ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( 0, $download->get_sales() ); + $this->assertNotEquals( $download->sales, get_post_meta( $download->ID, '_edd_download_gross_sales', true ) ); + + edd_delete_order( $order_id ); + edd_delete_order( $refund_id ); + } + + public function test_download_earnings_net_gross_not_equal_after_partial_refund() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 20, + ) + ); + + $order_item_id = edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + + $to_refund = array(); + $order = edd_get_order( $order_id ); + foreach ( $order->items as $order_item ) { + if ( $order_item->total > 0 ) { + $to_refund[] = array( + 'order_item_id' => $order_item->id, + 'subtotal' => ( $order_item->subtotal - $order_item->discount ) / 2, + 'tax' => $order_item->tax / 2, + 'total' => $order_item->total / 2, + ); + } + } + + $refund_id = edd_refund_order( $order->id, $to_refund ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( 10, $download->get_earnings() ); + $this->assertNotEquals( $download->earnings, get_post_meta( $download->ID, '_edd_download_gross_earnings', true ) ); + + edd_delete_order( $order_id ); + edd_delete_order( $refund_id ); + } + + public function test_download_sales_net_gross_equal_after_partial_refund() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 20, + ) + ); + + $order_item_id = edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + $order_item = edd_get_order_item( $order_item_id ); + + $to_refund = array(); + $order = edd_get_order( $order_id ); + foreach ( $order->items as $order_item ) { + if ( $order_item->total > 0 ) { + $to_refund[] = array( + 'order_item_id' => $order_item->id, + 'subtotal' => ( $order_item->subtotal - $order_item->discount ) / 2, + 'tax' => $order_item->tax / 2, + 'total' => $order_item->total / 2, + ); + } + } + + $refund_id = edd_refund_order( $order->id, $to_refund ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( $download->sales, get_post_meta( $download->ID, '_edd_download_gross_sales', true ) ); + + edd_delete_order( $order_id ); + edd_delete_order( $refund_id ); + } + + public function test_download_sales_net_gross_quantities_after_partial_refund() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 200, + 'total' => 200, + ) + ); + + $order_item_id = edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 200, + 'total' => 200, + 'quantity' => 10, + ) + ); + + $to_refund = array(); + $order = edd_get_order( $order_id ); + foreach ( $order->items as $order_item ) { + if ( $order_item->total > 0 ) { + $to_refund[] = array( + 'order_item_id' => $order_item->id, + 'subtotal' => ( $order_item->subtotal - $order_item->discount ) / 2, + 'tax' => $order_item->tax / 2, + 'total' => $order_item->total / 2, + 'quantity' => $order_item->quantity / 2, + ); + } + } + + $refund_id = edd_refund_order( $order->id, $to_refund ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( 5, $download->get_sales() ); + $this->assertNotEquals( $download->sales, get_post_meta( $download->ID, '_edd_download_gross_sales', true ) ); + + edd_delete_order( $order_id ); + edd_delete_order( $refund_id ); + } + + public function test_download_sales_gross_less_net_equals_discount() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 15, + ) + ); + + $order_item_id = edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 15, + 'quantity' => 1, + 'discount' => 5, + ) + ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( 5, get_post_meta( $download->ID, '_edd_download_gross_earnings', true ) - $download->earnings ); + + edd_delete_order( $order_id ); + } + + public function test_download_earnings_gross_equals_net_with_positive_fee() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 25, + ) + ); + + $order_item_id = edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + + $order_item_adjustment = edd_add_order_adjustment( + array( + 'object_id' => $order_item_id, + 'object_type' => 'order_item', + 'subtotal' => 5, + 'total' => 5, + ) + ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( (float) $download->earnings, (float) get_post_meta( $download->ID, '_edd_download_gross_earnings', true ) ); + + edd_delete_order( $order_id ); + } + + public function test_download_earnings_gross_minus_net_equals_negative_fee() { + $order_id = edd_add_order( + array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 15, + ) + ); + + $order_item_id = edd_add_order_item( + array( + 'order_id' => $order_id, + 'product_id' => $this->simple_download->ID, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'total' => 20, + 'quantity' => 1, + ) + ); + + $order_item_adjustment = edd_add_order_adjustment( + array( + 'object_id' => $order_item_id, + 'object_type' => 'order_item', + 'subtotal' => -5.00, + 'total' => -5.00, + ) + ); + + $download = edd_get_download( $this->simple_download->ID ); + $this->assertEquals( 5, get_post_meta( $download->ID, '_edd_download_gross_earnings', true ) - $download->earnings ); + + edd_delete_order( $order_id ); + } +} diff --git a/tests/downloads/tests-downloads.php b/tests/downloads/tests-downloads.php new file mode 100644 index 00000000000..7459d6a9d78 --- /dev/null +++ b/tests/downloads/tests-downloads.php @@ -0,0 +1,343 @@ +_post = Helpers\EDD_Helper_Download::create_variable_download(); + } + + public function tearDown(): void { + + parent::tearDown(); + + Helpers\EDD_Helper_Download::delete_download( $this->_post->ID ); + + } + + public function test_get_download() { + $out = edd_get_download( $this->_post->ID ); + + $this->assertObjectHasAttribute( 'ID', $out ); + $this->assertObjectHasAttribute( 'post_title', $out ); + $this->assertObjectHasAttribute( 'post_type', $out ); + + $this->assertEquals( $out->post_type, $this->_post->post_type ); + } + + public function test_get_download_null() { + $out = edd_get_download( 999999999 ); + + $this->assertSame( null, $out ); + } + + public function test_get_download_by_name() { + $out = edd_get_download( $this->_post->post_name ); + + $this->assertObjectHasAttribute( 'ID', $out ); + $this->assertObjectHasAttribute( 'post_title', $out ); + $this->assertObjectHasAttribute( 'post_type', $out ); + + $this->assertEquals( 'Variable Test Download Product', $out->get_name() ); + } + + public function test_get_download_by_name_null() { + $out = edd_get_download( 'TESTING BY NAME NULL' ); + + $this->assertSame( null, $out ); + } + + public function test_edd_get_download_by() { + + $download = edd_get_download_by( 'id', $this->_post->ID ); + $this->assertSame( $this->_post->ID, $download->ID ); + + $download = edd_get_download_by( 'sku', 'sku_0012' ); + $this->assertSame( $this->_post->ID, $download->ID ); + + $download = edd_get_download_by( 'slug', 'variable-test-download-product' ); + $this->assertSame( $this->_post->ID, $download->ID ); + + $downoad = edd_get_download_by( 'name', 'Variable Test Download Product' ); + $this->assertSame( $this->_post->ID, $download->ID ); + + } + + public function test_edd_download() { + + // Verify passing nothing gives us an empty download + $download = new \EDD_Download; + $this->assertEquals( 0, $download->ID ); + + // Create a Download + $args = array( + 'post_title' => 'Test Create Download' + ); + $download2 = new \EDD_Download; + $this->assertEquals( 0, $download2->ID ); + + $download2->create( $args ); + + $this->assertNotEmpty( $download2->ID ); + $this->assertEquals( 'download', $download2->post_type ); + $this->assertEquals( 'draft', $download2->post_status ); + $this->assertEquals( 0, $download2->sales ); + $this->assertEquals( 0.00, $download2->earnings ); + $this->assertFalse( $download2->has_variable_prices() ); + $this->assertEmpty( $download2->prices ); + + // Retrieve a previously created download + $prices = array( + array( + 'name' => 'Simple', + 'amount' => 20 + ), + array( + 'name' => 'Advanced', + 'amount' => 100 + ) + ); + $files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0 + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all' + ) + ); + $download3 = new \EDD_Download( $this->_post->ID ); + $this->assertNotEmpty( $download3->ID ); + $this->assertEquals( $this->_post->ID, $download3->ID ); + $this->assertEquals( 'download', $download3->post_type ); + $this->assertEquals( 'publish', $download3->post_status ); + $this->assertEquals( 0.00, $download3->price ); + $this->assertEquals( 0.00, $download3->get_price() ); + $this->assertTrue( $download3->has_variable_prices() ); + $this->assertNotEmpty( $download3->prices ); + $this->assertEquals( $prices, $download3->prices ); + $this->assertEquals( $prices, $download3->get_prices() ); + $this->assertEquals( 6, $download3->sales ); + $this->assertEquals( 120.00, $download3->earnings ); + $this->assertNotEmpty( $download3->files ); + $this->assertEquals( $files, $download3->files ); + $this->assertEquals( $files, $download3->get_files() ); + $this->assertEquals( 20, $download3->file_download_limit ); + $this->assertEquals( 20, $download3->get_file_download_limit() ); + $this->assertEquals( 0, $download3->get_file_price_condition( 0 ) ); + $this->assertEquals( 'all', $download3->get_file_price_condition( 1 ) ); + $this->assertEquals( 'default', $download3->get_type() ); + $this->assertFalse( $download3->is_bundled_download() ); + $this->assertIsArray( $download3->get_bundled_downloads() ); + $this->assertIsString( $download3->get_notes() ); + $this->assertIsString( $download3->notes ); + $this->assertEquals( 'Purchase Notes', $download3->get_notes() ); + $this->assertEquals( 'add_to_cart', $download3->get_button_behavior() ); + $this->assertFalse( $download3->is_free() ); + $this->assertFalse( $download3->is_free( 0 ) ); + $this->assertFalse( $download3->is_free( 1 ) ); + + update_post_meta( $download3->ID, '_variable_pricing', false ); + $download4 = new \EDD_Download( $download3->ID ); + $this->assertEmpty( $download4->prices ); + + // Test the magic __get function + $this->assertEquals( 20, $download3->file_download_limit ); + $this->assertTrue( is_wp_error( $download3->__get( 'asdf') ) ); + + } + + public function test_can_purchase() { + $download = new \EDD_Download( $this->_post->ID ); + $this->assertTrue( $download->can_purchase() ); + + add_filter( 'edd_can_purchase_download', '__return_false' ); + $this->assertFalse( $download->can_purchase() ); + remove_filter( 'edd_can_purchase_download', '__return_false' ); + + $download->post_status = 'draft'; + wp_set_current_user( 0 ); + $this->assertFalse( $download->can_purchase() ); + + add_filter( 'edd_can_purchase_download', '__return_true' ); + $this->assertTrue( $download->can_purchase() ); + remove_filter( 'edd_can_purchase_download', '__return_true' ); + } + + public function test_download_price() { + // This is correct and should equal 0.00 because this download uses variable pricing + $this->assertEquals( 0.00, edd_get_download_price( $this->_post->ID ) ); + } + + public function test_variable_pricing() { + $out = edd_get_variable_prices( $this->_post->ID ); + $this->assertNotEmpty( $out ); + foreach ( $out as $var ) { + $this->assertArrayHasKey( 'name', $var ); + $this->assertArrayHasKey( 'amount', $var ); + + if ( $var['name'] == 'Simple' ) { + $this->assertEquals( 20, $var['amount'] ); + } + + if ( $var['name'] == 'Advanced' ) { + $this->assertEquals( 100, $var['amount'] ); + } + } + } + + public function test_variable_pricing_edd_price() { + $out = edd_get_variable_prices( $this->_post->ID ); + $price_text = edd_price( $this->_post->ID, false, 0); + $this->assertStringContainsString( '$20.00', $price_text, 'Variable Price edd_price incorrect' ); + } + + public function test_has_variable_prices() { + $this->assertTrue( edd_has_variable_prices( $this->_post->ID ) ); + } + + public function test_default_variable_price() { + $this->assertEquals( 0, edd_get_default_variable_price( $this->_post->ID ) ); + + update_post_meta( $this->_post->ID, '_edd_default_price_id', 1 ); + $this->assertEquals( 1, edd_get_default_variable_price( $this->_post->ID ) ); + } + + public function test_get_price_option_name() { + $this->assertEquals( 'Simple', edd_get_price_option_name( $this->_post->ID, 0 ) ); + $this->assertEquals( 'Advanced', edd_get_price_option_name( $this->_post->ID, 1 ) ); + } + + public function test_get_lowest_price_option() { + $this->assertEquals( 20, edd_get_lowest_price_option( $this->_post->ID ) ); + } + + public function test_get_highest_price_option() { + $this->assertEquals( 100, edd_get_highest_price_option( $this->_post->ID ) ); + } + + public function test_price_range() { + $range = edd_price_range( $this->_post->ID ); + $expected = '$20.00 – $100.00'; + $this->assertIsString( $range ); + $this->assertEquals( $expected, $range ); + } + + public function test_single_price_option_mode() { + $this->assertTrue( edd_single_price_option_mode( $this->_post->ID ) ); + } + + public function test_download_type() { + $this->assertEquals( 'default', edd_get_download_type( $this->_post->ID ) ); + } + + public function test_download_earnings() { + $download = new \EDD_Download( $this->_post->ID ); + + $this->assertEquals( 120, edd_get_download_earnings_stats( $this->_post->ID ) ); + } + + public function test_download_sales() { + $this->assertEquals( 6, edd_get_download_sales_stats( $this->_post->ID ) ); + } + + public function test_get_download_files() { + $out = edd_get_download_files( $this->_post->ID ); + + foreach ( $out as $file ) { + $this->assertArrayHasKey( 'name', $file ); + $this->assertArrayHasKey( 'file', $file ); + $this->assertArrayHasKey( 'condition', $file ); + + if ( $file['name'] == 'File 1' ) { + $this->assertEquals( 'http://localhost/file1.jpg', $file['file'] ); + $this->assertEquals( 0, $file['condition'] ); + } + + if ( $file['name'] == 'File 2' ) { + $this->assertEquals( 'http://localhost/file2.jpg', $file['file'] ); + $this->assertEquals( 'all', $file['condition'] ); + } + } + } + + public function test_get_file_download_limit() { + $this->assertEquals( 20, edd_get_file_download_limit( $this->_post->ID ) ); + } + + public function test_get_file_download_limit_override() { + $this->assertEquals( 1, edd_get_file_download_limit_override( $this->_post->ID, 1 ) ); + } + + public function test_is_file_at_download_limit() { + $this->assertFalse( edd_is_file_at_download_limit( $this->_post->ID, 1, 1 ) ); + } + + public function test_get_file_price_condition() { + $this->assertEquals( 0, edd_get_file_price_condition( $this->_post->ID, 0 ) ); + $this->assertEquals( 'all', edd_get_file_price_condition( $this->_post->ID, 1 ) ); + } + + public function test_get_product_notes() { + $this->assertEquals( 'Purchase Notes', edd_get_product_notes( $this->_post->ID ) ); + } + + public function test_get_download_type() { + $this->assertEquals( 'default', edd_get_download_type( $this->_post->ID ) ); + } + + public function test_get_download_is_bundle() { + $this->assertFalse( edd_is_bundled_product( $this->_post->ID ) ); + } + + public function test_item_quantities_not_disabled() { + $this->assertFalse( edd_download_quantities_disabled( $this->_post->ID ) ); + } + + public function test_item_quantities_disabled() { + + update_post_meta( $this->_post->ID, '_edd_quantities_disabled', 1 ); + + $this->assertTrue( edd_download_quantities_disabled( $this->_post->ID ) ); + } + + public function test_bundled_products_conditions_price_id_has_one_download() { + $bundle = Helpers\EDD_Helper_Download::create_variable_bundled_download(); + $bundled_downloads = edd_get_bundled_products( $bundle->ID, 2 ); + + $this->assertEquals( 1, count( $bundled_downloads ) ); + } + + public function test_bundled_products_conditions_no_price_id_has_two_downloads() { + $bundle = Helpers\EDD_Helper_Download::create_variable_bundled_download(); + $bundled_downloads = edd_get_bundled_products( $bundle->ID ); + + $this->assertEquals( 2, count( $bundled_downloads ) ); + } + + public function test_download_has_variable_pricing_but_no_prices() { + $download = Helpers\EDD_Helper_Download::create_simple_download(); + update_post_meta( $download->ID, '_variable_pricing', true ); + update_post_meta( $download->ID, 'edd_variable_prices', array( 0 => '' ) ); + + $this->assertTrue( edd_has_variable_prices( $download->ID ) ); + $this->assertEmpty( edd_get_variable_prices( $download->ID ) ); + } +} diff --git a/tests/downloads/tests-process-download.php b/tests/downloads/tests-process-download.php new file mode 100644 index 00000000000..89fe6ff4665 --- /dev/null +++ b/tests/downloads/tests-process-download.php @@ -0,0 +1,313 @@ + 'complete', + 'type' => 'sale', + 'date_completed' => EDD()->utils->date( 'now' )->toDateTimeString(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 20, + 'total' => 20, + ) ); + + edd_add_order_item( array( + 'order_id' => $order_id, + 'product_id' => 1, + 'product_name' => 'Simple Download', + 'price_id' => 0, + 'status' => 'complete', + 'amount' => 10, + 'subtotal' => 10, + 'total' => 10, + 'quantity' => 1, + ) ); + + edd_add_order_item( array( + 'order_id' => $order_id, + 'product_id' => 2, + 'product_name' => 'Simple Download', + 'status' => 'complete', + 'amount' => 10, + 'subtotal' => 10, + 'total' => 10, + 'quantity' => 1, + ) ); + + self::$order = edd_get_order( $order_id ); + } + + /** + * @covers ::edd_set_requested_file_scheme + */ + public function test_set_scheme() { + $home_url = get_home_url(); + $file = trailingslashit( $home_url ) . 'test-file.jpg'; + $https_file = str_replace( 'http://', 'https://', $file ); + + $this->assertEquals( $file, edd_set_requested_file_scheme( $https_file, array(), '' ) ); + } + + /** + * If specifying a product ID that doesn't exist in the order, files cannot be downloaded + * + * @covers ::edd_order_grants_access_to_download_files + */ + public function test_order_with_non_existent_product_id_should_return_false() { + $this->assertFalse( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 500 + ) ) ); + } + + /** + * Fully `complete` order should be able to download files + * + * @covers ::edd_order_grants_access_to_download_files + */ + public function test_order_with_complete_item_should_return_true() { + // Not specifying price ID + $this->assertTrue( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 1 + ) ) ); + + // Specifying price ID + $this->assertTrue( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 1, + 'price_id' => 0, + ) ) ); + } + + public function test_order_with_null_price_id_should_return_true() { + // Not specifying price ID + $this->assertTrue( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 2, + 'price_id' => null, + ) ) ); + } + + /** + * If specifying a price ID that was not purchased in this order, files cannot be downloaded + * + * @covers ::edd_order_grants_access_to_download_files + */ + public function test_order_with_wrong_price_id_should_return_false() { + $this->assertFalse( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 1, + 'price_id' => 2 + ) ) ); + } + + /** + * If an order item has been refunded, the associated files can no longer be downloaded. + * + * @covers ::edd_order_grants_access_to_download_files + */ + public function test_refunded_item_in_partially_refunded_order_should_return_false() { + // Refund the order item for product ID 2. + foreach ( self::$order->get_items() as $item ) { + if ( 2 == $item->product_id ) { + edd_refund_order( $item->order_id, array( + array( + 'order_item_id' => $item->id, + 'subtotal' => $item->subtotal, + 'tax' => $item->tax + ) + ) ); + } + } + + // Fetch original order. + $order = edd_get_order( self::$order->id ); + + $this->assertSame( 'partially_refunded', $order->status ); + + $this->assertFalse( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 2 + ) ) ); + } + + /** + * If an order has been partially refunded, the item that's still `complete` can still be downloaded. + * + * @covers ::edd_order_grants_access_to_download_files + */ + public function test_complete_item_in_partially_refunded_order_should_return_true() { + $this->assertTrue( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 1 + ) ) ); + } + + /** + * If an order has been fully refunded, files cannot be downloaded for any of the items + * + * @covers ::edd_order_grants_access_to_download_files + */ + public function test_fully_refunded_order_should_return_false() { + // Refund all items. + edd_refund_order( self::$order->id ); + + // Fetch original order. + $order = edd_get_order( self::$order->id ); + + $this->assertSame( 'refunded', $order->status ); + + $this->assertFalse( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 1 + ) ) ); + + $this->assertFalse( edd_order_grants_access_to_download_files( array( + 'order_id' => self::$order->id, + 'product_id' => 2 + ) ) ); + } + + /** + * If no order exists with the provided ID, files cannot be downloaded + * + * @covers ::edd_order_grants_access_to_download_files + */ + public function test_non_existent_order_number_should_return_false() { + // Generate a random order ID until we get one that doesn't exist. + $order_id = 12345; + do { + $order_id++; + $order = edd_get_order( $order_id ); + } while ( $order instanceof \EDD\Orders\Order ); + + $this->assertFalse( edd_order_grants_access_to_download_files( array( + 'order_id' => $order_id, + 'product_id' => 1 + ) ) ); + } + + /** + * @covers edd_order_grants_access_to_download_files + */ + public function test_bundled_download_should_be_deliverable() { + $bundled_product = Helpers\EDD_Helper_Download::create_bundled_download(); + $bundled_order_item = edd_add_order_item( + array( + 'order_id' => self::$order->id, + 'product_id' => $bundled_product->ID, + 'subtotal' => 9.99, + 'status' => 'complete', + ) + ); + + $this->assertTrue( + edd_order_grants_access_to_download_files( + array( + 'order_id' => self::$order->id, + 'product_id' => $bundled_product->ID, + ) + ) + ); + } + + public function test_file_download_token() { + $eddfile = '1:2:3:4'; + $ttl = current_time( 'timestamp' ) + HOUR_IN_SECONDS; + $file = 4; + + $args = array( + 'eddfile' => $eddfile, + 'ttl' => $ttl, + 'file' => $file, + ); + + $token = edd_get_download_token( add_query_arg( $args, site_url() ) ); + $args['token'] = $token; + + $url = add_query_arg( $args, site_url() ); + + $this->assertTrue( edd_validate_url_token( $url ) ); + } + + public function test_file_download_token_out_of_order() { + $eddfile = '1:2:3:4'; + $ttl = current_time( 'timestamp' ) + HOUR_IN_SECONDS; + $file = 4; + + $args = array( + 'eddfile' => $eddfile, + 'ttl' => $ttl, + 'file' => $file, + ); + + $token = edd_get_download_token( add_query_arg( $args, site_url() ) ); + + // Re-order the arguments to verify for #8851. + $new_args = array( + 'file' => $file, + 'ttl' => $ttl, + 'token' => $token, + 'eddfile' => $eddfile, + ); + + $url = add_query_arg( $new_args, site_url() ); + + $this->assertTrue( edd_validate_url_token( $url ) ); + } + + public function test_custom_parameters() { + + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + $order = edd_get_order( $payment_id ); + $download = Helpers\EDD_Helper_Download::create_simple_download(); + + add_filter( 'edd_get_download_file_url_args', function ( $args, $payment_id, $params ) { + $args['beta'] = 1; + + return $args; + }, 10, 3 ); + + add_filter( 'edd_url_token_allowed_params', function ( $args ) { + $args[] = 'beta'; + + return $args; + } ); + + $parts = parse_url( add_query_arg( array(), edd_get_download_file_url( $order, $order->email, '', $download->ID ) ) ); + wp_parse_str( $parts['query'], $query_args ); + $url = add_query_arg( $query_args, site_url() ); + + $this->assertTrue( edd_validate_url_token( $url ) ); + } +} diff --git a/tests/fees/tests-fees-orders.php b/tests/fees/tests-fees-orders.php new file mode 100644 index 00000000000..544fd8f7fec --- /dev/null +++ b/tests/fees/tests-fees-orders.php @@ -0,0 +1,125 @@ + 'region', + 'name' => 'US', + 'description' => 'TN', + 'amount' => 10, + ) + ); + + self::$download = \EDD\Tests\Helpers\EDD_Helper_Download::create_simple_download(); + } + + public static function tearDownAfterClass(): void { + parent::tearDownAfterClass(); + edd_update_option( 'enable_taxes', false ); + } + + public function test_fee_with_no_tax_has_no_tax() { + $fee = $this->build_order_and_get_fee( true ); + + $this->assertEquals( 0, $fee->tax ); + } + + public function test_fee_with_no_tax_subtotal_equals_total() { + $fee = $this->build_order_and_get_fee( true ); + + $this->assertEquals( $fee->subtotal, $fee->total ); + } + + public function test_fee_with_tax_has_tax() { + $fee = $this->build_order_and_get_fee( false ); + + $this->assertEquals( 0.2, $fee->tax ); + } + + public function test_fee_with_tax_total_includes_tax() { + $fee = $this->build_order_and_get_fee( false ); + + $this->assertEquals( 2.2, $fee->total ); + } + + private function build_order_and_get_fee( $no_tax = false ) { + $order_id = edd_build_order( $this->get_order_data( $no_tax ) ); + + $order_adjustments = edd_get_order_adjustments( + array( + 'object_id' => $order_id, + ) + ); + + return reset( $order_adjustments ); + } + + private function get_order_data( $no_tax = false ) { + return array( + 'price' => 24, + 'date' => '2023-06-12 13:41:53', + 'user_email' => 'test@edd.local', + 'purchase_key' => '5c73adfa5ebb0bc47de72a82df7f0ae9', + 'currency' => 'USD', + 'downloads' => array( + array( + 'id' => self::$download->ID, + 'options' => array(), + 'quantity' => 1, + ), + ), + 'user_info' => array( + 'id' => 1, + 'email' => 'test@edd.local', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'discount' => 'none', + 'address' => array( + 'line1' => '1234 Main Street', + 'city' => 'Chattanooga', + 'state' => 'TN', + 'country' => 'US', + 'zip' => '37403', + ), + ), + 'cart_details' => array( + array( + 'name' => edd_get_download_name( self::$download->ID ), + 'id' => self::$download->ID, + 'item_number' => array( + 'id' => self::$download->ID, + 'options' => array(), + 'quantity' => 1, + ), + 'item_price' => 20, + 'quantity' => 1, + 'discount' => 0, + 'subtotal' => 22, + 'tax' => 0.10, + 'fees' => array( + 'simple_shipping_1234' => array( + 'amount' => 2.00, + 'label' => 'Simple Download Fee', + 'no_tax' => $no_tax, + 'type' => 'fee', + 'download_id' => self::$download->ID, + ), + ), + 'price' => 24, + ), + ), + ); + } +} diff --git a/tests/fees/tests-fees.php b/tests/fees/tests-fees.php new file mode 100644 index 00000000000..339ee6fff51 --- /dev/null +++ b/tests/fees/tests-fees.php @@ -0,0 +1,572 @@ +factory->post->create( array( 'post_title' => 'Test Download', 'post_type' => 'download', 'post_status' => 'publish' ) ); + $this->_post = get_post( $post_id ); + + edd_add_to_cart( $this->_post->ID ); + + } + + public function test_adding_fee_legacy() { + + EDD()->session->set( 'edd_cart_fees', null ); + + //This is not using the $args array because it's for backwards compatibility. + EDD()->fees->add_fee( 10, 'Shipping Fee', 'shipping_fee' ); + + $expected = array( + 'shipping_fee' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => 0, + 'price_id' => NULL + ), + ); + + $this->assertEquals( $expected, EDD()->fees->get_fees( 'all' ) ); + } + + public function test_adding_fee() { + + EDD()->session->set( 'edd_cart_fees', null ); + + //Arbitrary fee test. + EDD()->fees->add_fee( array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'download_id' => $this->_post->ID, + 'id' => 'arbitrary_fee', + 'type' => 'item' + ) ); + + $expected = array( + 'arbitrary_fee' => array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'type' => 'item', + 'no_tax' => false + ) + ); + + $this->assertEquals( $expected, EDD()->fees->get_fees( 'all' ) ); + } + + public function test_adding_fee_no_cart_item() { + + EDD()->session->set( 'edd_cart_fees', null ); + + edd_empty_cart(); + + //Arbitrary fee test. + $this->assertFalse( EDD()->fees->add_fee( array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'download_id' => $this->_post->ID, + 'id' => 'arbitrary_fee', + ) ) ); + + } + + public function test_adding_fee_for_variable_price() { + + EDD()->session->set( 'edd_cart_fees', null ); + + //Test with variable price id attached to a fee. + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ); + + $expected = array( + 'shipping_fee_with_variable_price_id' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1 + ), + ); + + $this->assertEquals( $expected, EDD()->fees->get_fees( 'all' ) ); + } + + public function test_adding_fee_for_variable_price_not_in_cart() { + + EDD()->session->set( 'edd_cart_fees', null ); + + edd_empty_cart(); + + //Test with variable price id attached to a fee. + $this->assertFalse( EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ) ); + + edd_add_to_cart( $this->_post->ID, array( 'price_id' => 1 ) ); + $this->assertNotEmpty( EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ) ); + } + + public function test_adding_fees() { + + EDD()->session->set( 'edd_cart_fees', null ); + + //Add Legacy Fee + EDD()->fees->add_fee( 10, 'Shipping Fee', 'shipping_fee' ); + + //Add Normal Fee with variable price id for to a fee. + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ); + + //Add Normal fee + EDD()->fees->add_fee( array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'download_id' => $this->_post->ID, + 'id' => 'arbitrary_fee', + 'type' => 'item' + ) ); + + $expected = array( + //Legacy Fee + 'shipping_fee' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => 0, + 'price_id' => NULL + ), + //Normal Fee with Variable Price + 'shipping_fee_with_variable_price_id' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1 + ), + //Normal Fee + 'arbitrary_fee' => array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'type' => 'item', + 'no_tax' => false + ) + ); + + $this->assertEquals( $expected, EDD()->fees->get_fees( 'all' ) ); + } + + public function test_has_fees() { + + EDD()->session->set( 'edd_cart_fees', null ); + + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ); + + $this->assertTrue( EDD()->fees->has_fees() ); + } + + public function test_get_fee() { + + EDD()->session->set( 'edd_cart_fees', null ); + + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ); + + $expected = array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1 + ); + + $this->assertEquals( $expected, EDD()->fees->get_fee( 'shipping_fee_with_variable_price_id' ) ); + + $fee = EDD()->fees->get_fee( 'shipping_fee_with_variable_price_id' ); + + $this->assertEquals( '10.00', $fee['amount'] ); + $this->assertEquals( 'Shipping Fee (Small)', $fee['label'] ); + $this->assertEquals( 'fee', $fee['type'] ); + + } + + public function test_get_all_fees() { + + EDD()->session->set( 'edd_cart_fees', null ); + + //Add Legacy Fee + EDD()->fees->add_fee( 10, 'Shipping Fee', 'shipping_fee' ); + + //Add Normal Fee with variable price id for to a fee. + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ); + + //Add Normal fee + EDD()->fees->add_fee( array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'download_id' => $this->_post->ID, + 'id' => 'arbitrary_fee', + 'type' => 'item' + ) ); + + $expected = array( + //Legacy Fee + 'shipping_fee' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => 0, + 'price_id' => NULL + ), + //Normal Fee with Variable Price + 'shipping_fee_with_variable_price_id' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1 + ), + //Normal Fee + 'arbitrary_fee' => array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'type' => 'item', + 'no_tax' => false + ) + ); + + //Test getting all Fees + $this->assertEquals( $expected, EDD()->fees->get_fees( 'all' ) ); + + $expected = array( + //Legacy Fee + 'shipping_fee' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => 0, + 'price_id' => NULL + ), + //Normal Fee with Variable Price + 'shipping_fee_with_variable_price_id' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1 + ), + ); + + //Test getting all fees with the type set to 'fee' + $this->assertEquals( $expected, EDD()->fees->get_fees( 'fee' ) ); + + $expected = array( + //Normal Fee + 'arbitrary_fee' => array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'type' => 'item', + 'no_tax' => false + ) + ); + + // Test getting only fees with the type set to 'item' + $this->assertEquals( $expected, EDD()->fees->get_fees( 'item' ) ); + + $expected = array( + //Normal Fee with Variable Price + 'shipping_fee_with_variable_price_id' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1 + ), + ); + + // Test getting download-specific fees + $this->assertEquals( $expected, EDD()->fees->get_fees( 'fee', $this->_post->ID ) ); + + } + + public function test_total_fees() { + + EDD()->session->set( 'edd_cart_fees', null ); + + //Add Normal Fee + EDD()->fees->add_fee( array( + 'amount' => '20.00', + 'label' => 'Tax Fee', + 'download_id' => NULL, + 'id' => 'arbitrary_fee_one', + 'type' => 'item' + ) ); + + //Add a variable price fee + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id_one', + 'type' => 'fee' + ) ); + + //Add another variable price fee + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Medium)', + 'download_id' => $this->_post->ID, + 'price_id' => 2, + 'id' => 'shipping_fee_with_variable_price_id_two', + 'type' => 'fee' + ) ); + + //Add another normal Fee + EDD()->fees->add_fee( array( + 'amount' => '20.00', + 'label' => 'Arbitrary Fee', + 'download_id' => NULL, + 'id' => 'arbitrary_fee_two', + 'type' => 'item' + ) ); + + //Test adding up all the fees + $this->assertEquals( 60, EDD()->fees->total() ); + + //Test getting the total of fees that match the post ID passed + $this->assertEquals( 20, EDD()->fees->total( $this->_post->ID ) ); + + //Test the string value of the fees total + $this->assertEquals( '60.00', EDD()->fees->total() ); + } + + public function test_record_fee() { + + EDD()->session->set( 'edd_cart_fees', null ); + + //Add Legacy Fee + EDD()->fees->add_fee( 10, 'Shipping Fee', 'shipping_fee' ); + + //Add Normal Fee with variable price id for to a fee. + EDD()->fees->add_fee( array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => 'shipping_fee_with_variable_price_id', + 'type' => 'fee' + ) ); + + //Add Normal fee + EDD()->fees->add_fee( array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'download_id' => $this->_post->ID, + 'id' => 'arbitrary_fee', + 'type' => 'item' + ) ); + + $expected = array( + 'fees' => array( + //Legacy Fee + 'shipping_fee' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => 0, + 'price_id' => NULL + ), + //Normal Fee with Variable Price + 'shipping_fee_with_variable_price_id' => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee (Small)', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1 + ), + //Normal Fee + 'arbitrary_fee' => array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'type' => 'item', + 'no_tax' => false + ) + ) + ); + + $actual = EDD()->fees->record_fees( $payment_meta = array(), $payment_data = array() ); + + $this->assertEquals( $expected, $actual ); + } + + public function test_fee_number_format_default() { + EDD()->session->set( 'edd_cart_fees', null ); + + EDD()->fees->add_fee( array( + 'amount' => '20', + 'label' => 'Arbitrary Item', + 'download_id' => $this->_post->ID, + 'id' => 'arbitrary_fee', + 'type' => 'item' + ) ); + + $expected = array( + 'arbitrary_fee' => array( + 'amount' => '20.00', + 'label' => 'Arbitrary Item', + 'type' => 'item', + 'no_tax' => false + ) + ); + + $this->assertEquals( $expected, EDD()->fees->get_fees( 'all' ) ); + } + + public function test_fee_number_format_decimal_filter() { + add_filter( 'edd_currency_decimal_count', array( $this, 'alter_decimal_filter' ), 10, 2 ); + + EDD()->session->set( 'edd_cart_fees', null ); + + EDD()->fees->add_fee( array( + 'amount' => '20', + 'label' => 'Arbitrary Item', + 'download_id' => $this->_post->ID, + 'id' => 'arbitrary_fee', + 'type' => 'item' + ) ); + + $expected = array( + 'arbitrary_fee' => array( + 'amount' => '20.000000', + 'label' => 'Arbitrary Item', + 'type' => 'item', + 'no_tax' => false + ) + ); + + $this->assertEquals( $expected, EDD()->fees->get_fees( 'all' ) ); + + remove_filter( 'edd_currency_decimal_count', array( $this, 'alter_decimal_filter' ), 10, 2 ); + } + + public function alter_decimal_filter() { + return 6; + } + + public function test_adding_fees_for_two_variable_prices() { + + EDD()->session->set( 'edd_cart_fees', null ); + edd_add_to_cart( $this->_post->ID, array( 'price_id' => 2 ) ); + + //Test with variable price id attached to a fee. + EDD()->fees->add_fee( + array( + 'amount' => 10.00, + 'label' => 'Shipping Fee', + 'download_id' => $this->_post->ID, + 'price_id' => 1, + 'id' => "simple_shipping_{$this->_post->ID}_1", + ) + ); + EDD()->fees->add_fee( + array( + 'amount' => 10.00, + 'label' => 'Shipping Fee', + 'download_id' => $this->_post->ID, + 'id' => "simple_shipping_{$this->_post->ID}_2", + 'price_id' => 2, + ) + ); + + $fee1 = array( + "simple_shipping_{$this->_post->ID}_1" => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee', + 'type' => 'fee', + 'no_tax' => false, + 'download_id' => $this->_post->ID, + 'price_id' => 1, + ), + ); + $fee2 = array( + "simple_shipping_{$this->_post->ID}_2" => array( + 'amount' => '10.00', + 'label' => 'Shipping Fee', + 'download_id' => $this->_post->ID, + 'type' => 'fee', + 'no_tax' => false, + 'price_id' => 2, + ), + ); + + $this->assertEquals( $fee1, EDD()->fees->get_fees( 'fee', $this->_post->ID, 1 ) ); + $this->assertEquals( $fee2, EDD()->fees->get_fees( 'fee', $this->_post->ID, 2 ) ); + $this->assertEquals( array_merge( $fee1, $fee2 ), EDD()->fees->get_fees( 'all' ) ); + } +} diff --git a/tests/gateways/tests-paypal.php b/tests/gateways/tests-paypal.php new file mode 100644 index 00000000000..85721b26511 --- /dev/null +++ b/tests/gateways/tests-paypal.php @@ -0,0 +1,683 @@ +order = edd_get_order( $order_id ); + } + + /** + * Builds a valid REST Request object that can be passed to the webhook event handler. + * + * @param string $payload JSON payload. + * + * @return \WP_REST_Request + */ + private function build_rest_request( $payload ) { + $request = new \WP_REST_Request( 'POST', Webhook_Handler::REST_NAMESPACE . '/' . Webhook_Handler::REST_ROUTE ); + $request->set_header( 'content-type', 'application/json' ); + $request->set_body( $payload ); + + return $request; + } + + /** + * Builds a payload for the PAYMENT.CAPTURE.COMPLETED event. + * + * @param array $args { + * + * @type float $amount + * @type string $currency_code + * @type string $transaction_id + * @type int $custom_id + * } + * + * @return string + */ + private function get_payment_capture_completed_payload( $args = array() ) { + $args = wp_parse_args( $args, array( + 'amount' => 120.00, + 'currency_code' => 'USD', + 'transaction_id' => self::TRANSACTION_ID, + 'custom_id' => $this->order->id + ) ); + + $args['amount'] = (float) $args['amount']; + + return '{ + "id": "WH-58D329510W468432D-8HN650336L201105X", + "create_time": "2019-02-14T21:50:07.940Z", + "resource_type": "capture", + "event_type": "PAYMENT.CAPTURE.COMPLETED", + "summary": "Payment completed for $ 120 USD", + "resource": { + "amount": { + "currency_code": "' . $args['currency_code'] . '", + "value": "' . $args['amount'] . '" + }, + "seller_protection": { + "status": "ELIGIBLE", + "dispute_categories": [ + "ITEM_NOT_RECEIVED", + "UNAUTHORIZED_TRANSACTION" + ] + }, + "update_time": "2019-02-14T21:49:58Z", + "create_time": "2019-02-14T21:49:58Z", + "final_capture": true, + "seller_receivable_breakdown": { + "gross_amount": { + "currency_code": "USD", + "value": "' . $args['amount'] . '" + }, + "paypal_fee": { + "currency_code": "' . $args['currency_code'] . '", + "value": "0.37" + }, + "net_amount": { + "currency_code": "' . $args['currency_code'] . '", + "value": "119.63" + } + }, + "custom_id": "' . $args['custom_id'] . '", + "links": [ + { + "href": "https://api.paypal.com/v2/payments/captures/' . $args['transaction_id'] . '", + "rel": "self", + "method": "GET" + }, + { + "href": "https://api.paypal.com/v2/payments/captures/' . $args['transaction_id'] . '/refund", + "rel": "refund", + "method": "POST" + }, + { + "href": "https://api.paypal.com/v2/payments/authorizations/7W5147081L658180V", + "rel": "up", + "method": "GET" + } + ], + "id": "' . $args['transaction_id'] . '", + "status": "COMPLETED" + }, + "links": [ + { + "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-58D329510W468432D-8HN650336L201105X", + "rel": "self", + "method": "GET", + "encType": "application/json" + }, + { + "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-58D329510W468432D-8HN650336L201105X/resend", + "rel": "resend", + "method": "POST", + "encType": "application/json" + } + ], + "event_version": "1.0", + "resource_version": "2.0" +}'; + } + + /** + * Builds a payload for the PAYMENT.CAPTURE.DENIED event. + * + * @param array $args + * + * @return string + */ + private function get_payment_capture_denied_payload( $args = array() ) { + $args = wp_parse_args( $args, array( + 'transaction_id' => self::TRANSACTION_ID + ) ); + + return '{ + "id": "WH-4SW78779LY2325805-07E03580SX1414828", + "create_time": "2019-02-14T22:20:08.370Z", + "resource_type": "capture", + "event_type": "PAYMENT.CAPTURE.DENIED", + "summary": "A $ 120.00 USD capture payment was denied", + "resource": { + "amount": { + "currency_code": "USD", + "value": "120.00" + }, + "seller_protection": { + "status": "ELIGIBLE", + "dispute_categories": [ + "ITEM_NOT_RECEIVED", + "UNAUTHORIZED_TRANSACTION" + ] + }, + "update_time": "2019-02-14T22:20:01Z", + "create_time": "2019-02-14T22:18:14Z", + "final_capture": true, + "seller_receivable_breakdown": { + "gross_amount": { + "currency_code": "USD", + "value": "120.00" + }, + "net_amount": { + "currency_code": "USD", + "value": "120.00" + } + }, + "links": [ + { + "href": "https://api.paypal.com/v2/payments/captures/' . $args['transaction_id'] . '", + "rel": "self", + "method": "GET" + }, + { + "href": "https://api.paypal.com/v2/payments/captures/' . $args['transaction_id'] . '/refund", + "rel": "refund", + "method": "POST" + }, + { + "href": "https://api.paypal.com/v2/payments/authorizations/' . $args['transaction_id'] . '", + "rel": "up", + "method": "GET" + } + ], + "id": "' . $args['transaction_id'] . '", + "status": "DECLINED" + }, + "links": [ + { + "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-4SW78779LY2325805-07E03580SX1414828", + "rel": "self", + "method": "GET", + "encType": "application/json" + }, + { + "href": "https://api.paypal.com/v1/notifications/webhooks-events/WH-4SW78779LY2325805-07E03580SX1414828/resend", + "rel": "resend", + "method": "POST", + "encType": "application/json" + } + ], + "event_version": "1.0", + "resource_version": "2.0" +}'; + } + + /** + * @covers \EDD\Gateways\PayPal\Webhooks\Events\Payment_Capture_Completed::process_event + */ + public function test_payment_capture_completed_marks_payment_complete() { + // Status should be pending at first. + $this->assertEquals( 'pending', $this->order->status ); + + $event = new Payment_Capture_Completed( $this->build_rest_request( $this->get_payment_capture_completed_payload( array( + 'amount' => 120, + 'currency_code' => 'USD' + ) ) ) ); + $event->handle(); + + // Refresh order object. + $order = edd_get_order( $this->order->id ); + $this->assertEquals( 'complete', $order->status ); + } + + /** + * @covers \EDD\Gateways\PayPal\Webhooks\Events\Payment_Capture_Completed::handle + */ + public function test_payment_capture_completed_with_mismatching_amount_throws_exception() { + $event = new Payment_Capture_Completed( $this->build_rest_request( $this->get_payment_capture_completed_payload( array( + 'amount' => 100.00 + ) ) ) ); + + if ( method_exists( $this, 'expectException' ) ) { + $this->expectException( 'Exception' ); + } + if ( method_exists( $this, 'expectExceptionMessage' ) ) { + $this->expectExceptionMessage( 'doesn\'t match payment amount' ); + } + + $event->handle(); + } + + /** + * @covers \EDD\Gateways\PayPal\Webhooks\Events\Payment_Capture_Completed::handle + */ + public function test_payment_capture_completed_with_mismatching_currency_throws_exception() { + $event = new Payment_Capture_Completed( $this->build_rest_request( $this->get_payment_capture_completed_payload( array( + 'currency_code' => 'GBP' + ) ) ) ); + + if ( method_exists( $this, 'expectException' ) ) { + $this->expectException( 'Exception' ); + } + if ( method_exists( $this, 'expectExceptionMessage' ) ) { + $this->expectExceptionMessage( 'Missing or invalid currency code' ); + } + + $event->handle(); + } + + /** + * @covers \EDD\Gateways\PayPal\Webhooks\Events\Payment_Capture_Completed::get_payment_from_capture + * @throws \Exception + */ + public function test_payment_capture_completed_with_correct_custom_id_but_wrong_transaction_id_throws_exception() { + $event = new Payment_Capture_Completed( $this->build_rest_request( $this->get_payment_capture_completed_payload( array( + 'transaction_id' => 'wrong' + ) ) ) ); + + if ( method_exists( $this, 'expectException' ) ) { + $this->expectException( 'Exception' ); + } + if ( method_exists( $this, 'expectExceptionMessage' ) ) { + $this->expectExceptionMessage( 'get_order_from_capture_object() - Transaction ID mismatch.' ); + } + + $event->handle(); + } + + /** + * @covers \EDD\Gateways\PayPal\Webhooks\Events\Payment_Capture_Denied::handle + * @throws \Exception + */ + public function test_payment_capture_denied_marks_payment_failed() { + $this->assertNotEquals( 'failed', $this->order->status ); + + $payload = $this->get_payment_capture_denied_payload(); + + $event = new Payment_Capture_Denied( $this->build_rest_request( $payload ) ); + $event->handle(); + + // Refresh order object. + $order = edd_get_order( $this->order->id ); + $this->assertEquals( 'failed', $order->status ); + } + + /** + * @covers \EDD\Gateways\PayPal\MerchantAccount::__construct() + */ + public function test_merchant_account_missing_merchant_id_throws_exception() { + $merchant = new MerchantAccount( array( + 'random_field' + ) ); + + if ( method_exists( $this, 'expectException' ) ) { + $this->expectException( '\EDD\Gateways\PayPal\Exceptions\MissingMerchantDetails' ); + } + + $merchant->validate(); + } + + /** + * @covers \EDD\Gateways\PayPal\MerchantAccount::__construct() + */ + public function test_merchant_account_missing_required_fields_throws_exception() { + $merchant = new MerchantAccount( array( + 'merchant_id' => 'merchant-123' + ) ); + + if ( method_exists( $this, 'expectException' ) ) { + $this->expectException( '\EDD\Gateways\PayPal\Exceptions\InvalidMerchantDetails' ); + } + + $merchant->validate(); + } + + /** + * @covers \EDD\Gateways\PayPal\MerchantAccount::is_account_ready + */ + public function test_merchant_account_not_ready_email_not_confirmed() { + $merchant = new MerchantAccount( array( + 'merchant_id' => 123, + 'payments_receivable' => true, + 'primary_email_confirmed' => false, + 'products' => array() + ) ); + + $this->assertFalse( $merchant->is_account_ready() ); + + $this->assertTrue( in_array( 'primary_email_confirmed', $merchant->get_errors()->get_error_codes() ) ); + } + + /** + * @covers \EDD\Gateways\PayPal\MerchantAccount::is_account_ready + */ + public function test_merchant_account_not_ready_payments_not_receivable() { + $merchant = new MerchantAccount( array( + 'merchant_id' => 123, + 'payments_receivable' => false, + 'primary_email_confirmed' => true, + 'products' => array() + ) ); + + $this->assertFalse( $merchant->is_account_ready() ); + + $this->assertTrue( in_array( 'payments_receivable', $merchant->get_errors()->get_error_codes() ) ); + } + + /** + * @covers \EDD\Gateways\PayPal\MerchantAccount::is_account_ready + */ + public function test_merchant_account_is_ready() { + $merchant = new MerchantAccount( array( + 'merchant_id' => 123, + 'payments_receivable' => true, + 'primary_email_confirmed' => true, + 'products' => array() + ) ); + + $this->assertTrue( $merchant->is_account_ready() ); + } + + /** + * @covers ::\EDD\Gateways\PayPal\_is_item_total_mismatch() + */ + public function test_item_total_mismatch_detected() { + $api_response = '{"name":"UNPROCESSABLE_ENTITY","details":[{"field":"\/purchase_units\/@reference_id==\'f4b51d81164ab4338fb9aa0911c94701\'\/amount\/breakdown\/item_total\/value","value":"0","issue":"ITEM_TOTAL_MISMATCH","description":"Should equal sum of (unit_amount * quantity) across all items for a given purchase_unit"}],"message":"The requested action could not be performed, semantically incorrect, or failed business validation.","debug_id":"123456789","links":[{"href":"https:\/\/developer.paypal.com\/docs\/api\/orders\/v2\/#error-ITEM_TOTAL_MISMATCH","rel":"information_link","method":"GET"}]}'; + + $this->assertTrue( \EDD\Gateways\PayPal\_is_item_total_mismatch( json_decode( $api_response ) ) ); + } + + /** + * @covers ::\EDD\Gateways\PayPal\_is_item_total_mismatch() + */ + public function test_item_total_mismatch_not_detected_on_success() { + $api_response = '{"id":"5GY4996685035442T","status":"CREATED","links":[{"href":"https:\/\/api.sandbox.paypal.com\/v2\/checkout\/orders\/5GY4996685035442T","rel":"self","method":"GET"},{"href":"https:\/\/www.sandbox.paypal.com\/checkoutnow?token=5GY4996685035442T","rel":"approve","method":"GET"},{"href":"https:\/\/api.sandbox.paypal.com\/v2\/checkout\/orders\/5GY4996685035442T","rel":"update","method":"PATCH"},{"href":"https:\/\/api.sandbox.paypal.com\/v2\/checkout\/orders\/5GY4996685035442T\/capture","rel":"capture","method":"POST"}]}'; + + $this->assertFalse( \EDD\Gateways\PayPal\_is_item_total_mismatch( json_decode( $api_response ) ) ); + } + + /** + * Product costs $10. + * Two added to cart (quantity: 2) + * 50% discount applied. + * + * @covers ::\EDD\Gateways\PayPal\get_order_purchase_units() + */ + public function test_purchase_units_with_quantities_and_discount() { + $purchase_data = array( + 'subtotal' => 20.00, + 'discount' => 10.00, + 'tax' => 0.00, + 'price' => 10.00, + 'cart_details' => array( + array( + 'id' => 1, + 'item_price' => 10.00, + 'quantity' => 2, // Quantity 2 + 'discount' => 10.00, // With 50% discount + 'subtotal' => 20.00, + 'tax' => 0.00, + 'price' => 10.00 + ) + ) + ); + + $payment_args = array( + 'purchase_key' => '123' + ); + + $expected = array( + 'reference_id' => '123', + 'amount' => array( + 'currency_code' => 'USD', + 'value' => '10.00', + 'breakdown' => array( + 'item_total' => array( + 'currency_code' => 'USD', + 'value' => '10.00', + ) + ), + ), + 'custom_id' => 1, + 'items' => array( + array( + 'name' => '1', + 'quantity' => 2, + 'unit_amount' => array( + 'currency_code' => 'USD', + 'value' => '5.00' + ) + ) + ) + ); + + $actual = \EDD\Gateways\PayPal\get_order_purchase_units( 1, $purchase_data, $payment_args ); + + $this->assertEqualSetsWithIndex( $expected, $actual[0] ); + } + + /** + * Product costs $10. + * $5 "handling fee" applied to the product. + * + * @covers ::\EDD\Gateways\PayPal\get_order_purchase_units() + */ + public function test_purchase_units_with_positive_fee() { + $purchase_data = array( + 'subtotal' => 15.00, + 'discount' => 0, + 'tax' => 0.00, + 'price' => 15.00, + 'cart_details' => array( + array( + 'id' => 1, + 'item_price' => 10.00, + 'quantity' => 1, + 'discount' => 0.00, + 'subtotal' => 15.00, + 'tax' => 0.00, + 'fees' => array( + 'handling' => array( + 'amount' => 5.00, + 'label' => 'Handling Fee', + 'no_tax' => 0, + 'type' => 'fee', + 'download_id' => 1, + ), + ), + 'price' => 15.00, + ), + ), + ); + + $payment_args = array( + 'purchase_key' => '123', + ); + + $expected = array( + 'reference_id' => '123', + 'amount' => array( + 'currency_code' => 'USD', + 'value' => '15.00', + 'breakdown' => array( + 'item_total' => array( + 'currency_code' => 'USD', + 'value' => '15.00', + ), + ), + ), + 'custom_id' => 1, + 'items' => array( + array( + 'name' => '1', + 'quantity' => 1, + 'unit_amount' => array( + 'currency_code' => 'USD', + 'value' => '15.00', + ), + ), + ), + ); + + $actual = \EDD\Gateways\PayPal\get_order_purchase_units( 1, $purchase_data, $payment_args ); + + $this->assertEqualSetsWithIndex( $expected, $actual[0] ); + } + + /** + * Product costs $10. + * $5 negative "fee" applied to the order. + * + * @covers ::\EDD\Gateways\PayPal\get_order_purchase_units() + */ + public function test_purchase_units_with_negative_fee() { + $purchase_data = array( + 'subtotal' => 10.00, + 'discount' => 0, + 'tax' => 0.00, + 'price' => 5.00, + 'cart_details' => array( + array( + 'id' => 1, + 'item_price' => 10.00, + 'quantity' => 1, + 'discount' => 0.00, + 'subtotal' => 10.00, + 'tax' => 0.00, + 'price' => 10.00, + ), + ), + 'fees' => array( + 'discount_fee' => array( + 'amount' => -5.00, + 'label' => 'Discount', + 'no_tax' => 0, + 'type' => 'fee', + ), + ), + ); + + $payment_args = array( + 'purchase_key' => '123', + ); + + $expected = array( + 'reference_id' => '123', + 'amount' => array( + 'currency_code' => 'USD', + 'value' => '5.00', + 'breakdown' => array( + 'item_total' => array( + 'currency_code' => 'USD', + 'value' => '10.00', + ), + 'discount' => array( + 'currency_code' => 'USD', + 'value' => '5.00', + ), + ), + ), + 'custom_id' => 1, + 'items' => array( + array( + 'name' => '1', + 'quantity' => 1, + 'unit_amount' => array( + 'currency_code' => 'USD', + 'value' => '10.00', + ), + ), + ), + ); + + $actual = \EDD\Gateways\PayPal\get_order_purchase_units( 1, $purchase_data, $payment_args ); + + $this->assertEqualSetsWithIndex( $expected, $actual[0] ); + } + + /** + * Product costs $10. + * 10% tax rate applied to the order. + * + * @covers ::\EDD\Gateways\PayPal\get_order_purchase_units() + */ + public function test_purchase_units_with_tax() { + $purchase_data = array( + 'subtotal' => 10.00, + 'discount' => 0, + 'tax' => 1.00, + 'tax_rate' => 0.1, + 'price' => 11.00, + 'cart_details' => array( + array( + 'id' => 1, + 'item_price' => 10.00, + 'quantity' => 1, + 'discount' => 0.00, + 'subtotal' => 10.00, + 'tax' => 1.00, + 'price' => 11.00, + ), + ), + ); + + $payment_args = array( + 'purchase_key' => '123', + ); + + $expected = array( + 'reference_id' => '123', + 'amount' => array( + 'currency_code' => 'USD', + 'value' => '11.00', + 'breakdown' => array( + 'item_total' => array( + 'currency_code' => 'USD', + 'value' => '10.00', + ), + 'tax_total' => array( + 'currency_code' => 'USD', + 'value' => '1.00', + ), + ), + ), + 'custom_id' => 1, + 'items' => array( + array( + 'name' => '1', + 'quantity' => 1, + 'unit_amount' => array( + 'currency_code' => 'USD', + 'value' => '10.00', + ), + ), + ), + ); + + $actual = \EDD\Gateways\PayPal\get_order_purchase_units( 1, $purchase_data, $payment_args ); + + $this->assertEqualSetsWithIndex( $expected, $actual[0] ); + } + +} diff --git a/tests/helpers/class-helper-discount.php b/tests/helpers/class-helper-discount.php new file mode 100644 index 00000000000..b3f4a669b44 --- /dev/null +++ b/tests/helpers/class-helper-discount.php @@ -0,0 +1,150 @@ + '20 Percent Off', + 'code' => '20OFF', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '20', + 'use_count' => 54, + 'max_uses' => 10, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start' => date( 'm/d/Y', time() ) . ' 00:00:00', + 'expiration' => date( 'm/d/Y', time() ) . ' 23:59:59', + ) ); + } + + /** + * Create a simple negative percentage discount. + * + * @since 2.3 + */ + public static function create_simple_negative_percent_discount() { + return edd_add_discount( array( + 'name' => 'Double Double', + 'code' => 'DOUBLE', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '-100', + 'use_count' => 54, + 'max_uses' => 10, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59' + ) ); + } + + /** + * Create a simple flat discount. + * + * @since 2.3 + */ + public static function create_simple_flat_discount() { + return edd_add_discount( array( + 'name' => '$10 Off', + 'code' => '10FLAT', + 'type' => 'flat', + 'status' => 'active', + 'amount' => '10', + 'max_uses' => 10, + 'use_count' => 0, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start' => date( 'm/d/Y', time() ) . ' 00:00:00', + 'expiration' => date( 'm/d/Y', time() ) . ' 23:59:59', + ) ); + } + + /** + * Create an expired flat discount. + * + * @since 3.0 + */ + public static function created_expired_flat_discount() { + return edd_add_discount( array( + 'name' => '$20 Off', + 'code' => '20FLAT', + 'type' => 'flat', + 'status' => 'expired', + 'amount' => '20', + 'max_uses' => 20, + 'use_count' => 0, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59' + ) ); + } + + /** + * Create legacy discount code. + * + * @since 3.0 + */ + public static function create_legacy_discount() { + + $discount_id = wp_insert_post( array( + 'post_type' => 'edd_discount', + 'post_title' => 'Legacy Discount', + 'post_status' => 'active' + ) ); + + $meta = array( + 'code' => 'OLD', + 'status' => 'active', + 'uses' => 10, + 'max_uses' => 20, + 'amount' => 20, + 'start' => '2000-01-01 00:00:00', + 'expiration' => '2051-12-31 23:59:59', + 'type' => 'percent', + 'min_price' => '10.50', + 'product_reqs' => array( 57 ), + 'product_condition' => 'all', + 'excluded_products' => array( 75 ), + 'is_not_global' => true, + 'is_single_use' => true + ); + + remove_filter( 'add_post_metadata', array( 'EDD\Compat\Discount', 'update_post_metadata' ), 99 ); + + foreach( $meta as $key => $value ) { + add_post_meta( $discount_id, '_edd_discount_' . $key, $value ); + } + + $compat = new EDD\Compat\Discount(); + + add_filter( 'add_post_metadata', array( $compat, 'update_post_metadata' ), 99, 5 ); + + return $discount_id; + } +} diff --git a/tests/helpers/class-helper-download.php b/tests/helpers/class-helper-download.php new file mode 100644 index 00000000000..b472080a6a0 --- /dev/null +++ b/tests/helpers/class-helper-download.php @@ -0,0 +1,292 @@ + 'Test Download Product', + 'post_name' => 'test-download-product', + 'post_type' => 'download', + 'post_status' => 'publish' + ) ); + + $_download_files = array( + array( + 'name' => 'Simple File 1', + 'file' => 'http://localhost/simple-file1.jpg', + 'condition' => 0 + ), + ); + + $meta = array( + 'edd_price' => '20.00', + '_variable_pricing' => 0, + 'edd_variable_prices' => false, + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 40, + '_edd_download_sales' => 2, + '_edd_download_limit_override_1' => 1, + 'edd_sku' => 'sku_0012' + ); + + foreach( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + return get_post( $post_id ); + + } + + /** + * Create a variable priced download. + * + * @since 2.3 + */ + public static function create_variable_download() { + + $post_id = wp_insert_post( array( + 'post_title' => 'Variable Test Download Product', + 'post_name' => 'variable-test-download-product', + 'post_type' => 'download', + 'post_status' => 'publish' + ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 20 + ), + array( + 'name' => 'Advanced', + 'amount' => 100 + ) + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0, + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all', + ), + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 120, + '_edd_download_sales' => 6, + '_edd_download_limit_override_1' => 1, + 'edd_sku' => 'sku_0012', + ); + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + return get_post( $post_id ); + + } + + /** + * Create a variable priced download. + * + * @since 2.3 + */ + public static function create_variable_download_with_multi_price_purchase() { + + $post_id = wp_insert_post( array( + 'post_title' => 'Variable Multi Test Download Product', + 'post_name' => 'variable-multi-test-download-product', + 'post_type' => 'download', + 'post_status' => 'publish' + ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 20 + ), + array( + 'name' => 'Advanced', + 'amount' => 100 + ), + array( + 'name' => 'Enterprise', + 'amount' => 200, + ), + array( + 'name' => 'Corporate', + 'amount' => 300, + ), + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0, + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all', + ), + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 120, + '_edd_download_sales' => 6, + '_edd_download_limit_override_1' => 1, + 'edd_sku' => 'sku_0013', + ); + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + return new \EDD_Download( $post_id ); + + } + + /** + * Create a bundled download. + * + * @since 2.3 + */ + public static function create_bundled_download() { + + $post_id = wp_insert_post( array( + 'post_title' => 'Bundled Test Download Product', + 'post_name' => 'bundled-test-download-product', + 'post_type' => 'download', + 'post_status' => 'publish' + ) ); + + $simple_download = EDD_Helper_Download::create_simple_download(); + $variable_download = EDD_Helper_Download::create_variable_download(); + + $meta = array( + 'edd_price' => '9.99', + '_variable_pricing' => 1, + 'edd_variable_prices' => false, + 'edd_download_files' => array(), + '_edd_bundled_products' => array( $simple_download->ID, $variable_download->ID ), + '_edd_download_limit' => 20, + 'edd_product_notes' => 'Bundled Purchase Notes', + '_edd_product_type' => 'bundle', + '_edd_download_earnings' => 120, + '_edd_download_sales' => 12, + ); + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + return get_post( $post_id ); + + } + + public static function create_variable_bundled_download() { + + $post_id = wp_insert_post( + array( + 'post_title' => 'Variable Bundled Test Download Product', + 'post_name' => 'variable-bundled-test-download-product', + 'post_type' => 'download', + 'post_status' => 'publish', + ) + ); + + $simple_download = self::create_simple_download(); + $variable_download = self::create_variable_download(); + $_variable_pricing = array( + array( + 'name' => 'Advanced', + 'amount' => 100, + ), + array( + 'name' => 'Enterprise', + 'amount' => 200, + ), + array( + 'name' => 'Corporate', + 'amount' => 300, + ), + ); + + $meta = array( + 'edd_price' => 9.99, + '_variable_pricing' => 1, + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array(), + '_edd_bundled_products' => array( + 1 => $simple_download->ID, + 2 => $variable_download->ID, + ), + '_edd_bundled_products_conditions' => array( + 1 => 1, + 2 => 2, + ), + '_edd_download_limit' => 20, + 'edd_product_notes' => 'Bundled Purchase Notes', + '_edd_product_type' => 'bundle', + '_edd_download_earnings' => 120, + '_edd_download_sales' => 12, + ); + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + return get_post( $post_id ); + } +} diff --git a/tests/helpers/class-helper-payment.php b/tests/helpers/class-helper-payment.php new file mode 100644 index 00000000000..6423cb4446a --- /dev/null +++ b/tests/helpers/class-helper-payment.php @@ -0,0 +1,673 @@ + 'none' + ); + + $args = wp_parse_args( $args, $defaults ); + + // Enable a few options + $edd_options['sequential_prefix'] = 'EDD-'; + + $simple_download = EDD_Helper_Download::create_simple_download(); + $variable_download = EDD_Helper_Download::create_variable_download(); + + /** Generate some sales */ + $user = get_userdata(1); + $user_info = array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'discount' => $args['discount'], + ); + + $download_details = array( + array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 0 + ) + ), + array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + ); + + $total = 0; + $simple_price = get_post_meta( $simple_download->ID, 'edd_price', true ); + $variable_prices = get_post_meta( $variable_download->ID, 'edd_variable_prices', true ); + $variable_item_price = $variable_prices[1]['amount']; // == $100 + + $total += $variable_item_price + $simple_price; + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => $simple_download->ID, + 'item_number' => array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $simple_price, + 'item_price' => $simple_price, + 'tax' => 0, + 'quantity' => 1 + ), + array( + 'name' => 'Variable Test Download', + 'id' => $variable_download->ID, + 'item_number' => array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $variable_item_price, + 'item_price' => $variable_item_price, + 'tax' => 0, + 'quantity' => 1 + ), + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending' + ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + $payment_id = edd_insert_payment( $purchase_data ); + + $transaction_id = 'EDD_ORDER'; + $payment = new \EDD_Payment( $payment_id ); + + $payment->transaction_id = $transaction_id; + $payment->save(); + + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ), $transaction_id ) ); + + return $payment_id; + + } + + /** + * Create a simple payment. + * + * @since 2.3 + */ + public static function create_simple_guest_payment() { + + global $edd_options; + + // Enable a few options + $edd_options['sequential_prefix'] = 'EDD-'; + + $simple_download = EDD_Helper_Download::create_simple_download(); + $variable_download = EDD_Helper_Download::create_variable_download(); + + /** Generate some sales */ + $user_info = array( + 'id' => 0, + 'email' => 'guest@example.org', + 'first_name' => 'Guest', + 'last_name' => 'User', + 'discount' => 'none' + ); + + $download_details = array( + array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 0 + ) + ), + array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + ); + + $total = 0; + $simple_price = get_post_meta( $simple_download->ID, 'edd_price', true ); + $variable_prices = get_post_meta( $variable_download->ID, 'edd_variable_prices', true ); + $variable_item_price = $variable_prices[1]['amount']; // == $100 + + $total += $variable_item_price + $simple_price; + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => $simple_download->ID, + 'item_number' => array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $simple_price, + 'item_price' => $simple_price, + 'tax' => 0, + 'quantity' => 1 + ), + array( + 'name' => 'Variable Test Download', + 'id' => $variable_download->ID, + 'item_number' => array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $variable_item_price, + 'item_price' => $variable_item_price, + 'tax' => 0, + 'quantity' => 1 + ), + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending' + ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + $payment_id = edd_insert_payment( $purchase_data ); + $transaction_id = 'EDD_GUEST_ORDER'; + edd_set_payment_transaction_id( $payment_id, $transaction_id ); + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ), $transaction_id ) ); + + return $payment_id; + + } + + /** + * Create a simple payment with tax. + * + * @since 2.3 + */ + public static function create_simple_payment_with_tax() { + + global $edd_options; + + // Enable a few options + $edd_options['sequential_prefix'] = 'EDD-'; + + $simple_download = EDD_Helper_Download::create_simple_download(); + $variable_download = EDD_Helper_Download::create_variable_download(); + + /** Generate some sales */ + $user = get_userdata(1); + $user_info = array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'discount' => 'none' + ); + + $download_details = array( + array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 0 + ) + ), + array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + ); + + $total = 0; + $simple_price = get_post_meta( $simple_download->ID, 'edd_price', true ); + $variable_prices = get_post_meta( $variable_download->ID, 'edd_variable_prices', true ); + $variable_item_price = $variable_prices[1]['amount']; // == $100 + + $total += $variable_item_price + $simple_price + 10 + 1; // Add our tax into the payment total + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => $simple_download->ID, + 'item_number' => array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $simple_price, + 'item_price' => $simple_price, + 'tax' => 1, + 'quantity' => 1 + ), + array( + 'name' => 'Variable Test Download', + 'id' => $variable_download->ID, + 'item_number' => array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $variable_item_price, + 'item_price' => $variable_item_price, + 'tax' => 10, + 'quantity' => 1 + ), + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending', + 'tax' => 11, + ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + $payment_id = edd_insert_payment( $purchase_data ); + + $transaction_id = 'EDD_ORDER_TAX'; + $payment = new \EDD_Payment( $payment_id ); + + $payment->transaction_id = $transaction_id; + $payment->save(); + + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ), $transaction_id ) ); + + return $payment_id; + + } + + /** + * Create a simple payment with a quantity of two + * + * @since 2.3 + */ + public static function create_simple_payment_with_quantity_tax() { + + global $edd_options; + + // Enable a few options + $edd_options['sequential_prefix'] = 'EDD-'; + + $simple_download = EDD_Helper_Download::create_simple_download(); + $variable_download = EDD_Helper_Download::create_variable_download(); + + /** Generate some sales */ + $user = get_userdata(1); + $user_info = array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'discount' => 'none' + ); + + $download_details = array( + array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 0 + ), + 'quantity' => 2, + ), + array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ), + 'quantity' => 2, + ), + ); + + $total = 0; + $simple_price = get_post_meta( $simple_download->ID, 'edd_price', true ); + $variable_prices = get_post_meta( $variable_download->ID, 'edd_variable_prices', true ); + $variable_item_price = $variable_prices[1]['amount']; // == $100 + + $total += $variable_item_price + $simple_price + 20 + 2; // Add our tax into the payment total + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => $simple_download->ID, + 'item_number' => array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $simple_price * 2, + 'item_price' => $simple_price, + 'tax' => 2, + 'quantity' => 2 + ), + array( + 'name' => 'Variable Test Download', + 'id' => $variable_download->ID, + 'item_number' => array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $variable_item_price * 2, + 'item_price' => $variable_item_price, + 'tax' => 20, + 'quantity' => 2 + ), + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending', + 'tax' => 22, + ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + $payment_id = edd_insert_payment( $purchase_data ); + + $transaction_id = 'EDD_ORDER_QUANTITY_TAX'; + $payment = new \EDD_Payment( $payment_id ); + + $payment->transaction_id = $transaction_id; + $payment->save(); + + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ), $transaction_id ) ); + + return $payment_id; + + } + + public static function create_simple_payment_with_fee() { + + global $edd_options; + + // Enable a few options + $edd_options['sequential_prefix'] = 'EDD-'; + + $simple_download = EDD_Helper_Download::create_simple_download(); + + add_filter( 'edd_cart_contents', function( $cart ) use ( $simple_download ) { + return array( 0 => array( + 'id' => $simple_download->ID, + 'options' => array(), + 'quantity' => 1 + ) ); + }, 10 ); + + add_filter( 'edd_item_quantities_enabled', '__return_true' ); + + /** Generate some sales */ + $user = get_userdata(1); + $user_info = array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'discount' => 'none' + ); + + $download_details = array( + array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 0 + ), + 'quantity' => 2, + ), + ); + + $total = 0; + $simple_price = get_post_meta( $simple_download->ID, 'edd_price', true ); + + $total += $simple_price + 2; // Add our tax into the payment total + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => $simple_download->ID, + 'item_number' => array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 1 + ), + ), + 'price' => $simple_price * 2, + 'item_price' => $simple_price, + 'tax' => 2, + 'quantity' => 2 + ), + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending', + 'tax' => 2, + ); + + $fee_args = array( + 'label' => 'Test Fee', + 'type' => 'fee', + 'amount' => 5, + ); + + EDD()->fees->add_fee( $fee_args ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + $payment_id = edd_insert_payment( $purchase_data ); + + $transaction_id = 'EDD_ORDER_FEE'; + $payment = new \EDD_Payment( $payment_id ); + + $payment->transaction_id = $transaction_id; + $payment->save(); + + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ), $transaction_id ) ); + + remove_all_filters( 'edd_cart_contents' ); + remove_filter( 'edd_item_quantities_enabled', '__return_true' ); + + return $payment_id; + + } + + /** + * Create a simple payment allowing a payment date to be passed + * + * @since 2.3 + */ + public static function create_simple_payment_with_date( $date ) { + + global $edd_options; + + // Enable a few options + $edd_options['sequential_prefix'] = 'EDD-'; + + $simple_download = EDD_Helper_Download::create_simple_download(); + $variable_download = EDD_Helper_Download::create_variable_download(); + + /** Generate some sales */ + $user = get_userdata(1); + $user_info = array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'discount' => 'none', + ); + + $download_details = array( + array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 0 + ) + ), + array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + ); + + $total = 0; + $simple_price = get_post_meta( $simple_download->ID, 'edd_price', true ); + $variable_prices = get_post_meta( $variable_download->ID, 'edd_variable_prices', true ); + $variable_item_price = $variable_prices[1]['amount']; // == $100 + + $total += $variable_item_price + $simple_price; + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => $simple_download->ID, + 'item_number' => array( + 'id' => $simple_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $simple_price, + 'item_price' => $simple_price, + 'tax' => 0, + 'quantity' => 1 + ), + array( + 'name' => 'Variable Test Download', + 'id' => $variable_download->ID, + 'item_number' => array( + 'id' => $variable_download->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'price' => $variable_item_price, + 'item_price' => $variable_item_price, + 'tax' => 0, + 'quantity' => 1 + ), + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => $date, + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending' + ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + $payment_id = edd_insert_payment( $purchase_data ); + + $transaction_id = 'EDD_ORDER_DATE'; + $payment = new \EDD_Payment( $payment_id ); + + $payment->transaction_id = $transaction_id; + $payment->date = $date; + $payment->save(); + + edd_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'easy-digital-downloads' ), $transaction_id ) ); + + return $payment_id; + + } + + public function fake_cart_contents_check() { + return true; + } + +} diff --git a/tests/helpers/shims.php b/tests/helpers/shims.php new file mode 100644 index 00000000000..445cd8dbc4c --- /dev/null +++ b/tests/helpers/shims.php @@ -0,0 +1,18 @@ +get_args( + array( + 'status' => 'expired', + 'expires' => date( 'Y-m-d 23:59:59', $yesterday ), + ) + ); + $expected = sprintf( 'Your license key expired on %s.', edd_date_i18n( $yesterday ) ); + $message = $this->get_message( $args ); + + $this->assertStringContainsString( $expected, $message ); + $this->assertStringContainsString( $args['license_key'], $message ); + } + + public function test_license_lifetime() { + $expected = 'License key never expires.'; + + $this->assertStringContainsString( $expected, $this->get_message( $this->get_args() ) ); + } + + public function test_license_revoked() { + $args = $this->get_args( + array( + 'status' => 'revoked', + ) + ); + $expected = 'Your license key has been disabled.'; + + $this->assertStringContainsString( $expected, $this->get_message( $args ) ); + } + + public function test_license_invalid_item_id() { + $args = $this->get_args( + array( + 'status' => 'invalid_item_id', + ) + ); + $expected = sprintf( 'This appears to be an invalid license key for %s.', $args['name'] ); + + $this->assertStringContainsString( $expected, $this->get_message( $args ) ); + } + + public function test_license_no_activations_left() { + $args = $this->get_args( + array( + 'status' => 'no_activations_left', + ) + ); + $expected = 'Your license key has reached its activation limit.'; + + $this->assertStringContainsString( $expected, $this->get_message( $args ) ); + } + + public function test_license_missing() { + $args = $this->get_args( + array( + 'status' => 'missing', + ) + ); + $expected = 'Invalid license.'; + + $this->assertStringContainsString( $expected, $this->get_message( $args ) ); + } + + public function test_license_expires_tomorrow() { + $tomorrow = strtotime( '+1 day', current_time( 'timestamp' ) ); + $args = $this->get_args( + array( + 'expires' => date( 'Y-m-d 23:59:59', $tomorrow ), + ) + ); + $expected = sprintf( 'Your license key expires soon! It expires on %s.', edd_date_i18n( $tomorrow ) ); + + $this->assertStringContainsString( $expected, $this->get_message( $args ) ); + } + + public function test_license_expires_next_month() { + $next_month = strtotime( '+32 days', current_time( 'timestamp' ) ); + $args = $this->get_args( + array( + 'expires' => date( 'Y-m-d 23:59:59', $next_month ), + ) + ); + $expected = sprintf( 'Your license key expires on %s.', edd_date_i18n( $next_month ) ); + + $this->assertStringContainsString( $expected, $this->get_message( $args ) ); + } + + public function test_license_third_party_missing() { + $args = $this->get_args( + array( + 'status' => 'missing', + 'api_url' => 'https://example.com', + ) + ); + + $this->assertStringContainsString( 'Please verify it.', $this->get_message( $args ) ); + } + + public function test_license_third_party_custom_uri() { + $args = $this->get_args( + array( + 'status' => 'site_inactive', + 'api_url' => 'https://example.com', + 'uri' => 'https://example.com', + ) + ); + + $this->assertStringContainsString( 'https://example.com', $this->get_message( $args ) ); + } + + private function get_args( $args = array() ) { + return wp_parse_args( + $args, + array( + 'status' => 'valid', + 'license_key' => 'bgvear89p7ty4qbrjkc4', + 'expires' => 'lifetime', + 'name' => 'Stripe Pro Payment Gateway', + ) + ); + } + + private function get_message( $args ) { + $messages = new \EDD\Licensing\Messages( $args ); + + return $messages->get_message(); + } +} diff --git a/tests/licensing/tests-licenses.php b/tests/licensing/tests-licenses.php new file mode 100644 index 00000000000..c9b17bff070 --- /dev/null +++ b/tests/licensing/tests-licenses.php @@ -0,0 +1,136 @@ +markTestSkipped( 'EDD Pro is not available.' ); + } + $license = self::$pass_handler->get_pro_license(); + + $this->assertEmpty( $license->key ); + $this->assertTrue( edd_is_inactive_pro() ); + } + + public function test_get_pro_license() { + $license_key = 'daksjfg98q3kjhJ3K4Q2354'; + update_site_option( 'edd_pro_license_key', $license_key ); + + $license = self::$pass_handler->get_pro_license(); + + $this->assertEquals( $license_key, $license->key ); + } + + public function test_get_pro_license_data() { + $license_key = 'daksjfg98q3kjhJ3K4Q2354'; + update_site_option( 'edd_pro_license_key', $license_key ); + self::$pass_handler->update_pro_license( $this->get_pass_license_data() ); + + $license = self::$pass_handler->get_pro_license(); + + $this->assertEquals( 7642331, $license->payment_id ); + } + + public function test_pro_license_inactive_pro_returns_false() { + $license_key = 'daksjfg98q3kjhJ3K4Q2354'; + update_site_option( 'edd_pro_license_key', $license_key ); + self::$pass_handler->update_pro_license( $this->get_pass_license_data() ); + + $this->assertFalse( edd_is_inactive_pro() ); + } + + public function test_get_stripe_license() { + $product_name = 'Stripe Pro Payment Gateway'; + $shortname = 'edd_' . preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $product_name ) ) ); + $license_key = 'bgvear89p7ty4qbrjkc4'; + + edd_update_option( "{$shortname}_license_key", $license_key ); + $license = new \EDD\Licensing\License( $product_name ); + $license->save( $this->get_stripe_license_data() ); + $license = $license->get(); + + $this->assertEquals( 167, $license->item_id ); + $this->assertEquals( $license_key, $license->key ); + } + + public function test_pass_manager_highest_pass_id() { + $license_key = 'daksjfg98q3kjhJ3K4Q2354'; + update_site_option( 'edd_pro_license_key', $license_key ); + self::$pass_handler->update_pro_license( $this->get_pass_license_data() ); + + $pass_manager = new \EDD\Admin\Pass_Manager(); + $this->assertEquals( 1464807, $pass_manager->highest_pass_id ); + } + + public function test_pass_manager_invalid_pass_highest_pass_id() { + $license_key = 'daksjfg98q3kjhJ3K4Q2354'; + update_site_option( 'edd_pro_license_key', $license_key ); + + $license_data = $this->get_pass_license_data(); + $license_data->pass_id = null; + $license_data->item_id = 161667; + self::$pass_handler->update_pro_license( $license_data ); + + $pass_manager = new \EDD\Admin\Pass_Manager(); + $this->assertNull( $pass_manager->highest_pass_id ); + } + + private function get_pass_license_data() { + return (object) array( + 'success' => true, + 'license' => 'valid', + 'item_id' => 1783595, + 'item_name' => '', + 'license_limit' => 1, + 'site_count' => 2, + 'expires' => 'lifetime', + 'activations_left' => 'unlimited', + 'payment_id' => 7642331, + 'customer_name' => 'John Doe', + 'customer_email' => 'john@edd.local', + 'price_id' => 0, + 'pass_id' => 1464807, + ); + } + + private function get_stripe_license_data() { + return (object) array( + 'success' => true, + 'license' => 'valid', + 'item_id' => 167, + 'item_name' => 'Stripe Pro Payment Gateway', + 'license_limit' => 1, + 'site_count' => 1, + 'expires' => 'lifetime', + 'activations_left' => 0, + 'payment_id' => 7642331, + 'customer_name' => 'John Doe', + 'customer_email' => 'john@edd.local', + 'price_id' => 1, + ); + } +} diff --git a/tests/logs/tests-api-request-log-meta.php b/tests/logs/tests-api-request-log-meta.php new file mode 100644 index 00000000000..20ab26ee17d --- /dev/null +++ b/tests/logs/tests-api-request-log-meta.php @@ -0,0 +1,127 @@ +api_request_log->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'log_api_request', 'meta' )->truncate(); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::add_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::add_meta() + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( edd_add_api_request_log_meta( self::$log->id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::add_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::add_meta() + */ + public function test_add_metadata_with_empty_value_should_be_true() { + $this->assertSame( 1, edd_add_api_request_log_meta( self::$log->id, 'test_key', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::add_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::add_meta() + */ + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertSame( 1, edd_add_api_request_log_meta( self::$log->id, 'test_key', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::update_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::update_meta() + */ + public function test_update_metadata_with_empty_key_value_should_be_empty() { + $this->assertSame( false, edd_update_api_request_log_meta( self::$log->id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::update_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::update_meta() + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertSame( 1, edd_update_api_request_log_meta( self::$log->id, 'test_key_2', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::update_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::update_meta() + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertSame( 1, edd_update_api_request_log_meta( self::$log->id, 'test_key_2', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::get_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::get_meta() + */ + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertSame( array(), edd_get_api_request_log_meta( self::$log->id ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::get_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::get_meta() + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertSame( '', edd_get_api_request_log_meta( self::$log->id, 'key_that_does_not_exist', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::get_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::get_meta() + */ + public function test_get_metadata_after_update_should_return_that_value() { + edd_update_api_request_log_meta( self::$log->id, 'test_key_2', '1' ); + $this->assertSame( '1', edd_get_api_request_log_meta( self::$log->id, 'test_key_2', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::delete_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::delete_meta() + */ + public function test_delete_metadata_with_valid_key_should_return_true() { + edd_update_api_request_log_meta( self::$log->id, 'test_key', '1' ); + $this->assertTrue( edd_delete_api_request_log_meta( self::$log->id, 'test_key' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_Api_Request::delete_meta() + * @covers \EDD\Api_Request_Logs\Api_Request_Log::delete_meta() + */ + public function test_delete_metadata_with_invalid_key_should_return_false() { + $this->assertFalse( edd_delete_api_request_log_meta( self::$log->id, 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/logs/tests-api-request-logs.php b/tests/logs/tests-api-request-logs.php new file mode 100644 index 00000000000..8ca1a083437 --- /dev/null +++ b/tests/logs/tests-api-request-logs.php @@ -0,0 +1,565 @@ +api_request_log->create_many( 5 ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_user_id_should_return_1() { + $logs = edd_get_api_request_logs( array( + 'user_id' => \WP_UnitTest_Generator_Sequence::$incr + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_user_id__in_should_return_2() { + $logs = edd_get_api_request_logs( array( + 'user_id__in' => array( \WP_UnitTest_Generator_Sequence::$incr, \WP_UnitTest_Generator_Sequence::$incr - 1 ) + ) ); + + $this->assertCount( 2, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_user_id__not_in_should_return_3() { + $logs = edd_get_api_request_logs( array( + 'user_id__not_in' => array( \WP_UnitTest_Generator_Sequence::$incr, \WP_UnitTest_Generator_Sequence::$incr - 1 ) + ) ); + + $this->assertCount( 3, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_api_key_should_return_1() { + $logs = edd_get_api_request_logs( array( + 'api_key' => 'b8062c469b938352k3bg4656999ed' . \WP_UnitTest_Generator_Sequence::$incr + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_api_key__in_should_return_2() { + $logs = edd_get_api_request_logs( array( + 'api_key__in' => array( + 'b8062c469b938352k3bg4656999ed' . \WP_UnitTest_Generator_Sequence::$incr, + 'b8062c469b938352k3bg4656999ed' . (\WP_UnitTest_Generator_Sequence::$incr - 1) + ) + ) ); + + $this->assertCount( 2, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_api_key__not_in_should_return_2() { + $logs = edd_get_api_request_logs( array( + 'api_key__not_in' => array( + 'b8062c469b938352k3bg4656999ed' . \WP_UnitTest_Generator_Sequence::$incr, + 'b8062c469b938352k3bg4656999ed' . (\WP_UnitTest_Generator_Sequence::$incr - 1) + ) + ) ); + + $this->assertCount( 3, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_token_should_return_1() { + $logs = edd_get_api_request_logs( array( + 'token' => 'b8062c469b938352k3bg4656999ed' . \WP_UnitTest_Generator_Sequence::$incr + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_token__in_should_return_2() { + $logs = edd_get_api_request_logs( array( + 'token__in' => array( + 'b8062c469b938352k3bg4656999ed' . \WP_UnitTest_Generator_Sequence::$incr, + 'b8062c469b938352k3bg4656999ed' . (\WP_UnitTest_Generator_Sequence::$incr - 1) + ) + ) ); + + $this->assertCount( 2, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_token__not_in_should_return_2() { + $logs = edd_get_api_request_logs( array( + 'token__not_in' => array( + 'b8062c469b938352k3bg4656999ed' . \WP_UnitTest_Generator_Sequence::$incr, + 'b8062c469b938352k3bg4656999ed' . (\WP_UnitTest_Generator_Sequence::$incr - 1) + ) + ) ); + + $this->assertCount( 3, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_ip_should_return_5() { + $logs = edd_get_api_request_logs( array( + 'ip' => '10.1.1.1' + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_ip__in_should_return_5() { + $logs = edd_get_api_request_logs( array( + 'ip__in' => array( '10.1.1.1' ) + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_ip__not_in_should_return_0() { + $logs = edd_get_api_request_logs( array( + 'ip__not_in' => array( '10.1.1.1' ) + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_time_should_return_1() { + $logs = edd_get_api_request_logs( array( + 'time' => '0.0' . \WP_UnitTest_Generator_Sequence::$incr + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_time__in_should_return_2() { + $logs = edd_get_api_request_logs( array( + 'time__in' => array( + '0.0' . \WP_UnitTest_Generator_Sequence::$incr, + '0.0' . (\WP_UnitTest_Generator_Sequence::$incr - 1) + ) + ) ); + + $this->assertCount( 2, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_time__not_in_should_return_3() { + $logs = edd_get_api_request_logs( array( + 'time__not_in' => array( + '0.0' . \WP_UnitTest_Generator_Sequence::$incr, + '0.0' . (\WP_UnitTest_Generator_Sequence::$incr - 1) + ) + ) ); + + $this->assertCount( 3, $logs ); + } + + /** + * @covers ::update_item() + */ + public function test_update_should_return_true() { + $success = edd_update_api_request_log( self::$logs[0], array( + 'ip' => '10.0.0.1', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::update_item() + */ + public function test_log_object_after_update_should_return_true() { + $success = edd_update_api_request_log( self::$logs[0], array( + 'ip' => '10.0.0.1', + ) ); + + $log = edd_get_api_request_log( self::$logs[0] ); + + $this->assertEquals( '10.0.0.1', $log->ip ); + } + + /** + * @covers ::update_item() + */ + public function test_update_without_ip_should_fail() { + $success = edd_update_api_request_log( null, array( + 'ip' => '10.0.0.1', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::delete_item() + */ + public function test_delete_should_return_true() { + $success = edd_delete_api_request_log( self::$logs[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::delete_item() + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_api_request_log( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_small_number_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'number' => 2, + ) ); + + $this->assertCount( 2, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_number_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_offset_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_search_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'search' => 'edd-api=sales', + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_search_with_orderby_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'search' => 'edd-api=sales', + 'orderby' => 'api_key', + ) ); + + $this->assertTrue( $logs[0]->api_key > $logs[1]->api_key ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_search_with_orderby_and_order_desc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'search' => 'edd-api=sales', + 'orderby' => 'api_key', + 'order' => 'desc' + ) ); + + $this->assertTrue( $logs[0]->api_key > $logs[1]->api_key ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_search_with_orderby_and_order_asc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'search' => 'edd-api=sales', + 'orderby' => 'api_key', + 'order' => 'asc' + ) ); + + $this->assertTrue( $logs[0]->api_key < $logs[1]->api_key ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_search_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'search' => 'edd-api=info', + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_api_key_and_order_asc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'api_key', + 'order' => 'asc' + ) ); + + $this->assertTrue( $logs[0]->api_key < $logs[1]->api_key ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_api_key_and_order_desc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'api_key', + 'order' => 'desc' + ) ); + + $this->assertTrue( $logs[0]->api_key > $logs[1]->api_key ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_token_and_order_asc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'token', + 'order' => 'asc' + ) ); + + $this->assertTrue( $logs[0]->token < $logs[1]->token ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_token_and_order_desc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'token', + 'order' => 'desc' + ) ); + + $this->assertTrue( $logs[0]->token > $logs[1]->token ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_request_and_order_asc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'request', + 'order' => 'asc' + ) ); + + $this->assertTrue( $logs[0]->request < $logs[1]->request ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_request_and_order_desc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'request', + 'order' => 'desc' + ) ); + + $this->assertTrue( $logs[0]->request > $logs[1]->request ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_time_and_order_asc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'time', + 'order' => 'asc' + ) ); + + $this->assertTrue( $logs[0]->time < $logs[1]->time ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_time_and_order_desc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'orderby' => 'time', + 'order' => 'desc' + ) ); + + $this->assertTrue( $logs[0]->time > $logs[1]->time ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_order_asc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'order' => 'asc', + ) ); + + $this->assertTrue( $logs[0]->id < $logs[1]->id ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_order_desc_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'order' => 'desc', + ) ); + + $this->assertTrue( $logs[0]->id > $logs[1]->id ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_by_user_id_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'user_id' => \WP_UnitTest_Generator_Sequence::$incr + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_by_invalid_user_id_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'user_id' => 99999, + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_version_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'version' => 'v99999', + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_api_key_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'api_key' => 'b8062c469b938052f3bf4656999ee995b8062c469b938052f3bf4656999ee995', + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_token_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'token' => 'b8062c469b938052f3bf4656999ee995b8062c469b938052f3bf4656999ee995', + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_request_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'request' => 'foo', + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_ip_should_return_true() { + $logs = edd_get_api_request_logs( array( + 'ip' => '999.999.999.999', + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::count() + */ + public function test_count() { + $this->assertEquals( 5, edd_count_api_request_logs() ); + } + +} diff --git a/tests/logs/tests-file-download-log-meta.php b/tests/logs/tests-file-download-log-meta.php new file mode 100644 index 00000000000..46f6b84b1f9 --- /dev/null +++ b/tests/logs/tests-file-download-log-meta.php @@ -0,0 +1,127 @@ +file_download_log->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'log_file_download', 'meta' )->truncate(); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::add_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::add_meta() + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( edd_add_file_download_log_meta( self::$log->id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::add_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::add_meta() + */ + public function test_add_metadata_with_empty_value_should_be_true() { + $this->assertSame( 1, edd_add_file_download_log_meta( self::$log->id, 'test_key', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::add_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::add_meta() + */ + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertSame( 1, edd_add_file_download_log_meta( self::$log->id, 'test_key', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::update_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::update_meta() + */ + public function test_update_metadata_with_empty_key_value_should_be_empty() { + $this->assertSame( false, edd_update_file_download_log_meta( self::$log->id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::update_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::update_meta() + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertSame( 1, edd_update_file_download_log_meta( self::$log->id, 'test_key_2', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::update_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::update_meta() + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertSame( 1, edd_update_file_download_log_meta( self::$log->id, 'test_key_2', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::get_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::get_meta() + */ + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertSame( array(), edd_get_file_download_log_meta( self::$log->id ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::get_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::get_meta() + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertSame( '', edd_get_file_download_log_meta( self::$log->id, 'key_that_does_not_exist', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::get_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::get_meta() + */ + public function test_get_metadata_after_update_should_return_that_value() { + edd_update_file_download_log_meta( self::$log->id, 'test_key_2', '1' ); + $this->assertSame( '1', edd_get_file_download_log_meta( self::$log->id, 'test_key_2', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::delete_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::delete_meta() + */ + public function test_delete_metadata_with_valid_key_should_return_true() { + edd_update_file_download_log_meta( self::$log->id, 'test_key', '1' ); + $this->assertTrue( edd_delete_file_download_log_meta( self::$log->id, 'test_key' ) ); + } + + /** + * @covers \EDD\Database\Queries\Log_File_Download::delete_meta() + * @covers \EDD\File_Download_Logs\File_Download_Log::delete_meta() + */ + public function test_delete_metadata_with_invalid_key_should_return_false() { + $this->assertFalse( edd_delete_file_download_log_meta( self::$log->id, 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/logs/tests-file-download-logs.php b/tests/logs/tests-file-download-logs.php new file mode 100644 index 00000000000..5e874140be0 --- /dev/null +++ b/tests/logs/tests-file-download-logs.php @@ -0,0 +1,239 @@ +file_download_log->create_many( 5 ); + } + + /** + * @covers ::update() + */ + public function test_update_should_return_true() { + $success = edd_update_file_download_log( self::$logs[0], array( + 'ip' => '10.0.0.1', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::update() + */ + public function test_log_object_after_update_should_return_true() { + $success = edd_update_file_download_log( self::$logs[0], array( + 'ip' => '10.0.0.1', + ) ); + + $log = edd_get_file_download_log( self::$logs[0] ); + + $this->assertEquals( '10.0.0.1', $log->ip ); + } + + /** + * @covers \EDD\Database\Queries\Logs::update() + */ + public function test_update_without_id_should_fail() { + $success = edd_update_file_download_log( null, array( + 'ip' => '10.0.0.1', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::delete() + */ + public function test_delete_should_return_true() { + $success = edd_delete_file_download_log( self::$logs[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::delete() + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_file_download_log( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_number_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_offset_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_orderby_product_id_and_order_asc_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'orderby' => 'product_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $logs[0]->product_id < $logs[1]->product_id ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_orderby_product_id_and_order_desc_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'orderby' => 'product_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $logs[0]->product_id > $logs[1]->product_id ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_orderby_file_id_and_order_asc_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'orderby' => 'file_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $logs[0]->file_id < $logs[1]->file_id ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_orderby_file_id_and_order_desc_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'orderby' => 'file_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $logs[0]->file_id > $logs[1]->file_id ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_order_asc_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'order' => 'asc', + ) ); + + $this->assertTrue( $logs[0]->id < $logs[1]->id ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_order_desc_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'order' => 'desc', + ) ); + + $this->assertTrue( $logs[0]->id > $logs[1]->id ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_by_product_id_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'product_id' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_by_invalid_object_id_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'product_id' => 99999, + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_invalid_order_id_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'order_id' => 99999, + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_invalid_file_id_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'file_id' => 99999, + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::get_logs() + */ + public function test_get_logs_with_invalid_price_id_should_return_true() { + $logs = edd_get_file_download_logs( array( + 'price_id' => 99999, + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::count() + */ + public function test_count() { + $this->assertEquals( 5, edd_count_file_download_logs() ); + } +} diff --git a/tests/logs/tests-log-meta.php b/tests/logs/tests-log-meta.php new file mode 100644 index 00000000000..274168e68bf --- /dev/null +++ b/tests/logs/tests-log-meta.php @@ -0,0 +1,127 @@ +log->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'log', 'meta' )->truncate(); + } + + /** + * @covers \EDD\Database\Queries\Logs::add_meta() + * @covers \EDD\Logs\Log::add_meta() + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( edd_add_log_meta( self::$log->id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::add_meta() + * @covers \EDD\Logs\Log::add_meta() + */ + public function test_add_metadata_with_empty_value_should_be_empty() { + $this->assertNotEmpty( edd_add_log_meta( self::$log->id, 'test_key', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::add_meta() + * @covers \EDD\Logs\Log::add_meta() + */ + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_log_meta( self::$log->id, 'test_key', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::update_meta() + * @covers \EDD\Logs\Log::update_meta() + */ + public function test_update_metadata_with_empty_key_value_should_be_empty() { + $this->assertEmpty( edd_update_note_meta( self::$log->id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::update_meta() + * @covers \EDD\Logs\Log::update_meta() + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_log_meta( self::$log->id, 'test_key_2', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::update_meta() + * @covers \EDD\Logs\Log::update_meta() + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_log_meta( self::$log->id, 'test_key_2', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::get_meta() + * @covers \EDD\Logs\Log::get_meta() + */ + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertEmpty( edd_get_note_meta( self::$log->id ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::get_meta() + * @covers \EDD\Logs\Log::get_meta() + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertEmpty( edd_get_log_meta( self::$log->id, 'key_that_does_not_exist', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::get_meta() + * @covers \EDD\Logs\Log::get_meta() + */ + public function test_get_metadata_after_update_should_return_that_value() { + edd_update_log_meta( self::$log->id, 'test_key_2', '1' ); + $this->assertEquals( '1', edd_get_log_meta( self::$log->id, 'test_key_2', true ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::delete_meta() + * @covers \EDD\Logs\Log::delete_meta() + */ + public function test_delete_metadata_with_valid_key_should_return_true() { + edd_update_log_meta( self::$log->id, 'test_key', '1' ); + $this->assertTrue( edd_delete_log_meta( self::$log->id, 'test_key' ) ); + } + + /** + * @covers \EDD\Database\Queries\Logs::delete_meta() + * @covers \EDD\Logs\Log::delete_meta() + */ + public function test_delete_metadata_with_invalid_key_should_return_false() { + $this->assertFalse( edd_delete_log_meta( self::$log->id, 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/logs/tests-logs.php b/tests/logs/tests-logs.php new file mode 100644 index 00000000000..53bf2831124 --- /dev/null +++ b/tests/logs/tests-logs.php @@ -0,0 +1,352 @@ +log->create_many( 5 ); + } + + /** + * @covers ::update_item() + */ + public function test_update_should_return_true() { + $success = edd_update_log( self::$logs[0], array( + 'title' => 'Log title 45', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::update_item() + */ + public function test_log_object_after_update_should_return_true() { + $success = edd_update_log( self::$logs[0], array( + 'title' => 'Log title 45', + ) ); + + $log = edd_get_log( self::$logs[0] ); + + $this->assertEquals( 'Log title 45', $log->title ); + } + + /** + * @covers ::update_item() + */ + public function test_update_without_id_should_fail() { + $success = edd_update_log( null, array( + 'message' => 'Payment status changed', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::delete_item() + */ + public function test_delete_should_return_true() { + $success = edd_delete_log( self::$logs[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::delete_item() + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_log( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_number_should_return_true() { + $logs = edd_get_logs( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_offset_should_return_true() { + $logs = edd_get_logs( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_search_should_return_true() { + $logs = edd_get_logs( array( + 'search' => 'Log title', + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_title_and_order_asc_should_return_true() { + $logs = edd_get_logs( array( + 'orderby' => 'title', + 'order' => 'asc' + ) ); + + $this->assertTrue( $logs[0]->title < $logs[1]->title ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_title_and_order_desc_should_return_true() { + $logs = edd_get_logs( array( + 'orderby' => 'title', + 'order' => 'desc' + ) ); + + $this->assertTrue( $logs[0]->title > $logs[1]->title ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_content_and_order_asc_should_return_true() { + $logs = edd_get_logs( array( + 'orderby' => 'content', + 'order' => 'asc' + ) ); + + $this->assertTrue( $logs[0]->content < $logs[1]->content ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_orderby_content_and_order_desc_should_return_true() { + $logs = edd_get_logs( array( + 'orderby' => 'content', + 'order' => 'desc' + ) ); + + $this->assertTrue( $logs[0]->content > $logs[1]->content ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_order_asc_should_return_true() { + $logs = edd_get_logs( array( + 'order' => 'asc', + ) ); + + $this->assertTrue( $logs[0]->id < $logs[1]->id ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_order_desc_should_return_true() { + $logs = edd_get_logs( array( + 'order' => 'desc', + ) ); + + $this->assertTrue( $logs[0]->id > $logs[1]->id ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_log_id_should_return_1() { + $logs = edd_get_logs( array( + 'id' => 1, + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_id__in_should_return_2() { + $logs = edd_get_logs( array( + 'id__in' => array( 1, 2 ), + ) ); + + $this->assertCount( 2, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_id__not_in_should_return_3() { + $logs = edd_get_logs( array( + 'id__not_in' => array( 1, 2 ), + 'number' => 5, + ) ); + + $this->assertCount( 3, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_object_id_should_return_1() { + $logs = edd_get_logs( array( + 'object_id' => \WP_UnitTest_Generator_Sequence::$incr + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_object_id__in_should_return_2() { + $logs = edd_get_logs( array( + 'object_id__in' => array( \WP_UnitTest_Generator_Sequence::$incr, \WP_UnitTest_Generator_Sequence::$incr - 1 ) + ) ); + + $this->assertCount( 2, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_object_id__not_in_should_return_3() { + $logs = edd_get_logs( array( + 'object_id__not_in' => array( \WP_UnitTest_Generator_Sequence::$incr, \WP_UnitTest_Generator_Sequence::$incr - 1 ), + 'number' => 5, + ) ); + + $this->assertCount( 3, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_object_id_should_return_0() { + $logs = edd_get_logs( array( + 'object_id' => 99999, + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_object_type_should_return_5() { + $logs = edd_get_logs( array( + 'object_type' => 'download', + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_object_type__in_should_return_2() { + $logs = edd_get_logs( array( + 'object_type__in' => array( 'download' ) + ) ); + + $this->assertCount( 5, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_object_type__not_in_should_return_3() { + $logs = edd_get_logs( array( + 'object_type__not_in' => array( 'download' ), + 'number' => 5, + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_by_title_should_return_1() { + global $wp_version; + + if ( version_compare( $wp_version, '5.4', '<' ) ) { + $title_search = 'Log title ' . \WP_UnitTest_Generator_Sequence::$incr; + + } else { + $title_search = 'Log title ' . zeroise( \WP_UnitTest_Generator_Sequence::$incr, 7 ); + } + + $logs = edd_get_logs( array( + 'title' => $title_search, + ) ); + + $this->assertCount( 1, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_with_invalid_title_should_return_0() { + $logs = edd_get_logs( array( + 'title' => 'Log title 99999', + ) ); + + $this->assertCount( 0, $logs ); + } + + /** + * @covers ::query() + */ + public function test_get_logs_returned_objects_should_return_true() { + $logs = edd_get_logs( array( + 'number' => 5 + ) ); + + foreach ( $logs as $log ) { + $this->assertInstanceOf( 'EDD\\Logs\\Log', $log ); + } + } + + /** + * @covers ::count() + */ + public function test_count() { + $this->assertEquals( 5, edd_count_logs() ); + } +} diff --git a/tests/notes/tests-note-meta.php b/tests/notes/tests-note-meta.php new file mode 100644 index 00000000000..5fa4aa17cc8 --- /dev/null +++ b/tests/notes/tests-note-meta.php @@ -0,0 +1,117 @@ +note->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'note', 'meta' )->truncate(); + } + + /** + * @covers \EDD\Database\Queries\Notes::add_meta() + * @covers Note::add_meta() + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( edd_add_note_meta( self::$note->id, '', '' ) ); + } + + public function test_add_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_note_meta( self::$note->id, 'test_key', '' ) ); + } + + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_note_meta( self::$note->id, 'test_key', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Notes::update_meta() + * @covers Note::update_meta() + */ + public function test_update_metadata_with_empty_key_value_should_return_false() { + $this->assertEmpty( edd_update_note_meta( self::$note->id, '', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Notes::update_meta() + * @covers Note::update_meta() + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_note_meta( self::$note->id, 'test_key_2', '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Notes::update_meta() + * @covers Note::update_meta() + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_note_meta( self::$note->id, 'test_key_2', '1' ) ); + } + + /** + * @covers \EDD\Database\Queries\Notes::get_meta() + * @covers Note::get_meta() + */ + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertEmpty( edd_get_note_meta( self::$note->id, '' ) ); + } + + /** + * @covers \EDD\Database\Queries\Notes::get_meta() + * @covers Note::get_meta() + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertEmpty( edd_get_note_meta( self::$note->id, 'key_that_does_not_exist', true ) ); + edd_update_note_meta( self::$note->id, 'test_key_2', '1' ); + $this->assertEquals( '1', edd_get_note_meta( self::$note->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_note_meta( self::$note->id, 'test_key_2', false ) ); + } + + /** + * @covers \EDD\Database\Queries\Notes::get_meta() + * @covers Note::get_meta() + */ + public function test_get_metadata_after_update_should_return_1_and_be_of_type_array() { + edd_update_note_meta( self::$note->id, 'test_key_2', '1' ); + + $this->assertEquals( '1', edd_get_note_meta( self::$note->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_note_meta( self::$note->id, 'test_key_2', false ) ); + } + + /** + * @covers \EDD\Database\Queries\Notes::delete_meta() + * @covers Note::delete_meta() + */ + public function test_delete_metadata_after_update() { + edd_update_note_meta( self::$note->id, 'test_key', '1' ); + + $this->assertTrue( edd_delete_note_meta( self::$note->id, 'test_key' ) ); + $this->assertFalse( edd_delete_note_meta( self::$note->id, 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/notes/tests-notes-db.php b/tests/notes/tests-notes-db.php new file mode 100644 index 00000000000..9d12c31b6f3 --- /dev/null +++ b/tests/notes/tests-notes-db.php @@ -0,0 +1,322 @@ +note->create_many( 5 ); + } + + public function test_installed() { + $this->assertTrue( edd_get_component_interface( 'note', 'table' )->exists() ); + } + + /** + * @covers ::insert() + */ + public function test_insert_should_return_false_if_no_object_id_supplied() { + $this->assertFalse( edd_add_note( array( + 'object_type' => 'payment', + 'content' => 'Payment status changed', + ) ) ); + } + + /** + * @covers ::insert() + */ + public function test_insert_should_return_false_if_no_object_type_supplied() { + $this->assertFalse( edd_add_note( array( + 'object_id' => 6278, + 'content' => 'Payment status changed', + ) ) ); + } + + /** + * @covers ::insert() + */ + public function test_insert_with_valid_data() { + $this->assertGreaterThan( 0, edd_add_note( array( + 'object_type' => 'payment', + 'object_id' => 6278, + 'content' => 'Payment status changed', + ) ) ); + } + + /** + * @covers ::insert() + */ + public function test_insert_with_invalid_data_should_return_false() { + $this->assertFalse( edd_add_note( array( ) ) ); + } + + /** + * @covers ::update() + */ + public function test_update_should_return_false_if_no_row_id_supplied() { + $this->assertFalse( edd_update_note( 0 ) ); + } + + /** + * @covers ::update() + */ + public function test_note_object_after_update_should_return_true() { + $this->assertSame( 1, edd_update_note( self::$notes[0], array( + 'content' => 'Note with updated body', + ) ) ); + + $note = edd_get_note( self::$notes[0] ); + + $this->assertSame( 'Note with updated body', $note->content ); + } + + /** + * @covers ::delete() + */ + public function test_delete_should_return_false_if_no_row_id_supplied() { + $this->assertFalse( edd_delete_note( 0 ) ); + } + + /** + * @covers ::delete() + */ + public function test_delete_should_return_false() { + $this->assertSame( 1, edd_delete_note( self::$notes[0] ) ); + + $note = edd_get_note( self::$notes[0] ); + + $this->assertFalse( $note ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_number_should_return_true() { + $notes = edd_get_notes( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_offset_should_return_true() { + $notes = edd_get_notes( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_search_should_return_true() { + $notes = edd_get_notes( array( + 'search' => 'Payment status changed for object with ID', + ) ); + + $this->assertCount( 5, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_orderby_object_id_and_order_asc_should_return_true() { + $notes = edd_get_notes( array( + 'orderby' => 'object_id', + 'order' => 'asc' + ) ); + + $this->assertTrue( $notes[0]->object_id < $notes[1]->object_id ); + } + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_orderby_object_id_and_order_desc_should_return_true() { + $notes = edd_get_notes( array( + 'orderby' => 'object_id', + 'order' => 'desc' + ) ); + + $this->assertTrue( $notes[0]->object_id > $notes[1]->object_id ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_orderby_content_and_order_asc_should_return_true() { + $notes = edd_get_notes( array( + 'orderby' => 'content', + 'order' => 'asc' + ) ); + + $this->assertTrue( $notes[0]->content < $notes[1]->content ); + } + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_orderby_content_and_order_desc_should_return_true() { + $notes = edd_get_notes( array( + 'orderby' => 'content', + 'order' => 'desc' + ) ); + $this->assertTrue( $notes[0]->content > $notes[1]->content ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_orderby_user_id_and_order_asc_should_return_true() { + $notes = edd_get_notes( array( + 'orderby' => 'user_id', + 'order' => 'asc' + ) ); + + $this->assertTrue( $notes[0]->user_id < $notes[1]->user_id ); + } + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_orderby_user_id_and_order_desc_should_return_true() { + $notes = edd_get_notes( array( + 'orderby' => 'user_id', + 'order' => 'desc' + ) ); + + $this->assertTrue( $notes[0]->user_id > $notes[1]->user_id ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_order_asc_should_return_true() { + $notes = edd_get_notes( array( + 'order' => 'asc', + ) ); + + $this->assertTrue( $notes[0]->id < $notes[1]->id ); + } + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_order_desc_should_return_true() { + $notes = edd_get_notes( array( + 'order' => 'desc', + ) ); + $this->assertTrue( $notes[0]->id > $notes[1]->id ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_by_object_id_should_be_1() { + $notes = edd_get_notes( array( + 'object_id' => \WP_UnitTest_Generator_Sequence::$incr + ) ); + + $this->assertCount( 1, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_by_invalid_object_id_should_be_0() { + $notes = edd_get_notes( array( + 'object_id' => 99999, + ) ); + + $this->assertCount( 0, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_by_object_type_should_be_5() { + $notes = edd_get_notes( array( + 'object_type' => 'payment', + ) ); + + $this->assertCount( 5, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_by_content_should_be_1() { + $notes = edd_get_notes( array( + 'content' => 'Payment status changed for object with ID: ' . \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_get_notes_with_invalid_content_should_be_0() { + $notes = edd_get_notes( array( + 'content' => 'Payment status changed for object with ID: 99999', + ) ); + + $this->assertCount( 0, $notes ); + } + + /** + * @covers ::get_notes() + */ + public function test_count_should_be_5() { + $this->assertSame( 5, edd_count_notes() ); + } + + /** + * @covers ::get_notes() + */ + public function test_count_with_valid_object_type_should_be_0() { + $this->assertSame( 5, edd_count_notes( array( + 'object_type' => 'payment' + ) ) ); + } + + /** + * @covers ::get_notes() + */ + public function test_count_with_invalid_object_type_should_be_0() { + $this->assertSame( 0, edd_count_notes( array( + 'object_type' => 'foo' + ) ) ); + } + + /** + * @covers ::get_notes() + */ + public function test_count_with_user_id_should_be_1() { + $this->assertSame( 1, edd_count_notes( array( + 'user_id' => \WP_UnitTest_Generator_Sequence::$incr + ) ) ); + } +} diff --git a/tests/notes/tests-notes.php b/tests/notes/tests-notes.php new file mode 100644 index 00000000000..0ffb0e2b195 --- /dev/null +++ b/tests/notes/tests-notes.php @@ -0,0 +1,186 @@ +discount->create_and_get( array( + 'name' => '20 Percent Off', + 'code' => '20OFF', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '20', + 'use_count' => 54, + 'max_uses' => 10, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59' + ) ); + + self::$note = self::edd()->note->create_and_get( array( + 'object_id' => self::$discount->id, + 'object_type' => 'discount', + 'content' => 'Test note content.', + 'user_id' => get_current_user_id() + ) ); + } + + /** + * @covers \edd_ajax_add_discount_note() + */ + public function test_add_discount_note_with_no_args_should_die() { + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + $e = false; + try { + $this->_handleAjax( 'edd_add_note' ); + } catch ( \WPAjaxDieStopException $e ) { } + + $this->assertSame( '-1', $e->getMessage() ); + } + + /** + * @covers \edd_ajax_add_discount_note() + */ + public function test_add_discount_note_with_incorrect_role_should_die() { + $this->_setRole( 'subscriber' ); + + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + $_POST['object_id'] = self::$discount->id; + $_POST['object_type'] = 'discount'; + $_POST['note'] = 'This is a test note.'; + + try { + $this->_handleAjax( 'edd_add_note' ); + } catch ( \WPAjaxDieStopException $e ) { } + + $this->assertSame( '-1', $e->getMessage() ); + } + + /** + * @covers \edd_ajax_add_discount_note() + */ + public function test_add_discount_note_with_no_note_should_die() { + $this->_setRole( 'shop_manager' ); + + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + $_POST['object_id'] = self::$discount->id; + $_POST['object_type'] = 'discount'; + + try { + $this->_handleAjax( 'edd_add_note' ); + } catch ( \WPAjaxDieStopException $e ) { } + + $this->assertSame( '-1', $e->getMessage() ); + } + + /** + * @covers \edd_ajax_add_discount_note() + */ + public function test_add_discount_note_should_return_true() { + $this->_setRole( 'shop_manager' ); + + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + $_POST['object_id'] = self::$discount->id; + $_POST['object_type'] = 'discount'; + $_POST['note'] = 'This is a test note.'; + + try { + $this->_handleAjax( 'edd_add_note' ); + } catch ( \WPAjaxDieContinueException $e ) { + unset( $e ); + } + + $this->assertStringContainsString( 'edd_note_html', $this->_last_response ); + } + + /** + * @covers \edd_ajax_delete_discount_note() + */ + public function test_delete_discount_note_with_no_args_should_die() { + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + + try { + $this->_handleAjax( 'edd_delete_note' ); + } catch ( \WPAjaxDieStopException $e ) { } + + $this->assertSame( '-1', $e->getMessage() ); + } + + /** + * @covers \edd_ajax_delete_discount_note() + */ + public function test_delete_discount_note_with_incorrect_role_should_die() { + $this->_setRole( 'subscriber' ); + + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + $_POST['note_id'] = 1; + + try { + $this->_handleAjax( 'edd_delete_note' ); + } catch ( \WPAjaxDieStopException $e ) { } + + $this->assertSame( '-1', $e->getMessage() ); + } + + /** + * @covers \edd_ajax_delete_discount_note() + */ + public function test_delete_discount_note_with_invalid_id_should_die() { + $this->_setRole( 'shop_manager' ); + + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + $_POST['note_id'] = 99; + + try { + $this->_handleAjax( 'edd_delete_note' ); + } catch ( \WPAjaxDieStopException $e ) { } + + $this->assertSame( '0', $e->getMessage() ); + } + + /** + * @covers \edd_ajax_delete_discount_note() + */ + public function test_delete_discount_note_should_return_true() { + $this->_setRole( 'shop_manager' ); + + $_POST['nonce'] = wp_create_nonce( 'edd_note' ); + $_POST['note_id'] = self::$note->id; + + try { + $this->_handleAjax( 'edd_delete_note' ); + } catch ( \WPAjaxDieStopException $e ) {} + + $this->assertSame( '1', $e->getMessage() ); + } +} diff --git a/tests/notifications/tests-notification-api.php b/tests/notifications/tests-notification-api.php new file mode 100644 index 00000000000..5b6e042c63b --- /dev/null +++ b/tests/notifications/tests-notification-api.php @@ -0,0 +1,197 @@ +components['notification']; + $thing = $component->get_interface( 'table' ); + if ( $thing instanceof \EDD\Database\Table ) { + $thing->truncate(); + } + + // Insert 5 notifications. + for ( $i = 1; $i <= 5; $i ++ ) { + self::$notificationIds[] = (int) EDD()->notifications->insert( array( + 'title' => 'Notification ' . $i, + 'content' => 'Notification ' . $i, + 'type' => 'success', + 'dismissed' => 0, + ) ); + } + + /** + * Also need to make sure we have the EDD roles so that we pass the + * capability check. + * + * @see \EDD\API\v3\Notifications::canViewNotification + */ + $roles = new \EDD_Roles; + $roles->add_roles(); + $roles->add_caps(); + + // Set current user as an administrator. + global $current_user; + $current_user = new \WP_User( 1 ); + $current_user->set_role( 'administrator' ); + $current_user->add_cap( 'manage_shop_settings' ); // I feel like this shouldn't be necessary to do, but it is. + } + + /** + * Performs a REST API request. + * + * @param string $endpointUri + * @param array $payload + * @param string $method + * + * @return \WP_REST_Response + */ + protected function makeRestRequest( $endpointUri = 'notifications', $payload = array(), $method = \WP_REST_Server::READABLE ) { + $request = new \WP_REST_Request( $method, sprintf( + '/%s/%s', + Endpoint::$namespace, + $endpointUri + ) ); + + $request->set_header( 'content-type', 'application/json' ); + + if ( ! empty( $payload ) ) { + $request->set_body( json_encode( $payload ) ); + } + + return self::$server->dispatch( $request ); + } + + /** + * @covers \EDD\API\v3\Notifications::listNotifications + * @return void + */ + public function test_get_notifications_returns_5_notifications() { + $response = $this->makeRestRequest(); + $response_data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertCount( 5, $response_data['active'] ); + if ( method_exists( $this, 'assertEqualsCanonicalizing' ) ) { + $this->assertEqualsCanonicalizing( self::$notificationIds, wp_list_pluck( $response_data['active'], 'id' ) ); + } else { + $this->assertEquals( self::$notificationIds, wp_list_pluck( $response_data['active'], 'id' ), '', 0, 1, true, true ); + } + } + + /** + * @covers \EDD\API\v3\Notifications::dismissNotification + * @return void + */ + public function test_dismissing_notification_updates_notification() { + $notificationId = self::$notificationIds[0]; + + $notification = new Notification( EDD()->notifications->get( $notificationId ) ); + $this->assertFalse( $notification->dismissed ); + + $response = $this->makeRestRequest( + 'notifications/' . $notificationId, + null, + \WP_REST_Server::DELETABLE + ); + + $this->assertEquals( 204, $response->get_status() ); + + $notification = new Notification( EDD()->notifications->get( $notificationId ) ); + $this->assertTrue( $notification->dismissed ); + } + + /** + * The notification we're attempting to delete doesn't exist. + * + * @return void + */ + public function test_dismissing_notification_with_invalid_id_fails() { + $notificationId = max( self::$notificationIds ) + 100; + + $response = $this->makeRestRequest( + 'notifications/' . $notificationId, + null, + \WP_REST_Server::DELETABLE + ); + + $this->assertEquals( 400, $response->get_status() ); + } + + public function test_subscriber_cannot_list_notifications() { + global $current_user; + $current_user = new \WP_User( wp_insert_user( array( + 'user_login' => 'test_subscriber', + 'user_email' => 'test_subscriber@easydigitaldownloads.com', + 'user_pass' => 'test_subscriber', + ) ) ); + $current_user->set_role( 'subscriber' ); + + $response = $this->makeRestRequest(); + + $this->assertEquals( 403, $response->get_status() ); + } + + public function test_subscriber_cannot_dismiss_notification() { + global $current_user; + $current_user = new \WP_User( wp_insert_user( array( + 'user_login' => 'test_subscriber', + 'user_email' => 'test_subscriber@easydigitaldownloads.com', + 'user_pass' => 'test_subscriber', + ) ) ); + $current_user->set_role( 'subscriber' ); + + $response = $this->makeRestRequest( + 'notifications/' . self::$notificationIds[0], + null, + \WP_REST_Server::DELETABLE + ); + + $this->assertEquals( 403, $response->get_status() ); + } + +} diff --git a/tests/notifications/tests-notification-db.php b/tests/notifications/tests-notification-db.php new file mode 100644 index 00000000000..4cd486f6461 --- /dev/null +++ b/tests/notifications/tests-notification-db.php @@ -0,0 +1,105 @@ +notifications->insert( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'dismissed' => 0, + ), 'notification' ); + + $notifications = EDD()->notifications->getActiveNotifications(); + + $this->assertSame( 1, count( $notifications ) ); + $this->assertTrue( $notifications[0] instanceof Notification ); + $this->assertSame( 'Notification', $notifications[0]->title ); + } + + /** + * @covers \EDD\Database\NotificationsDB::getActiveNotifications + */ + public function test_dismissed_notification_not_included_in_active() { + EDD()->notifications->insert( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'dismissed' => 1, + ), 'notification' ); + + $this->assertEmpty( EDD()->notifications->getActiveNotifications() ); + } + + /** + * @covers \EDD\Database\NotificationsDB::getActiveNotifications + */ + public function test_start_date_in_future_not_included_in_active() { + EDD()->notifications->insert( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'start' => date( 'Y-m-d H:i:s', strtotime( '+1 week' ) ), + ), 'notification' ); + + $this->assertEmpty( EDD()->notifications->getActiveNotifications() ); + } + + /** + * @covers \EDD\Database\NotificationsDB::getActiveNotifications + */ + public function test_end_date_in_past_not_included_in_active() { + EDD()->notifications->insert( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'end' => date( 'Y-m-d H:i:s', strtotime( '-1 week' ) ), + ), 'notification' ); + + $this->assertEmpty( EDD()->notifications->getActiveNotifications() ); + } + + /** + * @covers \EDD\Database\NotificationsDB::getActiveNotifications + */ + public function test_end_date_in_future_included_in_active() { + // Ends in 1 week - still valid. + EDD()->notifications->insert( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'end' => date( 'Y-m-d H:i:s', strtotime( '+1 week' ) ), + ), 'notification' ); + + // Ended 1 week ago - should not be in results. + EDD()->notifications->insert( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'end' => date( 'Y-m-d H:i:s', strtotime( '-1 week' ) ), + ), 'notification' ); + + $notifications = EDD()->notifications->getActiveNotifications(); + + $this->assertSame( 1, count( $notifications ) ); + } + +} diff --git a/tests/notifications/tests-notification-importer.php b/tests/notifications/tests-notification-importer.php new file mode 100644 index 00000000000..6d4ffef6839 --- /dev/null +++ b/tests/notifications/tests-notification-importer.php @@ -0,0 +1,255 @@ +components['notification']; + $thing = $component->get_interface( 'table' ); + if ( $thing instanceof \EDD\Database\Table ) { + $thing->truncate(); + } + } + + /** + * Builds a mock of the NotificationImporter class, overriding the return + * value of fetchNotifications(). + * + * @return NotificationImporter + */ + protected function getMockImporter( $returnValue = array() ) { + $mock = $this->getMockBuilder( '\\EDD\\Utils\\NotificationImporter' ) + ->setMethods( array( 'fetchNotifications' ) ) + ->getMock(); + + // Doing a json_decode / encode here so that we end up with an array of objects. + $mock->method( 'fetchNotifications' ) + ->willReturn( json_decode( json_encode( $returnValue ) ) ); + + return $mock; + } + + /** + * Returns all notifications in the database. + * + * @return object[] + */ + protected function getNotifications() { + $notifications = new \EDD\Database\Queries\Notification(); + + return $notifications->query( array( 'number' => 99999 ) ); + } + + /** + * @covers \EDD\Utils\NotificationImporter::insertNewNotification + */ + public function test_valid_notification_is_imported() { + $importer = $this->getMockImporter( array( + array( + 'title' => 'Announcing New EDD Feature', + 'content' => 'This is an exciting new EDD feature.', + 'id' => 90, + 'start' => null, + 'notification_type' => 'success', + ) + ) ); + + delete_option( 'edd_notification_req_timeout' ); + + $importer->run(); + + $notifications = $this->getNotifications(); + + $this->assertSame( 1, count( $notifications ) ); + + $this->assertSame( 'Announcing New EDD Feature', $notifications[0]->title ); + $this->assertSame( 'This is an exciting new EDD feature.', $notifications[0]->content ); + $this->assertEquals( 90, $notifications[0]->remote_id ); + $this->assertSame( null, $notifications[0]->start ); + $this->assertSame( 'success', $notifications[0]->type ); + } + + /** + * @covers \EDD\Utils\NotificationImporter::validateNotification + */ + public function test_notification_with_no_title_not_imported() { + $importer = $this->getMockImporter( array( + array( + // title is missing + 'content' => 'This is an exciting new EDD feature.', + 'id' => 90, + 'start' => null, + 'notification_type' => 'success', + ) + ) ); + + $importer->run(); + + $notifications = $this->getNotifications(); + + $this->assertSame( 0, count( $notifications ) ); + } + + /** + * @covers \EDD\Utils\NotificationImporter::updateExistingNotification + */ + public function test_existing_notification_updated_with_new_content() { + $importer = $this->getMockImporter( array( + array( + 'title' => 'Announcing New EDD Feature', + 'content' => 'This is an exciting new EDD feature.', + 'id' => 90, + 'start' => null, + 'notification_type' => 'success', + ) + ) ); + + $importer->run(); + + $notifications = $this->getNotifications(); + $this->assertSame( 'This is an exciting new EDD feature.', $notifications[0]->content ); + + delete_option( 'edd_notification_req_timeout' ); + + $importer = $this->getMockImporter( array( + array( + 'title' => 'Announcing New EDD Feature', + 'content' => 'This is an exciting new EDD feature with updated content.', + 'id' => 90, + 'start' => null, + 'notification_type' => 'success', + ) + ) ); + + $importer->run(); + + $notifications = $this->getNotifications(); + $this->assertSame( 'This is an exciting new EDD feature with updated content.', $notifications[0]->content ); + } + + /** + * Notification has an end date of 2 days ago, so it should not be imported. + * + * @throws \Exception + */ + public function test_ended_notification_doesnt_validate() { + $importer = new NotificationImporter(); + + $this->expectException( '\Exception' ); + $this->expectExceptionMessage( 'Notification has expired.' ); + + $notification = new \stdClass(); + $notification->title = 'Announcing New EDD Feature'; + $notification->content = 'This is an exciting new EDD feature.'; + $notification->id = 90; + $notification->end = date( 'Y-m-d H:i:s', strtotime( '-2 days' ) ); + $notification->notification_type = 'success'; + + $importer->validateNotification( $notification ); + } + + /** + * EDD was installed today, but notification was created 2 days ago. It should not + * validate because we only accept notifications created _after_ EDD was installed. + * + * @covers \EDD\Utils\NotificationImporter::validateNotification + * + * @throws \Exception + */ + public function test_notification_started_before_installation_date_doesnt_validate() { + $importer = new NotificationImporter(); + + $this->expectException( '\Exception' ); + $this->expectExceptionMessage( 'Notification created prior to EDD activation.' ); + + $notification = new \stdClass(); + $notification->title = 'Announcing New EDD Feature'; + $notification->content = 'This is an exciting new EDD feature.'; + $notification->id = 90; + $notification->start = date( 'Y-m-d H:i:s', strtotime( '-2 days' ) ); + $notification->notification_type = 'success'; + + $importer->validateNotification( $notification ); + } + + /** + * @covers \EDD\Utils\NotificationImporter::validateNotification + * + * @throws \Exception + */ + public function test_notification_missing_properties_doesnt_validate() { + $importer = new NotificationImporter(); + + $this->expectException( '\Exception' ); + $this->expectExceptionMessage( 'Missing required properties: ["title"]' ); + + $notification = new \stdClass(); + $notification->content = 'This is an exciting new EDD feature.'; + $notification->id = 90; + $notification->notification_type = 'success'; + + $importer->validateNotification( $notification ); + } + + /** + * @covers \EDD\Utils\NotificationImporter::validateNotification + * + * @throws \Exception + */ + public function test_notification_for_pass_holders_not_validated_for_free_install() { + $importer = new NotificationImporter(); + + $this->expectException( '\Exception' ); + $this->expectExceptionMessage( 'Condition(s) not met' ); + + $notification = new \stdClass(); + $notification->title = 'Announcing New EDD Feature for Pass Holders'; + $notification->content = 'This is an exciting new EDD feature.'; + $notification->id = 90; + $notification->notification_type = 'success'; + $notification->type = array( 'pass-any' ); + + $importer->validateNotification( $notification ); + } + + /** + * @covers \EDD\Utils\NotificationImporter::validateNotification + * + * @throws \Exception + */ + public function test_notification_for_version_1x_not_validated() { + $importer = new NotificationImporter(); + + $this->expectException( '\Exception' ); + $this->expectExceptionMessage( 'Condition(s) not met' ); + + $notification = new \stdClass(); + $notification->title = 'Announcing New EDD Feature for Pass Holders'; + $notification->content = 'This is an exciting new EDD feature.'; + $notification->id = 90; + $notification->notification_type = 'success'; + $notification->type = array( '1-x' ); + + $importer->validateNotification( $notification ); + } + +} diff --git a/tests/notifications/tests-notification-model.php b/tests/notifications/tests-notification-model.php new file mode 100644 index 00000000000..4d8ebae0e0e --- /dev/null +++ b/tests/notifications/tests-notification-model.php @@ -0,0 +1,99 @@ +notifications->insert( $data ); + + return new Notification( EDD()->notifications->get( $notificationId ) ); + } + + /** + * The `dismissed` property should be cast to boolean. + * + * @covers \EDD\Models\Notification::castAttribute + */ + public function test_dismissed_is_boolean() { + $notification = $this->insertAndGetNotification( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'dismissed' => 0, + ) ); + + $this->assertFalse( $notification->dismissed ); + } + + /** + * The `id` property should be cast to an integer. + * + * @covers \EDD\Models\Notification::castAttribute + */ + public function test_id_is_integer() { + $notification = $this->insertAndGetNotification( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'success', + 'dismissed' => 0, + ) ); + + $this->assertTrue( is_int( $notification->id ) ); + } + + /** + * The `buttons` property should be cast to an array if not empty. + * + * @covers \EDD\Models\Notification::castAttribute + */ + public function test_non_empty_buttons_is_array() { + $notification = $this->insertAndGetNotification( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'buttons' => array( + array( + 'type' => 'primary', + 'url' => 'https://easydigitaldownloads.com', + 'text' => 'Learn More', + ) + ), + 'type' => 'success', + 'dismissed' => 0, + ) ); + + $this->assertTrue( is_array( $notification->buttons ) ); + $this->assertSame( 'Learn More', $notification->buttons[0]['text'] ); + } + + /** + * The `buttons` property should be null if no data. + * + * @covers \EDD\Models\Notification::castAttribute + */ + public function test_empty_buttons_is_null() { + $notification = $this->insertAndGetNotification( array( + 'title' => 'Notification', + 'content' => 'Notification', + 'buttons' => null, + 'type' => 'success', + 'dismissed' => 0, + ) ); + + $this->assertTrue( is_null( $notification->buttons ) ); + } + +} diff --git a/tests/notifications/tests-notifications-local.php b/tests/notifications/tests-notifications-local.php new file mode 100644 index 00000000000..462a261fcd4 --- /dev/null +++ b/tests/notifications/tests-notifications-local.php @@ -0,0 +1,57 @@ +notifications->maybe_add_local_notification( + array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'error', + 'dismissed' => 0, + 'remote_id' => 'local', + ) + ); + + $notifications = EDD()->notifications->getActiveNotifications(); + + $this->assertSame( 1, count( $notifications ) ); + $this->assertTrue( $notifications[0] instanceof Notification ); + $this->assertSame( 'Notification', $notifications[0]->title ); + } + + public function test_add_local_notification_numeric_remote_id_returns_false() { + $notification = EDD()->notifications->maybe_add_local_notification( + array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'error', + 'dismissed' => 0, + 'remote_id' => 1, + ) + ); + + $this->assertFalse( $notification ); + } + + public function test_add_local_notification_missing_remote_id_returns_false() { + $notification = EDD()->notifications->maybe_add_local_notification( + array( + 'title' => 'Notification', + 'content' => 'Notification', + 'type' => 'error', + ) + ); + + $this->assertFalse( $notification ); + } +} diff --git a/tests/orders/tests-edd-payment.php b/tests/orders/tests-edd-payment.php new file mode 100644 index 00000000000..0b4dc7c57f6 --- /dev/null +++ b/tests/orders/tests-edd-payment.php @@ -0,0 +1,1025 @@ +payment = edd_get_payment( $payment_id ); + } + + public function tearDown(): void { + parent::tearDown(); + + Helpers\EDD_Helper_Payment::delete_payment( $this->payment->ID ); + + edd_destroy_order( $this->payment->ID ); + + $component = edd_get_component_interface( 'order', 'meta' ); + + if ( $component instanceof \EDD\Database\Table ) { + $component->truncate(); + } + + $this->payment = null; + } + + public function test_IDs() { + $this->assertSame( $this->payment->_ID, $this->payment->ID ); + } + + public function test_saving_updated_ID() { + $expected = $this->payment->ID; + + $this->payment->ID = 12121222; + $this->payment->save(); + + $this->assertSame( $expected, $this->payment->ID ); + } + + public function test_EDD_Payment_total() { + $this->assertEquals( 120.00, $this->payment->total ); + } + + public function test_edd_get_payment_by_transaction_ID_should_be_true() { + $payment = edd_get_payment( 'EDD_ORDER', true ); + + $this->assertSame( $payment->ID, $this->payment->ID ); + $this->assertSame( 'EDD_ORDER', $payment->transaction_id ); + } + + public function test_edd_get_payment_by_transaction_ID_for_guest_payment_should_be_true() { + $payment_id = Helpers\EDD_Helper_Payment::create_simple_guest_payment(); + + $payment = edd_get_payment( 'EDD_GUEST_ORDER', true ); + + $this->assertSame( 'EDD_GUEST_ORDER', $payment->transaction_id ); + + Helpers\EDD_Helper_Payment::delete_payment( $payment_id ); + } + + public function test_instantiating_EDD_Payment_with_no_args_should_be_null() { + $payment = new \EDD_Payment(); + $this->assertEquals( NULL, $payment->ID ); + } + + public function test_edd_get_payment_with_no_args_should_be_false() { + $payment = edd_get_payment(); + + $this->assertFalse( $payment ); + } + + public function test_edd_get_payment_with_invalid_id_should_be_false() { + $payment = edd_get_payment( 99999999999 ); + + $this->assertFalse( $payment ); + } + + public function test_instantiating_EDD_Payment_with_invalid_transaction_id_should_be_null() { + $payment = new \EDD_Payment( 'false-txn', true ); + + $this->assertEquals( NULL, $payment->ID ); + } + + public function test_edd_get_payment_with_invalid_transaction_id_should_be_false() { + $payment = edd_get_payment( 'false-txn', true ); + + $this->assertFalse( $payment ); + } + + public function test_updating_payment_status_to_pending() { + $this->payment->update_status( 'pending' ); + $this->assertEquals( 'pending', $this->payment->status ); + $this->assertEquals( 'Pending', $this->payment->status_nicename ); + } + + public function test_updating_payment_status_to_publish() { + // Test backwards compat + edd_update_payment_status( $this->payment->ID, 'complete' ); + + // Need to get the payment again since it's been updated + $this->payment = edd_get_payment( $this->payment->ID ); + $this->assertEquals( 'complete', $this->payment->status ); + $this->assertEquals( 'Completed', $this->payment->status_nicename ); + } + + public function test_add_download() { + + // Test class vars prior to adding a download. + $this->assertEquals( 2, count( $this->payment->downloads ) ); + $this->assertEquals( 120.00, $this->payment->total ); + + $new_download = Helpers\EDD_Helper_Download::create_simple_download(); + + $this->payment->add_download( $new_download->ID ); + $this->payment->save(); + + $this->assertEquals( 3, count( $this->payment->downloads ) ); + $this->assertEquals( 140.00, $this->payment->total ); + } + + public function test_add_download_with_an_item_price_of_0() { + + // Test class vars prior to adding a download. + $this->assertEquals( 2, count( $this->payment->downloads ) ); + $this->assertEquals( 120.00, $this->payment->total ); + + $new_download = Helpers\EDD_Helper_Download::create_simple_download(); + + $args = array( + 'item_price' => 0, + ); + + $this->payment->add_download( $new_download->ID, $args ); + $this->payment->save(); + + $this->assertEquals( 3, count( $this->payment->downloads ) ); + $this->assertEquals( 120.00, $this->payment->total ); + } + + public function test_add_download_with_fee() { + $args = array( + 'fees' => array( + array( + 'amount' => 5, + 'label' => 'Test Fee', + ), + ), + ); + + $new_download = Helpers\EDD_Helper_Download::create_simple_download(); + + $this->payment->add_download( $new_download->ID, $args ); + $this->payment->save(); + + $this->assertFalse( empty( $this->payment->cart_details[2]['fees'] ) ); + } + + public function test_remove_download() { + $download_id = $this->payment->cart_details[0]['id']; + $amount = $this->payment->cart_details[0]['price']; + $quantity = $this->payment->cart_details[0]['quantity']; + + $remove_args = array( + 'amount' => $amount, + 'quantity' => $quantity, + ); + + $this->payment->remove_download( $download_id, $remove_args ); + $this->payment->save(); + + $this->assertEquals( 1, count( $this->payment->downloads ) ); + $this->assertEquals( 100.00, $this->payment->total ); + } + + public function test_remove_download_by_index() { + $download_id = $this->payment->cart_details[1]['id']; + + $remove_args = array( + 'cart_index' => 1, + ); + + $this->payment->remove_download( $download_id, $remove_args ); + $this->payment->save(); + + $this->assertEquals( 1, count( $this->payment->downloads ) ); + $this->assertEquals( 20.00, $this->payment->total ); + } + + public function test_remove_download_with_quantity() { + global $edd_options; + + $edd_options['item_quantities'] = true; + + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment_with_quantity_tax(); + + $payment = edd_get_payment( $payment_id ); + + $testing_index = 1; + $download_id = $payment->cart_details[ $testing_index ]['id']; + + $remove_args = array( + 'quantity' => 1, + ); + + $payment->remove_download( $download_id, $remove_args ); + $payment->save(); + + $payment = edd_get_payment( $payment_id ); + + $this->assertEquals( 2, count( $payment->downloads ) ); + $this->assertEquals( 1, $payment->cart_details[ $testing_index ]['quantity'] ); + $this->assertEquals( 140.00, $payment->subtotal ); + $this->assertEquals( 12, $payment->tax ); + $this->assertEquals( 152.00, $payment->total ); + + Helpers\EDD_Helper_Payment::delete_payment( $payment_id ); + unset( $edd_options['item_quantities'] ); + } + + public function test_payment_add_fee() { + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee 1', + ) ); + + $this->assertEquals( 1, count( $this->payment->fees ) ); + $this->assertEquals( 125, $this->payment->total ); + + $this->payment->save(); + + $this->payment = edd_get_payment( $this->payment->ID ); + + $this->assertEquals( 5, $this->payment->fees_total ); + $this->assertEquals( 125, $this->payment->total ); + + // Test backwards compatibility with _edd_payment_meta. + $payment_meta = edd_get_payment_meta( $this->payment->ID, '_edd_payment_meta', true ); + $this->assertArrayHasKey( 'fees', $payment_meta ); + + $fees = $payment_meta['fees']; + $this->assertEquals( 1, count( $fees ) ); + } + + public function test_user_info() { + $this->assertSame( 'Admin', $this->payment->first_name ); + $this->assertSame( 'User', $this->payment->last_name ); + } + + public function test_for_serialized_user_info() { + + // Issue #4248 + $this->payment->user_info = serialize( array( + 'first_name' => 'John', + 'last_name' => 'Doe', + ) ); + + $this->payment->save(); + + $this->assertIsArray( $this->payment->user_info ); + + foreach ( $this->payment->user_info as $key => $value ) { + $this->assertFalse( is_serialized( $value ), $key . ' returned a searlized value' ); + } + } + + public function test_modify_amount() { + $args = array( + 'item_price' => '1,001.95', + ); + + $this->payment->modify_cart_item( 0, $args ); + $this->payment->save(); + + $this->assertEquals( 1001.95, $this->payment->cart_details[0]['price'] ); + } + + public function test_payment_remove_fee() { + for ( $i = 0; $i <= 2; $i++ ) { + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee ' . $i, + 'type' => 'fee', + ) ); + } + + $this->payment->save(); + + $this->assertEquals( 3, count( $this->payment->fees ) ); + $this->assertEquals( 'Test Fee 1', $this->payment->fees[1]['label'] ); + $this->assertEquals( 135, $this->payment->total ); + + $this->payment->remove_fee( 1 ); + $this->payment->save(); + + $this->assertEquals( 2, count( $this->payment->fees ) ); + $this->assertEquals( 130, $this->payment->total ); + $this->assertEquals( 'Test Fee 2', $this->payment->fees[1]['label'] ); + + // Test that it saves to the DB + $fees = edd_get_order_adjustments( + array( + 'object_id' => $this->payment->ID, + 'object_type' => 'order', + 'type' => 'fee', + 'order' => 'ASC', + ) + ); + + $this->assertEquals( 2, count( $fees ) ); + $fee = $fees[1]; + $this->assertEquals( 'Test Fee 2', $fee->description ); + } + + public function test_payment_remove_fee_by_index() { + for ( $i = 0; $i <= 2; $i++ ) { + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee ' . $i, + 'type' => 'fee', + ) ); + } + + $this->payment->save(); + + $this->assertEquals( 3, count( $this->payment->fees ) ); + $this->assertEquals( 'Test Fee 1', $this->payment->fees[1]['label'] ); + $this->assertEquals( 135, $this->payment->total ); + + $this->payment->remove_fee_by( 'index', 1, true ); + $this->payment->save(); + + $this->assertEquals( 2, count( $this->payment->fees ) ); + $this->assertEquals( 130, $this->payment->total ); + $this->assertEquals( 'Test Fee 2', $this->payment->fees[1]['label'] ); + + // Test that it saves to the DB + $payment_meta = edd_get_payment_meta( $this->payment->ID, '_edd_payment_meta', true ); + + $this->assertArrayHasKey( 'fees', $payment_meta ); + + $fees = $payment_meta['fees']; + + $this->assertEquals( 2, count( $fees ) ); + $this->assertEquals( 'Test Fee 2', $fees[2]['label'] ); + } + + public function test_payment_remove_fee_by_label_should_be_empty() { + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee', + 'type' => 'fee', + ) ); + + $this->assertEquals( 1, count( $this->payment->fees ) ); + $this->assertEquals( 'Test Fee', $this->payment->fees[0]['label'] ); + $this->payment->save(); + + $this->payment->remove_fee_by( 'label', 'Test Fee' ); + $this->assertEmpty( $this->payment->fees ); + $this->assertEquals( 120, $this->payment->total ); + $this->payment->save(); + + // Test that it saves to the DB + $payment_meta = edd_get_payment_meta( $this->payment->ID, '_edd_payment_meta', true ); + $this->assertArrayHasKey( 'fees', $payment_meta ); + + $fees = $payment_meta['fees']; + $this->assertEmpty( $fees ); + } + + public function test_payment_remove_fee_by_label_w_multi_no_global() { + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee', + 'type' => 'fee', + ) ); + + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee', + 'type' => 'fee', + ) ); + + $this->assertEquals( 2, count( $this->payment->fees ) ); + $this->assertEquals( 'Test Fee', $this->payment->fees[0]['label'] ); + $this->payment->save(); + + $this->payment->remove_fee_by( 'label', 'Test Fee' ); + $this->assertEquals( 1, count( $this->payment->fees ) ); + $this->assertEquals( 125, $this->payment->total ); + $this->payment->save(); + + $payment_meta = edd_get_payment_meta( $this->payment->ID, '_edd_payment_meta', true ); + $this->assertArrayHasKey( 'fees', $payment_meta ); + + $fees = $payment_meta['fees']; + $this->assertEquals( 1, count( $fees ) ); + } + + public function test_payment_remove_fee_by_label_w_multi_w_global() { + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee', + 'type' => 'fee', + ) ); + + $this->payment->add_fee( array( + 'amount' => 5, + 'label' => 'Test Fee', + 'type' => 'fee', + ) ); + + $this->assertEquals( 2, count( $this->payment->fees ) ); + $this->assertEquals( 'Test Fee', $this->payment->fees[0]['label'] ); + $this->payment->save(); + + $this->payment->remove_fee_by( 'label', 'Test Fee', true ); + $this->assertEmpty( $this->payment->fees ); + $this->assertEquals( 120, $this->payment->total ); + $this->payment->save(); + + $payment_meta = edd_get_payment_meta( $this->payment->ID, '_edd_payment_meta', true ); + $this->assertArrayHasKey( 'fees', $payment_meta ); + $this->assertEmpty( $payment_meta['fees'] ); + } + + public function test_payment_with_initial_fee() { + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment_with_fee(); + + $payment = edd_get_payment( $payment_id ); + + $this->assertFalse( empty( $payment->fees ) ); + $this->assertEquals( 47, $payment->total ); + } + + public function test_update_date_future() { + $current_date = $this->payment->date; + + $new_date = strtotime( $this->payment->date ) + DAY_IN_SECONDS; + $this->payment->date = date( 'Y-m-d H:i:s', $new_date ); + $this->payment->save(); + + $date2 = strtotime( $this->payment->date ); + $this->assertEquals( $new_date, $date2 ); + } + + public function test_update_date_past() { + $new_date = strtotime( $this->payment->date ) - DAY_IN_SECONDS; + + $this->payment->date = date( 'Y-m-d H:i:s', $new_date ); + $this->payment->save(); + + $date2 = strtotime( $this->payment->date ); + $this->assertEquals( $new_date, $date2 ); + } + + public function test_refund_payment() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $download = new \EDD_Download( $this->payment->downloads[0]['id'] ); + $earnings = $download->earnings; + $sales = $download->sales; + + $store_earnings = edd_get_total_earnings(); + $store_sales = edd_get_total_sales(); + + $this->payment->refund(); + + wp_cache_flush(); + + $this->assertEquals( 'refunded', $this->payment->status ); + + $download2 = new \EDD_Download( $download->ID ); + + $this->assertEquals( $earnings - $download->price, $download2->earnings ); + $this->assertEquals( $sales - 1, $download2->sales ); + + $this->assertEquals( $store_earnings - $this->payment->total, edd_get_total_earnings() ); + $this->assertEquals( $store_sales - 1, edd_get_total_sales() ); + } + + /** + * @expectEDDeprecated edd_undo_purchase_on_refund + */ + public function test_refund_payment_legacy() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $download = new \EDD_Download( $this->payment->downloads[0]['id'] ); + $earnings = $download->earnings; + $sales = $download->sales; + + edd_undo_purchase_on_refund( $this->payment->ID, 'refunded', 'complete' ); + + wp_cache_flush(); + + $payment = edd_get_payment( $this->payment->ID ); + $this->assertEquals( 'refunded', $payment->status ); + + $download2 = new \EDD_Download( $download->ID ); + + $this->assertEquals( $earnings - $download->price, $download2->earnings ); + $this->assertEquals( $sales - 1, $download2->sales ); + } + + public function test_modifying_address() { + $new_address = array( + 'line1' => '123 Main St', + 'line2' => '', + 'city' => 'New York City', + 'state' => 'New York', + 'zip' => '10010', + 'country' => 'US', + ); + $this->payment->address = $new_address; + $this->payment->save(); + + $this->assertEquals( $new_address, $this->payment->address ); + } + + public function test_modify_cart_item_price() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $this->payment->modify_cart_item( 0, array( 'item_price' => 1 ) ); + $this->payment->save(); + + $this->assertEquals( 1, $this->payment->cart_details[0]['item_price'] ); + + $download = new \EDD_Download( $this->payment->cart_details[0]['id'] ); + $this->assertEquals( 1.00, $download->get_earnings() ); + } + + public function test_modify_cart_item_quantity() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $this->payment->modify_cart_item( 0, array( + 'quantity' => 3, + 'item_price' => 1, + ) ); + $this->payment->save(); + + $this->assertEquals( 3, $this->payment->cart_details[0]['quantity'] ); + $this->assertEquals( 3, $this->payment->cart_details[0]['price'] ); + + $download = new \EDD_Download( $this->payment->cart_details[0]['id'] ); + $this->assertEquals( 3.00, $download->get_earnings() ); + $this->assertEquals( 3, $download->get_sales() ); + } + + public function test_modify_cart_item_tax() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $this->payment->modify_cart_item( 0, array( 'tax' => 2 ) ); + $this->payment->save(); + + $this->assertEquals( 2, $this->payment->cart_details[0]['tax'] ); + $this->assertEquals( 2, $this->payment->tax ); + + $this->payment->modify_cart_item( 0, array( 'tax' => 0 ) ); + $this->payment->save(); + + $this->assertEquals( 0, $this->payment->cart_details[0]['tax'] ); + $this->assertEquals( 0, $this->payment->tax ); + } + + public function test_modify_cart_item_discount() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $original_total = $this->payment->cart_details[0]['price']; + $this->payment->modify_cart_item( 0, array( 'discount' => 1 ) ); + $this->payment->save(); + + $this->assertEquals( 1, $this->payment->cart_details[0]['discount'] ); + $this->assertEquals( $original_total - 1, $this->payment->cart_details[0]['price'] ); + $this->assertEquals( 1, $this->payment->discounted_amount ); + } + + public function test_modify_cart_item_with_disallowed_changes_should_return_false() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $change_permitted = $this->payment->modify_cart_item( 0, array( + 'quantity' => $this->payment->cart_details[0]['quantity'], + 'item_price' => $this->payment->cart_details[0]['price'], + ) ); + + $this->assertFalse( $change_permitted ); + } + + public function test_filtering_payment_meta() { + add_filter( 'edd_payment_meta', array( $this, 'alter_payment_meta' ), 10, 2 ); + + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + + remove_filter( 'edd_payment_meta', array( $this, 'alter_payment_meta' ), 10, 2 ); + + $payment = edd_get_payment( $payment_id ); + + $this->assertEquals( 'PL', $payment->payment_meta['user_info']['address']['country'] ); + } + + /** + * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/5228 + */ + public function test_issue_5228_data() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + $this->setExpectedIncorrectUsage( 'add_post_meta()/update_post_meta()' ); + + $meta = $this->payment->get_meta(); + + $meta[0] = array(); + $meta[0]['test'] = 'Test Value'; + + update_post_meta( $this->payment->ID, '_edd_payment_meta', $meta ); + + $direct_meta = get_post_meta( $this->payment->ID, '_edd_payment_meta', $meta ); + $this->assertTrue( isset( $direct_meta ) ); + + $payment = edd_get_payment( $this->payment->ID ); + $meta = $payment->get_meta(); + + $this->assertFalse( isset( $meta[0] ) ); + $this->assertTrue( isset( $meta['test'] ) ); + $this->assertEquals( 'Test Value', $meta['test'] ); + } + + public function test_user_id_mismatch() { + $this->setExpectedIncorrectUsage( 'add_post_meta()/update_post_meta()' ); + + update_post_meta( $this->payment->ID, '_edd_payment_user_id', 99999 ); + $payment = edd_get_payment( $this->payment->ID ); + $customer = edd_get_customer( $payment->customer_id ); + + $this->assertEquals( $payment->user_id, $customer->user_id ); + } + + public function test_pending_affecting_stats() { + $this->payment->status = 'complete'; + $this->payment->save(); + + $customer = new \EDD_Customer( $this->payment->customer_id ); + $download = new \EDD_Download( $this->payment->downloads[0]['id'] ); + + $customer_sales = $customer->purchase_count; + $customer_earnings = $customer->purchase_value; + + $download_sales = $download->sales; + $download_earnings = $download->earnings; + + $store_earnings = edd_get_total_earnings(); + $store_sales = edd_get_total_sales(); + + $this->payment->status = 'pending'; + $this->payment->save(); + wp_cache_flush(); + + $this->assertEmpty( $this->payment->completed_date ); + + $customer = new \EDD_Customer( $this->payment->customer_id ); + $download = new \EDD_Download( $this->payment->downloads[0]['id'] ); + + $this->assertEquals( $customer_earnings - $this->payment->total, $customer->purchase_value ); + $this->assertEquals( $customer_sales - 1, $customer->purchase_count ); + + $this->assertEquals( $download_earnings - $this->payment->cart_details[0]['price'], $download->earnings ); + $this->assertEquals( $download_sales - $this->payment->downloads[0]['quantity'], $download->sales ); + + $this->assertEquals( $store_earnings - $this->payment->total, edd_get_total_earnings() ); + $this->assertEquals( $store_sales - 1, edd_get_total_sales() ); + } + + public function test_refund_affecting_stats() { + + $this->payment->status = 'complete'; + $this->payment->save(); + + $customer = new \EDD_Customer( $this->payment->customer_id ); + $customer_sales = $customer->purchase_count; + $customer_earnings = $customer->purchase_value; + + $download = new \EDD_Download( $this->payment->downloads[0]['id'] ); + + $download_sales = $download->sales; + $download_earnings = $download->earnings; + + $store_earnings = edd_get_total_earnings(); + $store_sales = edd_get_total_sales(); + + $this->payment->refund(); + wp_cache_flush(); + + $download = new \EDD_Download( $this->payment->downloads[0]['id'] ); + $customer = edd_get_customer( $this->payment->customer_id ); + + $this->assertEquals( $customer_earnings - $this->payment->total, $customer->purchase_value ); + $this->assertEquals( $customer_sales - 1, $customer->purchase_count ); + + $this->assertEquals( $download_earnings - $this->payment->cart_details[0]['price'], $download->earnings ); + $this->assertEquals( $download_sales - $this->payment->downloads[0]['quantity'], $download->sales ); + + $this->assertEquals( $store_earnings - $this->payment->total, edd_get_total_earnings() ); + $this->assertEquals( $store_sales - 1, edd_get_total_sales() ); + } + + public function test_remove_with_multi_price_points_by_price_id() { + $download = Helpers\EDD_Helper_Download::create_variable_download_with_multi_price_purchase(); + $payment = new \EDD_Payment(); + + $payment->add_download( $download->ID, array( 'price_id' => 0 ) ); + $payment->add_download( $download->ID, array( 'price_id' => 1 ) ); + $payment->add_download( $download->ID, array( 'price_id' => 2 ) ); + $payment->add_download( $download->ID, array( 'price_id' => 3 ) ); + + $this->assertEquals( 4, count( $payment->downloads ) ); + $this->assertEquals( 620, $payment->total ); + + $payment->status = 'complete'; + $payment->save(); + + $payment->remove_download( $download->ID, array( 'price_id' => 1 ) ); + $payment->save(); + + $this->assertEquals( 3, count( $payment->downloads ) ); + + $this->assertEquals( 0, $payment->downloads[0]['options']['price_id'] ); + $this->assertEquals( 0, $payment->cart_details[0]['item_number']['options']['price_id'] ); + + $this->assertEquals( 2, $payment->downloads[1]['options']['price_id'] ); + $this->assertEquals( 2, $payment->cart_details[2]['item_number']['options']['price_id'] ); + + $this->assertEquals( 3, $payment->downloads[2]['options']['price_id'] ); + $this->assertEquals( 3, $payment->cart_details[3]['item_number']['options']['price_id'] ); + } + + public function test_remove_with_multi_price_points_by_cart_index() { + $download = Helpers\EDD_Helper_Download::create_variable_download_with_multi_price_purchase(); + $payment = new \EDD_Payment(); + + $payment->add_download( $download->ID, array( 'price_id' => 0 ) ); + $payment->add_download( $download->ID, array( 'price_id' => 1 ) ); + $payment->add_download( $download->ID, array( 'price_id' => 2 ) ); + $payment->add_download( $download->ID, array( 'price_id' => 3 ) ); + + $this->assertEquals( 4, count( $payment->downloads ) ); + $this->assertEquals( 620, $payment->total ); + + $payment->status = 'complete'; + $payment->save(); + + $payment->remove_download( $download->ID, array( 'cart_index' => 1 ) ); + $payment->remove_download( $download->ID, array( 'cart_index' => 2 ) ); + $payment->save(); + + $this->assertEquals( 2, count( $payment->downloads ) ); + + $this->assertEquals( 0, $payment->downloads[0]['options']['price_id'] ); + $this->assertEquals( 0, $payment->cart_details[0]['item_number']['options']['price_id'] ); + + $this->assertEquals( 3, $payment->downloads[1]['options']['price_id'] ); + $this->assertEquals( 3, $payment->cart_details[3]['item_number']['options']['price_id'] ); + } + + public function test_remove_with_multiple_same_price_by_price_id_different_prices() { + $download = Helpers\EDD_Helper_Download::create_variable_download_with_multi_price_purchase(); + $payment = new \EDD_Payment(); + + $payment->add_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 10, + ) ); + + $payment->add_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 20, + ) ); + + $payment->add_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 30, + ) ); + + $this->assertEquals( 3, count( $payment->downloads ) ); + $this->assertEquals( 60, $payment->total ); + + $payment->status = 'complete'; + $payment->save(); + + $payment->remove_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 20, + ) ); + + $payment->save(); + + $this->assertEquals( 2, count( $payment->downloads ) ); + + $this->assertEquals( 0, $payment->downloads[0]['options']['price_id'] ); + $this->assertEquals( 0, $payment->cart_details[0]['item_number']['options']['price_id'] ); + $this->assertEquals( 10, $payment->cart_details[0]['item_price'] ); + + $this->assertEquals( 0, $payment->downloads[1]['options']['price_id'] ); + $this->assertEquals( 0, $payment->cart_details[2]['item_number']['options']['price_id'] ); + $this->assertEquals( 30, $payment->cart_details[2]['item_price'] ); + } + + public function test_remove_with_multiple_same_price_by_price_id_same_prices() { + $download = Helpers\EDD_Helper_Download::create_variable_download_with_multi_price_purchase(); + $payment = new \EDD_Payment(); + + $payment->add_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 10, + ) ); + + $payment->add_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 10, + ) ); + + $payment->add_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 10, + ) ); + + $this->assertEquals( 3, count( $payment->downloads ) ); + $this->assertEquals( 30, $payment->total ); + + $payment->status = 'complete'; + $payment->save(); + + $payment->remove_download( $download->ID, array( + 'price_id' => 0, + 'item_price' => 10, + ) ); + + $payment->save(); + + $this->assertEquals( 2, count( $payment->downloads ) ); + + $this->assertEquals( 0, $payment->downloads[0]['options']['price_id'] ); + $this->assertEquals( 0, $payment->cart_details[1]['item_number']['options']['price_id'] ); + $this->assertEquals( 10, $payment->cart_details[1]['item_price'] ); + + $this->assertEquals( 0, $payment->downloads[1]['options']['price_id'] ); + $this->assertEquals( 0, $payment->cart_details[2]['item_number']['options']['price_id'] ); + $this->assertEquals( 10, $payment->cart_details[2]['item_price'] ); + + } + + /** + * Ensures that setting a dynamic tax rate to a payment saves that in order meta and can be retrieved. + * + * This is testing backwards compatibility, when in 2.x you could set a tax rate without a referencing + * ID. We need to ensure this is accessible in 3.x when using an order object. + */ + public function test_setting_dynamic_tax_rate_saves_rate_in_order_meta() { + $payment = $this->payment; + $payment->tax_rate = 0.2; + + $tax_amount = $payment->total * 0.2; + $payment->increase_tax( $tax_amount ); + $payment->save(); + + // Now fetch the order equivalent. + $order = edd_get_order( $payment->ID ); + + $this->assertEquals( 20, $order->get_tax_rate() ); + $this->assertEquals( $tax_amount, $order->tax ); + + $this->assertEquals( 20, edd_get_order_meta( $payment->ID, 'tax_rate', true ) ); + } + + /** + * In 2.x, tax rates are stored as decimals. In 3.x they are stored as percentages. When using + * EDD_Payment you should always get a decimal back. + */ + public function test_get_tax_rate_returns_decimal() { + $payment = new \EDD_Payment(); + $payment->tax_rate = 0.2; + $payment->total = 10; + + $tax_amount = $payment->total * 0.2; + $payment->increase_tax( $tax_amount ); + $payment->save(); + + // Fetch a new payment object. + $payment = edd_get_payment( $payment->ID ); + $this->assertEquals( 0.2, $payment->tax_rate ); + } + + /** + * This tests backwards compatibility in `edd_receipt_show_download_files()` when passing + * an array as the third parameter instead of the new `Order_Item` object. This test + * ensures that the function successfully converts that array to an order item object + * to be passed through to the filter `edd_order_receipt_show_download_files`. + * + * @covers \edd_receipt_show_download_files() + */ + public function test_receipt_show_download_files_converts_array_to_order_item() { + $payment = $this->payment; + $receipt_args = array( + 'id' => $payment->ID, + ); + $cart = edd_get_payment_meta_cart_details( $payment->ID, true ); + $cart_item = reset( $cart ); + $download_id = $cart_item['id']; + + // Test sending a payment item array to the filter, which will convert it to an \EDD\Orders\Order_Item object. + add_filter( 'edd_order_receipt_show_download_files', function( $ret, $item_id, $order_receipt_args, $order_item_object ) use ( $cart_item ) { + $this->assertInstanceOf( '\\EDD\\Orders\\Order_Item', $order_item_object ); + $this->assertTrue( $order_item_object->id === $cart_item['order_item_id'] ); + $this->assertTrue( $order_item_object->product_id === $cart_item['id'] ); + $this->assertTrue( (int) $order_item_object->product_id === (int) $item_id ); + + return $ret; + }, 10, 4 ); + $this->assertTrue( edd_receipt_show_download_files( $download_id, $receipt_args, $cart_item ) ); + } + + public function test_creating_complete_payment_marks_order_items_complete() { + $payment = new \EDD_Payment(); + $payment->first_name = 'John'; + $payment->last_name = 'Doe'; + $payment->user_id = 1; + $payment->email = 'johndoe@edd.local'; + $payment->total = 0; + $payment->gateway = 'manual'; + + $simple_download = Helpers\EDD_Helper_Download::create_simple_download(); + $payment->add_download( + $simple_download->ID, + array( + 'price_id' => false, + ) + ); + + // Update the payment status to mark the order as complete. + $payment->status = 'complete'; + $payment->save(); + + $order_items = edd_get_order_items( + array( + 'order_id' => $payment->ID, + ) + ); + $order_item = reset( $order_items ); + + $this->assertEquals( 'complete', $order_item->status ); + } + + public function test_inserting_payment_incomplete_data_returns_true() { + $payment = edd_insert_payment( + array( + 'customer_id' => 1, + 'user_id' => 1, + 'gateway' => 'manual', + 'total' => edd_sanitize_amount( sanitize_text_field( 20 ) ), + 'status' => 'pending', + 'user_info' => array( + 'email' => 'admin@edd.local', + ), + ) + ); + + $this->assertNotEmpty( $payment ); + } + + /* Helpers ***************************************************************/ + + public function alter_payment_meta( $meta, $payment_data ) { + $meta['user_info']['address']['country'] = 'PL'; + + return $meta; + } + + public function add_meta() { + $this->assertTrue( $this->payment->add_meta( '_test_add_payment_meta', 'test' ) ); + } + + public function add_meta_false_empty_key() { + $this->assertFalse( $this->payment->add_meta( '', 'test' ) ); + } + + public function add_meta_unique_false() { + $this->assertFalse( $this->payment->add_meta( '_edd_payment_key', 'test', true ) ); + } + + public function delete_meta() { + $this->assertTrue( $this->payment->delete_meta( '_edd_payment_key' ) ); + } + + public function delete_meta_no_key() { + $this->assertFalse( $this->payment->delete_meta( '' ) ); + } + + public function delete_meta_missing_key() { + $this->assertFalse( $this->payment->delete_meta( '_edd_nonexistant_key' ) ); + } +} diff --git a/tests/orders/tests-order-addresses.php b/tests/orders/tests-order-addresses.php new file mode 100644 index 00000000000..9c3a6a3b6a3 --- /dev/null +++ b/tests/orders/tests-order-addresses.php @@ -0,0 +1,686 @@ +order_address->create_many( 5 ); + } + + /** + * @covers ::edd_update_order_address + */ + public function test_update_should_return_true() { + $success = edd_update_order_address( self::$order_addresses[0], array( + 'address' => 'Address Line 1', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_update_order_address + */ + public function test_address_object_after_update_should_return_true() { + edd_update_order_address( self::$order_addresses[0], array( + 'address' => 'Address Line 1', + ) ); + + $order_address = edd_get_order_address( self::$order_addresses[0] ); + + $this->assertSame( 'Address Line 1', $order_address->address ); + } + + /** + * @covers ::edd_update_order_address + */ + public function test_update_without_id_should_fail() { + $success = edd_update_order_address( null, array( + 'email' => 'eddtest@edd.test', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_should_return_true() { + $success = edd_delete_order_address( self::$order_addresses[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_order_address( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_number_should_return_true() { + $orders = edd_get_order_addresses( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_offset_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_id_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->id < $order_addresses[1]->id ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_id_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->id > $order_addresses[1]->id ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_id__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_order_id_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'order_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->order_id < $order_addresses[1]->order_id ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_order_id_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'order_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->order_id > $order_addresses[1]->order_id ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_order_id__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'order_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_order_id__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'order_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_name_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'name', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->name < $order_addresses[1]->name ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_name_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'name', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->name > $order_addresses[1]->name ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_name__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'name__in' => array( + 'name' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_name__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'name__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_address_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'address', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->address < $order_addresses[1]->address ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_address_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'address', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->address > $order_addresses[1]->address ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_address__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'address__in' => array( + 'address' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_address__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'address__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_address2_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'address2', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->address2 < $order_addresses[1]->address2 ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_address2_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'address2', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->address2 > $order_addresses[1]->address2 ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_address2__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'address2__in' => array( + 'address2' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_address2__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'address2__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_city_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'city', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->city < $order_addresses[1]->city ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_city_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'city', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->city > $order_addresses[1]->city ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_city__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'city__in' => array( + 'city' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_city__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'city__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_region_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'region', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->region < $order_addresses[1]->region ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_region_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'region', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->region > $order_addresses[1]->region ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_region__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'region__in' => array( + 'region' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_region__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'region__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_postal_code_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'postal_code', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->postal_code < $order_addresses[1]->postal_code ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_postal_code_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'postal_code', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->postal_code > $order_addresses[1]->postal_code ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_postal_code__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'postal_code__in' => array( + 'postal_code' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_postal_code__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'postal_code__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_country_and_order_asc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'country', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_addresses[0]->country < $order_addresses[1]->country ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_orderby_country_and_order_desc_should_return_true() { + $order_addresses = edd_get_order_addresses( array( + 'orderby' => 'country', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_addresses[0]->country > $order_addresses[1]->country ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_country__in_should_return_1() { + $order_addresses = edd_get_order_addresses( array( + 'country__in' => array( + 'country' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_country__not_in_should_return_5() { + $order_addresses = edd_get_order_addresses( array( + 'country__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_addresses ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_id_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_order_id_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'order_id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_name_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'name' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_address_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'address' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_address2_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'address2' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_city_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'city' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_region_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'region' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_postal_code_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'postal_code' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_country_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'country' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_date_created_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'date_created' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_addresses + */ + public function test_get_order_addresses_with_invalid_date_modified_should_return_0() { + $transactions = edd_get_order_addresses( array( + 'date_modified' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } + + public function test_add_order_address_identical_should_return_false() { + $order_address = edd_get_order_address( self::$order_addresses[0] ); + $data = (array) $order_address; + unset( $data['id'] ); + + $this->assertFalse( edd_add_order_address( $data ) ); + } + + public function test_add_order_address_different_should_update_address() { + $order_address = edd_get_order_address( self::$order_addresses[0] ); + $data = array( + 'order_id' => $order_address->order_id, + 'city' => 'A Totally New City', + ); + + $new_order_address_id = edd_add_order_address( $data ); + $this->assertFalse( $new_order_address_id ); + + $new_order_address = edd_get_order_address( self::$order_addresses[0] ); + $this->assertSame( 'A Totally New City', $new_order_address->city ); + } +} diff --git a/tests/orders/tests-order-adjustment-meta.php b/tests/orders/tests-order-adjustment-meta.php new file mode 100644 index 00000000000..6b724dda391 --- /dev/null +++ b/tests/orders/tests-order-adjustment-meta.php @@ -0,0 +1,112 @@ +order_adjustment->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'order_adjustment', 'meta' )->truncate(); + } + + /** + * @covers ::edd_add_order_adjustment_meta + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( edd_add_order_adjustment_meta( self::$order_adjustment->id, '', '' ) ); + } + + /** + * @covers ::edd_add_order_adjustment_meta + */ + public function test_add_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_order_adjustment_meta( self::$order_adjustment->id, 'test_key', '' ) ); + } + + /** + * @covers ::edd_add_order_adjustment_meta + */ + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_order_adjustment_meta( self::$order_adjustment->id, 'test_key', '1' ) ); + } + + /** + * @covers ::edd_update_order_adjustment_meta + */ + public function test_update_metadata_with_empty_key_value_should_return_false() { + $this->assertEmpty( edd_update_order_adjustment_meta( self::$order_adjustment->id, '', '' ) ); + } + + /** + * @covers ::edd_update_order_adjustment_meta + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', '' ) ); + } + + /** + * @covers ::edd_update_order_adjustment_meta + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', '1' ) ); + } + + /** + * @covers ::edd_get_order_adjustment_meta + */ + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertEmpty( edd_get_order_adjustment_meta( self::$order_adjustment->id, '' ) ); + } + + /** + * @covers ::edd_get_order_adjustment_meta + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertEmpty( edd_get_order_adjustment_meta( self::$order_adjustment->id, 'key_that_does_not_exist', true ) ); + edd_update_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', '1' ); + $this->assertEquals( '1', edd_get_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', false ) ); + } + + /** + * @covers ::edd_get_order_adjustment_meta + */ + public function test_get_metadata_after_update_should_return_1_and_be_of_type_array() { + edd_update_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', '1' ); + + $this->assertEquals( '1', edd_get_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_order_adjustment_meta( self::$order_adjustment->id, 'test_key_2', false ) ); + } + + /** + * @covers ::edd_delete_order_adjustment_meta + */ + public function test_delete_metadata_after_update() { + edd_update_order_adjustment_meta( self::$order_adjustment->id, 'test_key', '1' ); + + $this->assertTrue( edd_delete_order_adjustment_meta( self::$order_adjustment->id, 'test_key' ) ); + $this->assertFalse( edd_delete_order_adjustment_meta( self::$order_adjustment->id, 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/orders/tests-order-adjustments.php b/tests/orders/tests-order-adjustments.php new file mode 100644 index 00000000000..2ce4fc69bc2 --- /dev/null +++ b/tests/orders/tests-order-adjustments.php @@ -0,0 +1,607 @@ +order_adjustment->create_many( 5 ); + } + + /** + * @covers ::edd_update_order_adjustment() + */ + public function test_update_should_return_true() { + $success = edd_update_order_adjustment( self::$order_adjustments[0], array( + 'type' => 'Adjustment Type ' . \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_update_order_adjustment() + */ + public function test_order_object_after_update_should_return_true() { + edd_update_order_adjustment( self::$order_adjustments[0], array( + 'description' => 'Adjustment Description 999', + ) ); + + $order_adjustment = edd_get_order_adjustment( self::$order_adjustments[0] ); + + $this->assertSame( 'Adjustment Description 999', $order_adjustment->description ); + } + + /** + * @covers ::edd_update_order_adjustment + */ + public function test_update_without_id_should_fail() { + $success = edd_update_order_adjustment( null, array( + 'description' => 'Adjustment Description 999', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_delete_order_adjustment + */ + public function test_delete_should_return_true() { + $success = edd_delete_order_adjustment( self::$order_adjustments[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_delete_order_adjustment + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_order_adjustment( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_number_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_offset_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_id_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->id < $order_adjustments[1]->id ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_id_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->id > $order_adjustments[1]->id ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_object_id_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'object_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->object_id < $order_adjustments[1]->object_id ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_object_id_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'object_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->object_id > $order_adjustments[1]->object_id ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_object_id_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'object_id' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_object_id__in_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'object_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_object_id__not_in_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'object_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_object_type_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'object_type' => 'order', + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_type_id_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'type_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->type_id < $order_adjustments[1]->type_id ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_type_id_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'type_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->type_id > $order_adjustments[1]->type_id ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_type_id_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'type_id' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_type_id__in_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'type_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_type_id__not_in_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'type_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_type_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'type', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->type < $order_adjustments[1]->type ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_type_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'type', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->type > $order_adjustments[1]->type ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_type_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'type' => 'Adjustment Type ' . \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_type__in_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'type__in' => array( + 'Adjustment Type ' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_type__not_in_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'type__not_in' => array( + 'Adjustment Type ' . 999, + ), + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_description_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'description', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->description < $order_adjustments[1]->description ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_description_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'description', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->description > $order_adjustments[1]->description ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_description_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'description' => 'Adjustment Description ' . \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_description__in_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'description__in' => array( + 'Adjustment Description ' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_description__not_in_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'description__not_in' => array( + 'Adjustment Description ' . 999, + ), + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_subtotal_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'subtotal', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->subtotal < $order_adjustments[1]->subtotal ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_subtotal_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'subtotal', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->subtotal > $order_adjustments[1]->subtotal ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_subtotal_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'subtotal' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_subtotal__in_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'subtotal__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_subtotal__not_in_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'subtotal__not_in' => array( + -999, + ), + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_tax_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'tax', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->tax < $order_adjustments[1]->tax ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_tax_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'tax', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->tax > $order_adjustments[1]->tax ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_tax_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'tax' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_tax__in_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'tax__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_tax__not_in_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'tax__not_in' => array( + -999, + ), + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_total_and_order_asc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'total', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_adjustments[0]->total < $order_adjustments[1]->total ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_orderby_total_and_order_desc_should_return_true() { + $order_adjustments = edd_get_order_adjustments( array( + 'orderby' => 'total', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_adjustments[0]->total > $order_adjustments[1]->total ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_total_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'total' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_total__in_should_return_1() { + $order_adjustments = edd_get_order_adjustments( array( + 'total__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_adjustments ); + } + + /** + * @covers ::edd_get_order_adjustments + */ + public function test_get_order_adjustments_with_total__not_in_should_return_5() { + $order_adjustments = edd_get_order_adjustments( array( + 'total__not_in' => array( + -999, + ), + ) ); + + $this->assertCount( 5, $order_adjustments ); + } + + /** + * @covers ::edd_add_order_adjustment + */ + public function test_order_adjustment_with_null_type_id_returns_null() { + $adjustment_id = edd_add_order_adjustment( array( + 'object_id' => 1, + 'object_type' => 'order', + 'type_id' => null, + 'type' => 'fee', + 'description' => 'Custom Fee', + 'subtotal' => 5.00, + 'tax' => 0.00, + 'total' => 5.00, + ) ); + $adjustment = edd_get_order_adjustment( $adjustment_id ); + + $this->assertNull( $adjustment->type_id ); + } + + /** + * @covers ::edd_add_order_adjustment + */ + public function test_order_adjustment_with_no_type_key_returns_null() { + $adjustment_id = edd_add_order_adjustment( array( + 'object_id' => 1, + 'object_type' => 'order', + 'type_id' => null, + 'type' => 'fee', + 'description' => 'Custom Fee', + 'subtotal' => 5.00, + 'tax' => 0.00, + 'total' => 5.00, + ) ); + $adjustment = edd_get_order_adjustment( $adjustment_id ); + + $this->assertNull( $adjustment->type_key ); + } +} diff --git a/tests/orders/tests-order-item-meta.php b/tests/orders/tests-order-item-meta.php new file mode 100644 index 00000000000..abf4030920e --- /dev/null +++ b/tests/orders/tests-order-item-meta.php @@ -0,0 +1,112 @@ +order_item->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'order_item', 'meta' )->truncate(); + } + + /** + * @covers ::edd_add_order_meta + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( edd_add_order_meta( self::$order_item->id, '', '' ) ); + } + + /** + * @covers ::edd_add_order_meta + */ + public function test_add_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_order_meta( self::$order_item->id, 'test_key', '' ) ); + } + + /** + * @covers ::edd_add_order_meta + */ + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_order_meta( self::$order_item->id, 'test_key', '1' ) ); + } + + /** + * @covers ::edd_update_order_meta + */ + public function test_update_metadata_with_empty_key_value_should_return_false() { + $this->assertEmpty( edd_update_order_meta( self::$order_item->id, '', '' ) ); + } + + /** + * @covers ::edd_update_order_meta + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_order_meta( self::$order_item->id, 'test_key_2', '' ) ); + } + + /** + * @covers ::edd_update_order_meta + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_order_meta( self::$order_item->id, 'test_key_2', '1' ) ); + } + + /** + * @covers ::edd_get_order_meta + */ + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertEmpty( edd_get_order_meta( self::$order_item->id, '' ) ); + } + + /** + * @covers ::edd_get_order_meta + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertEmpty( edd_get_order_meta( self::$order_item->id, 'key_that_does_not_exist', true ) ); + edd_update_order_meta( self::$order_item->id, 'test_key_2', '1' ); + $this->assertEquals( '1', edd_get_order_meta( self::$order_item->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_order_meta( self::$order_item->id, 'test_key_2', false ) ); + } + + /** + * @covers ::edd_get_order_meta + */ + public function test_get_metadata_after_update_should_return_1_and_be_of_type_array() { + edd_update_order_meta( self::$order_item->id, 'test_key_2', '1' ); + + $this->assertEquals( '1', edd_get_order_meta( self::$order_item->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_order_meta( self::$order_item->id, 'test_key_2', false ) ); + } + + /** + * @covers ::edd_delete_order_meta + */ + public function test_delete_metadata_after_update() { + edd_update_order_meta( self::$order_item->id, 'test_key', '1' ); + + $this->assertTrue( edd_delete_order_meta( self::$order_item->id, 'test_key' ) ); + $this->assertFalse( edd_delete_order_meta( self::$order_item->id, 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/orders/tests-order-items.php b/tests/orders/tests-order-items.php new file mode 100644 index 00000000000..1d901ee2a23 --- /dev/null +++ b/tests/orders/tests-order-items.php @@ -0,0 +1,851 @@ +order_item->create_many( 5 ); + } + + /** + * @covers ::edd_update_order_item + */ + public function test_update_should_return_true() { + $success = edd_update_order_item( self::$order_items[0], array( + 'product_name' => 'Stripe', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_update_order_item + */ + public function test_order_object_after_update_should_return_true() { + edd_update_order_item( self::$order_items[0], array( + 'product_name' => 'Stripe', + ) ); + + $order_item = edd_get_order_item( self::$order_items[0] ); + + $this->assertSame( 'Stripe', $order_item->product_name ); + } + + /** + * @covers ::edd_update_order_item + */ + public function test_update_without_id_should_fail() { + $success = edd_update_order_item( null, array( + 'product_name' => 'Stripe', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_delete_order_item + */ + public function test_delete_should_return_true() { + $success = edd_delete_order_item( self::$order_items[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_delete_order_item + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_order_item( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_number_should_return_true() { + $orders = edd_get_order_items( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_offset_should_return_true() { + $orders = edd_get_order_items( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $orders ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_id_and_order_asc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_items[0]->id < $order_items[1]->id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_id_and_order_desc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_items[0]->id > $order_items[1]->id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_order_id_and_order_asc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'order_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_items[0]->order_id < $order_items[1]->order_id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_order_id_and_order_desc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'order_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_items[0]->order_id > $order_items[1]->order_id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_order_id_should_return_1() { + $order_items = edd_get_order_items( array( + 'order_id' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_order_id__in_should_return_1() { + $order_items = edd_get_order_items( array( + 'order_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_order_id__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'order_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_product_id_and_order_asc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'product_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_items[0]->product_id < $order_items[1]->product_id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_product_id_and_order_desc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'product_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_items[0]->product_id > $order_items[1]->product_id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_product_id_should_return_1() { + $order_items = edd_get_order_items( array( + 'product_id' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_product_id__in_should_return_1() { + $order_items = edd_get_order_items( array( + 'product_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_product_id__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'product_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_product_name_should_return_5() { + $order_items = edd_get_order_items( array( + 'product_name' => 'Test Product', + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_price_id_and_order_asc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'price_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_items[0]->price_id < $order_items[1]->price_id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_price_id_and_order_desc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'price_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_items[0]->price_id > $order_items[1]->price_id ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_price_id_should_return_1() { + $order_items = edd_get_order_items( array( + 'price_id' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_price_id__in_should_return_1() { + $order_items = edd_get_order_items( array( + 'price_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_price_id__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'price_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_cart_index_and_order_asc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'cart_index', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_items[0]->cart_index < $order_items[1]->cart_index ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_orderby_cart_index_and_order_desc_should_return_true() { + $order_items = edd_get_order_items( array( + 'orderby' => 'cart_index', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_items[0]->cart_index > $order_items[1]->cart_index ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_cart_index_should_return_1() { + $order_items = edd_get_order_items( array( + 'cart_index' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_cart_index__in_should_return_1() { + $order_items = edd_get_order_items( array( + 'cart_index__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_cart_index__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'cart_index__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_type_should_return_5() { + $order_items = edd_get_order_items( array( + 'type' => 'download', + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_status_should_return_5() { + $order_items = edd_get_order_items( array( + 'status' => 'inherit', + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_quantity_should_return_5() { + $order_items = edd_get_order_items( array( + 'quantity' => 1, + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_quantity__in_should_return_5() { + $order_items = edd_get_order_items( array( + 'quantity__in' => array( + 1, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_quantity__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'quantity__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_amount_should_return_5() { + $order_items = edd_get_order_items( array( + 'amount' => 20, + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_amount__in_should_return_5() { + $order_items = edd_get_order_items( array( + 'amount__in' => array( + 20, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_amount__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'amount__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_subtotal_should_return_5() { + $order_items = edd_get_order_items( array( + 'subtotal' => 20, + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_subtotal__in_should_return_5() { + $order_items = edd_get_order_items( array( + 'subtotal__in' => array( + 20, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_subtotal__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'subtotal__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_tax_should_return_5() { + $order_items = edd_get_order_items( array( + 'tax' => 5, + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_tax__in_should_return_5() { + $order_items = edd_get_order_items( array( + 'tax__in' => array( + 5, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_tax__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'tax__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_discount_should_return_5() { + $order_items = edd_get_order_items( array( + 'discount' => 5, + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_discount__in_should_return_5() { + $order_items = edd_get_order_items( array( + 'discount__in' => array( + 5, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_discount__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'discount__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_total_should_return_5() { + $order_items = edd_get_order_items( array( + 'total' => 20, + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_total__in_should_return_5() { + $order_items = edd_get_order_items( array( + 'total__in' => array( + 20, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_total__not_in_should_return_5() { + $order_items = edd_get_order_items( array( + 'total__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_id_should_return_0() { + $order_items = edd_get_order_items( array( + 'id' => 999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_order_id_should_return_0() { + $order_items = edd_get_order_items( array( + 'order_id' => 999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_product_id_should_return_0() { + $order_items = edd_get_order_items( array( + 'product_id' => 999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_product_name_should_return_0() { + $order_items = edd_get_order_items( array( + 'product_name' => 'Invalid Product Name', + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_price_id_should_return_0() { + $order_items = edd_get_order_items( array( + 'price_id' => 999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_cart_index_should_return_0() { + $order_items = edd_get_order_items( array( + 'cart_index' => 999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_type_should_return_0() { + $order_items = edd_get_order_items( array( + 'type' => 'invalid', + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_status_should_return_0() { + $order_items = edd_get_order_items( array( + 'status' => 'invalid', + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_quantity_should_return_0() { + $order_items = edd_get_order_items( array( + 'quantity' => 999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_amount_should_return_0() { + $order_items = edd_get_order_items( array( + 'amount' => 999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_subtotal_should_return_0() { + $order_items = edd_get_order_items( array( + 'subtotal' => -999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_discount_should_return_0() { + $order_items = edd_get_order_items( array( + 'discount' => -999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_tax_should_return_0() { + $order_items = edd_get_order_items( array( + 'tax' => -999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_total_should_return_0() { + $order_items = edd_get_order_items( array( + 'total' => -999, + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_date_created_should_return_0() { + $order_items = edd_get_order_items( array( + 'date_created' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::edd_get_order_items + */ + public function test_get_order_items_with_invalid_date_modified_should_return_0() { + $order_items = edd_get_order_items( array( + 'date_modified' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $order_items ); + } + + /** + * @covers ::get_fees + */ + public function test_get_fees_should_be_empty() { + $this->assertEmpty( edd_get_order_item( self::$order_items[0] )->get_fees() ); + } + + /** + * @covers ::is_deliverable + */ + public function test_order_item_marked_complete_is_deliverable_returns_true() { + edd_update_order_item( + self::$order_items[4], + array( + 'status' => 'complete', + ) + ); + + $order_item = edd_get_order_item( self::$order_items[4] ); + + $this->assertTrue( $order_item->is_deliverable() ); + } + + /** + * @covers ::is_deliverable + */ + public function test_order_item_marked_refunded_is_deliverable_returns_false() { + edd_update_order_item( + self::$order_items[4], + array( + 'status' => 'refunded', + ) + ); + + $order_item = edd_get_order_item( self::$order_items[4] ); + + $this->assertFalse( $order_item->is_deliverable() ); + } + + /** + * @covers edd_get_deliverable_order_item_statuses + */ + public function test_order_item_deliverable_statuses_includes_partially_refunded() { + $this->assertTrue( in_array( 'partially_refunded', edd_get_deliverable_order_item_statuses(), true ) ); + } +} diff --git a/tests/orders/tests-order-meta.php b/tests/orders/tests-order-meta.php new file mode 100644 index 00000000000..fd8448a670e --- /dev/null +++ b/tests/orders/tests-order-meta.php @@ -0,0 +1,112 @@ +order->create_and_get(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_get_component_interface( 'order', 'meta' )->truncate(); + } + + /** + * @covers ::edd_add_order_meta + */ + public function test_add_metadata_with_empty_key_value_should_return_false() { + $this->assertFalse( edd_add_order_meta( self::$order->id, '', '' ) ); + } + + /** + * @covers ::edd_add_order_meta + */ + public function test_add_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_order_meta( self::$order->id, 'test_key', '' ) ); + } + + /** + * @covers ::edd_add_order_meta + */ + public function test_add_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_add_order_meta( self::$order->id, 'test_key', '1' ) ); + } + + /** + * @covers ::edd_update_order_meta + */ + public function test_update_metadata_with_empty_key_value_should_return_false() { + $this->assertEmpty( edd_update_order_meta( self::$order->id, '', '' ) ); + } + + /** + * @covers ::edd_update_order_meta + */ + public function test_update_metadata_with_empty_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_order_meta( self::$order->id, 'test_key_2', '' ) ); + } + + /** + * @covers ::edd_update_order_meta + */ + public function test_update_metadata_with_key_value_should_not_be_empty() { + $this->assertNotEmpty( edd_update_order_meta( self::$order->id, 'test_key_2', '1' ) ); + } + + /** + * @covers ::edd_get_order_meta + */ + public function test_get_metadata_with_no_args_should_be_empty() { + $this->assertEmpty( edd_get_order_meta( self::$order->id, '' ) ); + } + + /** + * @covers ::edd_get_order_meta + */ + public function test_get_metadata_with_invalid_key_should_be_empty() { + $this->assertEmpty( edd_get_order_meta( self::$order->id, 'key_that_does_not_exist', true ) ); + edd_update_order_meta( self::$order->id, 'test_key_2', '1' ); + $this->assertEquals( '1', edd_get_order_meta( self::$order->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_order_meta( self::$order->id, 'test_key_2', false ) ); + } + + /** + * @covers ::edd_get_order_meta + */ + public function test_get_metadata_after_update_should_return_1_and_be_of_type_array() { + edd_update_order_meta( self::$order->id, 'test_key_2', '1' ); + + $this->assertEquals( '1', edd_get_order_meta( self::$order->id, 'test_key_2', true ) ); + $this->assertIsArray( edd_get_order_meta( self::$order->id, 'test_key_2', false ) ); + } + + /** + * @covers ::edd_delete_order_meta + */ + public function test_delete_metadata_after_update() { + edd_update_order_meta( self::$order->id, 'test_key', '1' ); + + $this->assertTrue( edd_delete_order_meta( self::$order->id, 'test_key' ) ); + $this->assertFalse( edd_delete_order_meta( self::$order->id, 'key_that_does_not_exist' ) ); + } +} diff --git a/tests/orders/tests-order-recovery.php b/tests/orders/tests-order-recovery.php new file mode 100644 index 00000000000..ccc2d059baa --- /dev/null +++ b/tests/orders/tests-order-recovery.php @@ -0,0 +1,177 @@ +generate_recoverable_order(); + EDD()->session->set( 'edd_resume_payment', self::$recoverable_payment_id ); + self::$recovered_payment_id = $this->generate_recovered_order(); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_destroy_order( self::$recoverable_payment_id ); + edd_destroy_order( self::$recovered_payment_id ); + wp_cache_flush(); + } + + public function test_recovering_payment_ids_match() { + $this->assertSame( self::$recoverable_payment_id, self::$recovered_payment_id ); + } + + public function test_recovering_payment_guest_to_guest() { + + $payment = edd_get_payment( self::$recovered_payment_id ); + $payment_customer = new \EDD_Customer( $payment->customer_id ); + $recovery_customer = new \EDD_Customer( 'batman@thebatcave.co' ); + + $this->assertSame( $payment_customer->id, $recovery_customer->id ); + } + + public function test_recovering_payment_gateway_change() { + $order = edd_get_order( self::$recovered_payment_id ); + + $this->assertEquals( 'stripe', $order->gateway ); + } + + private function generate_recoverable_order() { + $initial_purchase_data = array( + 'price' => 299.0, + 'date' => date( 'Y-m-d H:i:s' ), + 'user_email' => 'bruce@waynefoundation.org', + 'purchase_key' => '186c2fb5402d756487bd4b6192d59bc2', + 'currency' => 'USD', + 'downloads' => + array( + 0 => + array( + 'id' => '1906', + 'options' => + array( + 'price_id' => '1', + ), + 'quantity' => 1, + ), + ), + 'user_info' => + array( + 'id' => 0, + 'email' => 'bruce@waynefoundation.org', + 'first_name' => 'Bruce', + 'last_name' => 'Wayne', + 'discount' => 'none', + 'address' => + array(), + ), + 'cart_details' => + array( + 0 => + array( + 'name' => 'Test Product 1', + 'id' => '1906', + 'item_number' => + array( + 'id' => '1906', + 'options' => + array( + 'price_id' => '1', + ), + 'quantity' => 1, + ), + 'item_price' => 299.0, + 'quantity' => 1, + 'discount' => 0.0, + 'subtotal' => 299.0, + 'tax' => 0.0, + 'fees' => + array(), + 'price' => 299.0, + ), + ), + 'gateway' => 'paypal', + 'status' => 'pending', + ); + + return edd_insert_payment( $initial_purchase_data ); + } + + private function generate_recovered_order() { + $recovery_purchase_data = array( + 'price' => 299.0, + 'date' => '2017-08-15 18:10:37', + 'user_email' => 'batman@thebatcave.co', + 'purchase_key' => '4f2b5cda76c2a997996f4cf8b68255ed', + 'currency' => 'USD', + 'downloads' => + array( + 0 => + array( + 'id' => '1906', + 'options' => + array( + 'price_id' => '1', + ), + 'quantity' => 1, + ), + ), + 'user_info' => + array( + 'id' => 0, + 'email' => 'batman@thebatcave.co', + 'first_name' => 'Batman', + 'last_name' => '', + 'discount' => 'none', + 'address' => + array(), + ), + 'cart_details' => + array( + 0 => + array( + 'name' => 'Test Product 1', + 'id' => '1906', + 'item_number' => + array( + 'id' => '1906', + 'options' => + array( + 'price_id' => '1', + ), + 'quantity' => 1, + ), + 'item_price' => 299.0, + 'quantity' => 1, + 'discount' => 0.0, + 'subtotal' => 299.0, + 'tax' => 0.0, + 'fees' => + array(), + 'price' => 299.0, + ), + ), + 'gateway' => 'stripe', + 'status' => 'pending', + ); + + return edd_insert_payment( $recovery_purchase_data ); + } +} diff --git a/tests/orders/tests-order-status.php b/tests/orders/tests-order-status.php new file mode 100644 index 00000000000..9ea1fec3d94 --- /dev/null +++ b/tests/orders/tests-order-status.php @@ -0,0 +1,98 @@ +assertEquals( $expected, edd_get_complete_order_statuses() ); + } + + /** + * @covers edd_get_complete_order_statuses() + */ + public function test_get_complete_order_statuses_label() { + $statuses = edd_get_complete_order_statuses( true ); + + $this->assertEquals( 'Completed', $statuses['complete'] ); + } + + /** + * @covers edd_get_incomplete_order_statuses() + */ + public function test_get_incomplete_order_statuses() { + $expected = array( 'pending', 'abandoned', 'processing', 'failed', 'cancelled' ); + + $this->assertEquals( $expected, edd_get_incomplete_order_statuses() ); + } + + /** + * @covers edd_get_incomplete_order_statuses() + */ + public function test_get_incomplete_order_statuses_label() { + $statuses = edd_get_incomplete_order_statuses( true ); + + $this->assertEquals( 'Abandoned', $statuses['abandoned'] ); + } + + /** + * @covers edd_recoverable_order_statuses() + */ + public function test_recoverable_order_statuses() { + $expected = array( 'pending', 'abandoned', 'failed' ); + + $this->assertEquals( $expected, edd_recoverable_order_statuses() ); + } + + /** + * @covers edd_recoverable_order_statuses() + */ + public function test_recoverable_order_statuses_label() { + $statuses = edd_recoverable_order_statuses( true ); + + $this->assertEquals( 'Failed', $statuses['failed'] ); + } + + /** + * @covers edd_get_complete_order_statuses() + */ + public function test_order_status_complete_should_be_in_complete_status_array() { + $order = parent::edd()->order->create_and_get(); + + $this->assertTrue( in_array( $order->status, edd_get_complete_order_statuses(), true ) ); + } + + /** + * @covers edd_get_incomplete_order_statuses() + */ + public function test_order_status_complete_should_not_be_in_incomplete_status_array() { + $order = parent::edd()->order->create_and_get(); + + $this->assertFalse( in_array( $order->status, edd_get_incomplete_order_statuses(), true ) ); + } + + /** + * @covers \EDD\Orders\Order::is_recoverable() + */ + public function test_order_status_pending_is_recoverable() { + $order = parent::edd()->order->create_and_get(); + edd_update_order( $order->id, array( 'status' => 'pending' ) ); + $order = edd_get_order( $order->id ); + + $this->assertTrue( $order->is_recoverable() ); + } +} diff --git a/tests/orders/tests-order-transactions.php b/tests/orders/tests-order-transactions.php new file mode 100644 index 00000000000..cad816fe5ce --- /dev/null +++ b/tests/orders/tests-order-transactions.php @@ -0,0 +1,542 @@ +order_transaction->create_many( 5 ); + } + + /** + * @covers ::edd_update_order_transaction + */ + public function test_update_should_return_true() { + $success = edd_update_order_transaction( self::$order_transactions[0], array( + 'gateway' => 'stripe', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_update_order_transaction + */ + public function test_order_object_after_update_should_return_true() { + edd_update_order_transaction( self::$order_transactions[0], array( + 'gateway' => 'stripe', + ) ); + + $order_transaction = edd_get_order_transaction( self::$order_transactions[0] ); + + $this->assertSame( 'stripe', $order_transaction->gateway ); + } + + /** + * @covers ::edd_update_order_transaction + */ + public function test_update_without_id_should_fail() { + $success = edd_update_order_transaction( null, array( + 'gateway' => 'stripe', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_should_return_true() { + $success = edd_delete_order_transaction( self::$order_transactions[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_delete_order_transaction + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_order_transaction( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_number_should_return_true() { + $orders = edd_get_order_transactions( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_offset_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_id_and_order_asc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_transactions[0]->id < $order_transactions[1]->id ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_id_and_order_desc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_transactions[0]->id > $order_transactions[1]->id ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_id__not_in_should_return_5() { + $order_transactions = edd_get_order_transactions( array( + 'id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_object_id_and_order_asc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'object_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_transactions[0]->object_id < $order_transactions[1]->object_id ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_object_id_and_order_desc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'object_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_transactions[0]->object_id > $order_transactions[1]->object_id ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_object_id__in_should_return_1() { + $order_transactions = edd_get_order_transactions( array( + 'object_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_object_id__not_in_should_return_5() { + $order_transactions = edd_get_order_transactions( array( + 'object_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_object_type_and_order_asc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'object_type', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_transactions[0]->object_type < $order_transactions[1]->object_type ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_object_type_and_order_desc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'object_type', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_transactions[0]->object_type > $order_transactions[1]->object_type ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_object_type__in_should_return_1() { + $order_transactions = edd_get_order_transactions( array( + 'object_type__in' => array( + 'order' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_object_type__not_in_should_return_5() { + $order_transactions = edd_get_order_transactions( array( + 'object_type__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_transaction_id_and_order_asc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'transaction_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_transactions[0]->transaction_id < $order_transactions[1]->transaction_id ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_transaction_id_and_order_desc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'transaction_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_transactions[0]->transaction_id > $order_transactions[1]->transaction_id ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_transaction_id__in_should_return_1() { + $order_transactions = edd_get_order_transactions( array( + 'transaction_id__in' => array( + 'transaction' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_transaction_id__not_in_should_return_5() { + $order_transactions = edd_get_order_transactions( array( + 'transaction_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_gateway_and_order_asc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'gateway', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_transactions[0]->gateway < $order_transactions[1]->gateway ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_gateway_and_order_desc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'gateway', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_transactions[0]->gateway > $order_transactions[1]->gateway ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_gateway__in_should_return_1() { + $order_transactions = edd_get_order_transactions( array( + 'gateway__in' => array( + 'gateway' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_gateway__not_in_should_return_5() { + $order_transactions = edd_get_order_transactions( array( + 'gateway__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_status_and_order_asc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'status', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_transactions[0]->status < $order_transactions[1]->status ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_status_and_order_desc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'status', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_transactions[0]->status > $order_transactions[1]->status ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_status__in_should_return_1() { + $order_transactions = edd_get_order_transactions( array( + 'status__in' => array( + 'status' . \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_status__not_in_should_return_5() { + $order_transactions = edd_get_order_transactions( array( + 'status__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_total_and_order_asc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'total', + 'order' => 'asc', + ) ); + + $this->assertTrue( $order_transactions[0]->total < $order_transactions[1]->total ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_orderby_total_and_order_desc_should_return_true() { + $order_transactions = edd_get_order_transactions( array( + 'orderby' => 'total', + 'order' => 'desc', + ) ); + + $this->assertTrue( $order_transactions[0]->total > $order_transactions[1]->total ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_total__in_should_return_1() { + $order_transactions = edd_get_order_transactions( array( + 'total__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_total__not_in_should_return_5() { + $order_transactions = edd_get_order_transactions( array( + 'total__not_in' => array( + -999, + ), + ) ); + + $this->assertCount( 5, $order_transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_id_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_object_id_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'object_id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_object_type_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'object_type' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_transaction_id_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'transaction_id' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_gateway_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'gateway' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_status_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'status' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_total_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'total' => -999, + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_date_created_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'date_created' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } + + /** + * @covers ::edd_get_order_transactions + */ + public function test_get_order_transactions_with_invalid_date_modified_should_return_0() { + $transactions = edd_get_order_transactions( array( + 'date_modified' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $transactions ); + } +} diff --git a/tests/orders/tests-orders.php b/tests/orders/tests-orders.php new file mode 100644 index 00000000000..faad7459efe --- /dev/null +++ b/tests/orders/tests-orders.php @@ -0,0 +1,644 @@ +order->create_many( 5 ); + } + + /** + * @covers ::edd_update_order + */ + public function test_update_should_return_true() { + $success = edd_update_order( self::$orders[0], array( + 'gateway' => 'Stripe', + ) ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_update_order + */ + public function test_order_object_after_update_should_return_true() { + edd_update_order( self::$orders[0], array( + 'gateway' => 'Stripe', + ) ); + + $order = edd_get_order( self::$orders[0] ); + + $this->assertSame( 'Stripe', $order->gateway ); + } + + /** + * @covers ::edd_update_order + */ + public function test_update_without_id_should_fail() { + $success = edd_update_order( null, array( + 'gateway' => 'Stripe', + ) ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_delete_order + */ + public function test_delete_should_return_true() { + $success = edd_delete_order( self::$orders[0] ); + + $this->assertSame( 1, $success ); + } + + /** + * @covers ::edd_delete_order + */ + public function test_delete_should_delete_metadata() { + edd_add_order_meta( self::$orders[1], 'test_meta_key', 'test_meta_value', true ); + + // This assertion is added to ensure that metadata was, in fact, added to the order. + $this->assertEquals( 'test_meta_value', edd_get_order_meta( self::$orders[1], 'test_meta_key', true ) ); + + edd_delete_order( self::$orders[1] ); + $this->assertEmpty( edd_get_order_meta( self::$orders[1], 'test_meta_key', true ) ); + } + + /** + * @covers ::edd_delete_order + */ + public function test_delete_should_delete_metadata_non_unique() { + edd_add_order_meta( self::$orders[1], 'test_meta_key', '2', false ); + edd_add_order_meta( self::$orders[1], 'test_meta_key', '1', false ); + + // This assertion is added to ensure that metadata was, in fact, added to the order. + $this->assertEquals( array( 2, 1 ), edd_get_order_meta( self::$orders[1], 'test_meta_key', false ) ); + + edd_delete_order( self::$orders[1] ); + $this->assertEmpty( edd_get_order_meta( self::$orders[1], 'test_meta_key', false ) ); + } + + /** + * @covers ::edd_delete_order + */ + public function test_delete_without_id_should_fail() { + $success = edd_delete_order( '' ); + + $this->assertFalse( $success ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_number_should_return_true() { + $orders = edd_get_orders( array( + 'number' => 10, + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_offset_should_return_true() { + $orders = edd_get_orders( array( + 'number' => 10, + 'offset' => 4, + ) ); + + $this->assertCount( 1, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_id_and_order_asc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $orders[0]->id < $orders[1]->id ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_id_and_order_desc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $orders[0]->id > $orders[1]->id ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_user_id_and_order_asc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'user_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $orders[0]->user_id < $orders[1]->user_id ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_user_id_should_return_1() { + $orders = edd_get_orders( array( + 'user_id' => \WP_UnitTest_Generator_Sequence::$incr, + ) ); + + $this->assertCount( 1, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_user_id__in_should_return_1() { + $orders = edd_get_orders( array( + 'user_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_user_id__not_in_should_return_5() { + $orders = edd_get_orders( array( + 'user_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_user_id_and_order_desc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'user_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $orders[0]->user_id > $orders[1]->user_id ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_customer_id_and_order_asc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'customer_id', + 'order' => 'asc', + ) ); + + $this->assertTrue( $orders[0]->customer_id < $orders[1]->customer_id ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_customer_id_and_order_desc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'customer_id', + 'order' => 'desc', + ) ); + + $this->assertTrue( $orders[0]->customer_id > $orders[1]->customer_id ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_customer_id__in_should_return_1() { + $orders = edd_get_orders( array( + 'customer_id__in' => array( + \WP_UnitTest_Generator_Sequence::$incr, + ), + ) ); + + $this->assertCount( 1, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_customer_id__not_in_should_return_5() { + $orders = edd_get_orders( array( + 'customer_id__not_in' => array( + 999, + ), + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_email_and_order_asc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'email', + 'order' => 'asc', + ) ); + + $this->assertTrue( $orders[0]->email < $orders[1]->email ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_orderby_email_and_order_desc_should_return_true() { + $orders = edd_get_orders( array( + 'orderby' => 'email', + 'order' => 'desc', + ) ); + + $this->assertTrue( $orders[0]->email > $orders[1]->email ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_email__in_should_return_1() { + $orders = edd_get_orders( array( + 'email__in' => array( + 'user' . \WP_UnitTest_Generator_Sequence::$incr . '@edd.test', + ), + ) ); + + $this->assertCount( 1, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_email__not_in_should_return_5() { + $orders = edd_get_orders( array( + 'email__not_in' => array( + 'user999@edd.test', + ), + ) ); + + $this->assertCount( 5, $orders ); + } + + /** + * @covers \EDD\Database\Queries\Order::query_by_product + */ + public function test_get_orders_with_product_price_id_should_return_1() { + $items = edd_get_order_items( array( 'order_id' => self::$orders[0] ) ); + foreach( $items as $item ) { + edd_update_order_item( $item->id, array( + 'price_id' => 2 + ) ); + } + + $orders = edd_count_orders( array( + 'product_price_id' => 2 + ) ); + + $this->assertSame( 1, $orders ); + } + + /** + * @covers \EDD\Database\Queries\Order::query_by_product + */ + public function test_get_orders_with_product_price_id_0_should_return_1() { + $items = edd_get_order_items( array( 'order_id' => self::$orders[0] ) ); + foreach( $items as $item ) { + edd_update_order_item( $item->id, array( + 'price_id' => 0 + ) ); + } + + $orders = edd_get_orders( array( + 'product_price_id' => 0 + ) ); + + $this->assertCount( 1, $orders ); + } + + /** + * @covers \EDD\Database\Queries\Order::query_by_product + */ + public function get_get_orders_with_no_product_price_id_should_return_5() { + // Make sure all order items have price_id `null`. + $items = edd_get_order_items( array( 'order_id__in' => self::$orders ) ); + foreach( $items as $item ) { + edd_update_order_item( $item->id, array( + 'price_id' => null + ) ); + } + + // Now set one to `1`. + $items = edd_get_order_items( array( 'order_id' => self::$orders[0] ) ); + foreach( $items as $item ) { + edd_update_order_item( $item->id, array( + 'price_id' => 1 + ) ); + } + + // There should be 4 records at `null`, and 1 at `1`. + $this->assertSame( 4, edd_count_orders( array( 'product_price_id' => null ) ) ); + $this->assertSame( 1, edd_count_orders( array( 'product_price_id' => 1 ) ) ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_id_should_return_0() { + $orders = edd_get_orders( array( + 'id' => 999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_parent_should_return_0() { + $orders = edd_get_orders( array( + 'parent' => 999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_status_should_return_0() { + $orders = edd_get_orders( array( + 'status' => 'invalid_status', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_date_created_should_return_0() { + $orders = edd_get_orders( array( + 'date_created' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_date_modified_should_return_0() { + $orders = edd_get_orders( array( + 'date_modified' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_date_completed_should_return_0() { + $orders = edd_get_orders( array( + 'date_completed' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_date_refundable_should_return_0() { + $orders = edd_get_orders( array( + 'date_refundable' => '2250-01-01 23:59:59', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_user_id_should_return_0() { + $orders = edd_get_orders( array( + 'user_id' => 999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_customer_id_should_return_0() { + $orders = edd_get_orders( array( + 'customer_id' => 999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_email_should_return_0() { + $orders = edd_get_orders( array( + 'email' => 'invalid_email@domain.test', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_ip_should_return_0() { + $orders = edd_get_orders( array( + 'ip' => '255.255.255.255', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_gateway_should_return_0() { + $orders = edd_get_orders( array( + 'gateway' => 'invalid', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_mode_should_return_0() { + $orders = edd_get_orders( array( + 'mode' => 'invalid', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_currency_should_return_0() { + $orders = edd_get_orders( array( + 'currency' => 'ABC', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_payment_key_should_return_0() { + $orders = edd_get_orders( array( + 'payment_key' => 'INVALID', + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_subtotal_should_return_0() { + $orders = edd_get_orders( array( + 'subtotal' => -999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_tax_should_return_0() { + $orders = edd_get_orders( array( + 'tax' => -999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_discount_should_return_0() { + $orders = edd_get_orders( array( + 'discount' => -999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::edd_get_orders + */ + public function test_get_orders_with_invalid_total_should_return_0() { + $orders = edd_get_orders( array( + 'total' => -999, + ) ); + + $this->assertCount( 0, $orders ); + } + + /** + * @covers ::is_complete + */ + public function test_is_order_complete_should_return_true() { + $this->assertTrue( edd_get_order( self::$orders[0] )->is_complete() ); + } + + /** + * @covers ::get_number + */ + public function test_order_number_should_be_id_and_return_true() { + $this->assertSame( self::$orders[0], (int) edd_get_order( self::$orders[0] )->id ); + } + + /** + * @covers ::__get + */ + public function test_order_object_magic_getter_for_address_should_return_true() { + foreach ( self::$orders as $order ) { + $o = edd_get_order( $order ); + + $this->assertNotEmpty( $o->address ); + $this->assertInstanceOf( 'EDD\Orders\Order_Address', $o->address ); + } + } + + /** + * @covers ::__get + */ + public function test_order_object_magic_getter_for_adjustments_should_return_true() { + foreach ( self::$orders as $order ) { + $o = edd_get_order( $order ); + + $this->assertNotEmpty( $o->adjustments ); + + foreach ( $o->adjustments as $adjustment ) { + $this->assertInstanceOf( 'EDD\Orders\Order_Adjustment', $adjustment ); + } + } + } + + /** + * @covers ::__get + */ + public function test_order_object_magic_getter_for_items_should_return_true() { + foreach ( self::$orders as $order ) { + $o = edd_get_order( $order ); + + $this->assertNotEmpty( $o->items ); + + foreach ( $o->items as $item ) { + $this->assertInstanceOf( 'EDD\Orders\Order_Item', $item ); + } + } + } +} diff --git a/tests/orders/tests-payments-back-compat.php b/tests/orders/tests-payments-back-compat.php new file mode 100644 index 00000000000..559418c86cd --- /dev/null +++ b/tests/orders/tests-payments-back-compat.php @@ -0,0 +1,232 @@ +order->create_many( 5 ); + } + + /** + * @covers ::wp_count_posts + */ + public function test_wp_count_posts() { + $this->assertSame( 5, (int) wp_count_posts( 'edd_payment' )->complete ); + } + + /** + * @covers ::pre_get_posts + */ + public function test_get_posts() { + $this->setExpectedIncorrectUsage( 'get_posts()/get_post()/WP_Query' ); + + get_posts( array( 'post_type' => 'edd_payment' ) ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_purchase_key() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->payment_key; + $actual = get_post_meta( self::$orders[0], '_edd_payment_purchase_key', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_transaction_id() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->get_transaction_id(); + $actual = get_post_meta( self::$orders[0], '_edd_payment_transaction_id', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_user_email() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->email; + $actual = get_post_meta( self::$orders[0], '_edd_payment_user_email', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_meta() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_payment( self::$orders[0] )->get_meta( '_edd_payment_meta' ); + $actual = get_post_meta( self::$orders[0], '_edd_payment_meta', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_completed_date() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->date_completed; + $actual = get_post_meta( self::$orders[0], '_edd_completed_date', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_gateway() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->gateway; + $actual = get_post_meta( self::$orders[0], '_edd_payment_gateway', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_user_id() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->user_id; + $actual = get_post_meta( self::$orders[0], '_edd_payment_user_id', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_user_ip() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->ip; + $actual = get_post_meta( self::$orders[0], '_edd_payment_user_ip', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_mode() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->mode; + $actual = get_post_meta( self::$orders[0], '_edd_payment_mode', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_tax_rate() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->get_tax_rate(); + $actual = get_post_meta( self::$orders[0], '_edd_payment_tax_rate', true ); + + $this->assertSame( $expected, $actual ); + } + + public function test_tax_rate_converted_to_decimal_when_querying_post_meta() { + // Create an adjustment. + $adjustment_id = edd_add_tax_rate( array( + 'name' => 'GB', + 'type' => 'tax_rate', + 'amount' => 20 + ) ); + + // Create an order. + $order = parent::edd()->order->create_and_get( array( + 'tax_rate_id' => $adjustment_id, + 'tax' => 2 + ) ); + + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $this->assertEquals( 0.2, get_post_meta( $order->id, '_edd_payment_tax_rate', true ) ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_customer_id() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->customer_id; + $actual = get_post_meta( self::$orders[0], '_edd_payment_customer_id', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_total() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->total; + $actual = get_post_meta( self::$orders[0], '_edd_payment_total', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_tax() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->tax; + $actual = get_post_meta( self::$orders[0], '_edd_payment_tax', true ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::get_post_metadata + */ + public function test_get_post_metadata__edd_payment_number() { + $this->setExpectedIncorrectUsage( 'get_post_meta()' ); + + $expected = edd_get_order( self::$orders[0] )->get_number(); + $actual = get_post_meta( self::$orders[0], '_edd_payment_number', true ); + + $this->assertSame( $expected, $actual ); + } +} diff --git a/tests/orders/tests-payments-compat.php b/tests/orders/tests-payments-compat.php new file mode 100644 index 00000000000..2e77fe592a4 --- /dev/null +++ b/tests/orders/tests-payments-compat.php @@ -0,0 +1,130 @@ +payment_id = wp_insert_post( + array( + 'post_type' => 'edd_payment', + 'post_status' => 'publish', + ) + ); + + update_post_meta( + $this->payment_id, + '_edd_payment_meta', + array( + 'key' => 'd8eb04c6fca9a2941a9ed1706a5d48d3', + 'email' => 'customer@test.com', + 'user_info' => array( + 'id' => 1234, + 'email' => 'customer@test.com', + 'first_name' => 'Customer', + ), + 'downloads' => array( + array( + 'id' => 1, + 'quantity' => 1, + 'options' => array( + 'quantity' => 1, + ), + ), + ), + ) + ); + update_post_meta( $this->payment_id, '_edd_payment_gateway', 'stripe' ); + update_post_meta( $this->payment_id, '_edd_payment_total', 20.00 ); + update_post_meta( $this->payment_id, '_edd_payment_customer_id', 213 ); + update_option( 'edd_v3_migration_pending', 123456, false ); + + $this->payment = edd_get_payment( $this->payment_id ); + } + + public function tearDown(): void { + parent::tearDown(); + + wp_delete_post( $this->payment_id ); + delete_option( 'edd_v3_migration_pending' ); + } + + public function test_edd_get_payment_no_order_during_migration_should_return_object() { + $this->assertTrue( $this->payment instanceof \EDD_Payment ); + } + + public function test_edd_get_payment_no_order_during_migration_should_have_payment_id() { + $this->assertSame( $this->payment_id, $this->payment->ID ); + } + + public function test_edd_get_payment_no_order_during_migration_should_have_order_object() { + $this->assertTrue( $this->payment->order instanceof Order ); + } + + public function test_edd_get_payment_no_order_during_migration_payment_id_should_match_order_id() { + $this->assertSame( $this->payment->order->id, $this->payment_id ); + } + + public function test_edd_get_payment_no_order_during_migration_should_have_payment_meta() { + $this->assertNotEmpty( $this->payment->payment_meta ); + } + + public function test_edd_get_payment_no_order_during_migration_should_have_download() { + $this->assertSame( 1, $this->payment->downloads[0]['id'] ); + } + + public function test_edd_get_payment_no_order_during_migration_total_is_20() { + $this->assertEquals( 20, $this->payment->total ); + } + + public function test_edd_get_payment_no_order_during_migration_gateway_is_stripe() { + $this->assertEquals( 'stripe', $this->payment->gateway ); + } + + public function test_edd_get_payment_no_order_during_migration_status_is_complete() { + $this->assertEquals( 'complete', $this->payment->status ); + } + + public function test_edd_get_payment_no_order_during_migration_customer_id_is_213() { + $this->assertEquals( 213, $this->payment->customer_id ); + } + + public function test_edd_get_order_no_order_during_migration_gets_order() { + $order = edd_get_order( $this->payment_id ); + + $this->assertSame( $this->payment_id, $order->id ); + } + + public function test_edd_get_payment_meta_no_order_during_migration_gets_meta() { + update_post_meta( $this->payment->ID, '_edd_random_metadata', 'sample_meta' ); + + $this->assertSame( 'sample_meta', edd_get_payment_meta( $this->payment->ID, '_edd_random_metadata', true ) ); + } + + public function test_edd_get_payment_no_order_outside_migration_should_return_false() { + delete_option( 'edd_v3_migration_pending' ); + $payment = edd_get_payment( $this->payment_id ); + + $this->assertFalse( $payment ); + } + + public function test_edd_get_order_no_order_outside_migration_should_return_false() { + delete_option( 'edd_v3_migration_pending' ); + $order = edd_get_order( $this->payment_id ); + + $this->assertFalse( $order ); + } +} diff --git a/tests/orders/tests-payments.php b/tests/orders/tests-payments.php new file mode 100755 index 00000000000..5f51539a832 --- /dev/null +++ b/tests/orders/tests-payments.php @@ -0,0 +1,487 @@ + self::$payment->ID, + ) + ); + } + + public function tearDown(): void { + parent::tearDown(); + + Helpers\EDD_Helper_Payment::delete_payment( self::$payment->ID ); + + edd_destroy_order( self::$payment->ID ); + + $component = edd_get_component_interface( 'order_transaction', 'table' ); + + if ( $component instanceof \EDD\Database\Table ) { + $component->truncate(); + } + + self::$payment = null; + } + + public function test_get_payments() { + $payments = edd_get_payments(); + + $this->assertTrue( is_array( (array) $payments[0] ) ); + $this->assertArrayHasKey( 'ID', (array) $payments[0] ); + $this->assertEquals( 'edd_payment', $payments[0]->post_type ); + } + + public function test_get_payments_download_array() { + $payments = edd_get_payments( + array( + 'download' => array( self::$order_items[0]->product_id ), + ) + ); + + $this->assertFalse( empty( $payments ) ); + $this->assertSame( self::$payment->ID, $payments[0]->ID ); + } + + public function test_get_payments_download_string() { + $payments = edd_get_payments( + array( + 'download' => (string) self::$order_items[0]->product_id, + ) + ); + + $this->assertFalse( empty( $payments ) ); + $this->assertSame( self::$payment->ID, $payments[0]->ID ); + } + + public function test_get_payments_download_numeric() { + $payments = edd_get_payments( + array( + 'download' => (int) self::$order_items[0]->product_id, + ) + ); + + $this->assertFalse( empty( $payments ) ); + $this->assertSame( self::$payment->ID, $payments[0]->ID ); + } + + public function test_payments_query_edd_payments() { + $payments = new \EDD_Payments_Query( array( 'output' => 'edd_payments' ) ); + $payments = $payments->get_payments(); + + $this->assertTrue( is_object( $payments[0] ) ); + $this->assertTrue( property_exists( $payments[0], 'ID' ) ); + $this->assertTrue( property_exists( $payments[0], 'cart_details' ) ); + $this->assertTrue( property_exists( $payments[0], 'user_info' ) ); + } + + public function test_payments_query_payments() { + $payments = new \EDD_Payments_Query( array( 'output' => 'payments' ) ); + $out = $payments->get_payments(); + $this->assertTrue( is_object( $out[0] ) ); + $this->assertTrue( property_exists( $out[0], 'ID' ) ); + $this->assertTrue( property_exists( $out[0], 'cart_details' ) ); + $this->assertTrue( property_exists( $out[0], 'user_info' ) ); + } + + public function test_payments_query_default() { + $payments = new \EDD_Payments_Query(); + $payments = $payments->get_payments(); + + $this->assertTrue( is_object( $payments[0] ) ); + $this->assertTrue( property_exists( $payments[0], 'ID' ) ); + $this->assertTrue( property_exists( $payments[0], 'cart_details' ) ); + $this->assertTrue( property_exists( $payments[0], 'user_info' ) ); + } + + public function test_payments_query_search_discount() { + $discount = edd_get_discount( Helpers\EDD_Helper_Discount::create_simple_percent_discount() ); + + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment( array( 'discount' => $discount->code ) ); + + $payments_query = new \EDD_Payments_Query( array( 's' => 'discount:' . $discount->code ) ); + $out = $payments_query->get_payments(); + + $this->assertEquals( 1, count( $out ) ); + $this->assertEquals( $payment_id, $out[0]->ID ); + + Helpers\EDD_Helper_Payment::delete_payment( $payment_id ); + + $payments_query = new \EDD_Payments_Query( array( 's' => 'discount:' . $discount->code ) ); + $out = $payments_query->get_payments(); + + $this->assertEquals( 0, count( $out ) ); + } + + public function test_payments_query_count_payments() { + $payments = new \EDD_Payments_Query( array( 'count' => true ) ); + $count = $payments->get_payments(); + + $this->assertTrue( is_numeric( $count ) ); + $this->assertEquals( 1, $count ); + } + + public function test_edd_get_payment_by() { + $payment = edd_get_payment_by( 'id', self::$payment->ID ); + $this->assertObjectHasAttribute( 'ID', $payment ); + + $payment = edd_get_payment_by( 'key', self::$payment->key ); + $this->assertObjectHasAttribute( 'ID', $payment ); + } + + public function test_fake_insert_payment() { + $this->assertFalse( edd_insert_payment() ); + } + + public function test_payment_completed_flag_not_exists() { + $completed_date = edd_get_payment_completed_date( self::$payment->ID ); + $this->assertEmpty( $completed_date ); + } + + public function test_update_payment_status() { + edd_update_payment_status( self::$payment->ID, 'complete' ); + + $out = edd_get_payments(); + + $this->assertEquals( 'complete', $out[0]->status ); + } + + public function test_update_payment_status_with_invalid_id() { + $updated = edd_update_payment_status( 12121212, 'complete' ); + + $this->assertFalse( $updated ); + } + + public function test_check_for_existing_payment() { + edd_update_payment_status( self::$payment->ID, 'complete' ); + + $this->assertTrue( edd_check_for_existing_payment( self::$payment->ID ) ); + } + + public function test_get_payment_status() { + $this->assertEquals( 'pending', edd_get_payment_status( self::$payment->ID ) ); + + $this->assertEquals( 'pending', edd_get_payment_status( self::$payment ) ); + $this->assertFalse( edd_get_payment_status( 1212121212121 ) ); + } + + public function test_get_payment_status_label() { + $this->assertEquals( 'Pending', edd_get_payment_status( self::$payment->ID, true ) ); + + $this->assertEquals( 'Pending', edd_get_payment_status( self::$payment, true ) ); + } + + public function test_get_payment_statuses() { + $out = edd_get_payment_statuses(); + + $expected = array( + 'pending' => 'Pending', + 'complete' => 'Completed', + 'refunded' => 'Refunded', + 'partially_refunded' => 'Partially Refunded', + 'failed' => 'Failed', + 'revoked' => 'Revoked', + 'abandoned' => 'Abandoned', + 'processing' => 'Processing', + ); + + $this->assertEquals( $expected, $out ); + } + + public function test_get_payment_status_keys() { + $out = edd_get_payment_status_keys(); + + $expected = array( + 'pending' => __( 'Pending', 'easy-digital-downloads' ), + 'processing' => __( 'Processing', 'easy-digital-downloads' ), + 'complete' => __( 'Completed', 'easy-digital-downloads' ), + 'refunded' => __( 'Refunded', 'easy-digital-downloads' ), + 'partially_refunded' => __( 'Partially Refunded', 'easy-digital-downloads' ), + 'revoked' => __( 'Revoked', 'easy-digital-downloads' ), + 'failed' => __( 'Failed', 'easy-digital-downloads' ), + 'abandoned' => __( 'Abandoned', 'easy-digital-downloads' ) + ); + + asort( $expected ); + + $expected = array_keys( $expected ); + + $this->assertIsArray( $out ); + $this->assertEquals( $expected, $out ); + } + + public function test_delete_purchase() { + edd_delete_purchase( self::$payment->ID ); + + $this->assertEmpty( edd_get_payments() ); + } + + public function test_get_payment_completed_date() { + edd_update_payment_status( self::$payment->ID, 'complete' ); + $payment = new \EDD_Payment( self::$payment->ID ); + + $this->assertIsString( $payment->completed_date ); + $this->assertTrue( in_array( date( 'Y-m-d H:i', strtotime( $payment->completed_date ) ), $this->get_expected_date_range() ) ); + } + + public function test_get_payment_completed_date_bc() { + edd_update_payment_status( self::$payment->ID, 'complete' ); + $completed_date = edd_get_payment_completed_date( self::$payment->ID ); + + $this->assertIsString( $completed_date ); + $this->assertTrue( in_array( date( 'Y-m-d H:i', strtotime( $completed_date ) ), $this->get_expected_date_range() ) ); + } + + /** + * Gets an array of expected dates (a 20 second range). + */ + private function get_expected_date_range() { + return array( + date( 'Y-m-d H:i', strtotime( '-10 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-9 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-8 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-7 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-6 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-5 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-4 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-3 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-2 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '-1 second' ) ), + date( 'Y-m-d H:i' ), + date( 'Y-m-d H:i', strtotime( '+1 second' ) ), + date( 'Y-m-d H:i', strtotime( '+2 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+3 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+4 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+5 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+6 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+7 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+8 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+9 seconds' ) ), + date( 'Y-m-d H:i', strtotime( '+10 seconds' ) ), + ); + } + + public function test_get_payment_number() { + global $edd_options; + $edd_options['enable_sequential'] = 1; + $edd_options['sequential_prefix'] = 'EDD-'; + $edd_options['sequential_start'] = 11; + + $payment_id = Helpers\EDD_Helper_Payment::create_simple_payment(); + + // Create more payments just because. + Helpers\EDD_Helper_Payment::create_simple_payment(); + Helpers\EDD_Helper_Payment::create_simple_payment(); + + $order_number = new \EDD\Orders\Number(); + $next_number = $order_number->get_next_payment_number(); + + $this->assertIsInt( $next_number ); + $this->assertIsString( $order_number->format( $next_number ) ); + $this->assertEquals( 'EDD-14', $order_number->format( $next_number ) ); + + $payment = edd_get_payment( $payment_id ); + $last_payment_number = $order_number->unformat( $payment->number ); + $this->assertEquals( 11, $last_payment_number ); + $this->assertEquals( 'EDD-11', $payment->number ); + $this->assertEquals( 14, $next_number ); + + // Now disable sequential and ensure values come back as expected + $edd_options['enable_sequential'] = 0; + + $payment = edd_get_payment( $payment_id ); + $this->assertEquals( $payment_id, $payment->number ); + } + + public function test_get_payment_transaction_id_bc() { + $this->assertEquals( self::$payment->transaction_id, edd_get_payment_transaction_id( self::$payment->ID ) ); + } + + public function test_get_payment_transaction_id_legacy() { + $this->assertEquals( self::$payment->transaction_id, edd_paypal_get_payment_transaction_id( self::$payment->ID ) ); + } + + public function test_get_payment_meta() { + // Test by getting the payment key with three different methods + $this->assertEquals( self::$payment->key, self::$payment->get_meta( '_edd_payment_purchase_key' ) ); + $this->assertEquals( self::$payment->key, edd_get_payment_meta( self::$payment->ID, '_edd_payment_purchase_key', true ) ); + + // Try and retrieve the transaction ID + $this->assertEquals( self::$payment->transaction_id, self::$payment->get_meta( '_edd_payment_transaction_id' ) ); + + $this->assertEquals( self::$payment->email, self::$payment->get_meta( '_edd_payment_user_email' ) ); + } + + public function test_get_payment_meta_bc() { + // Test by getting the payment key with three different methods + $this->assertEquals( self::$payment->key, edd_get_payment_meta( self::$payment->ID, '_edd_payment_purchase_key' ) ); + $this->assertEquals( self::$payment->key, edd_get_payment_meta( self::$payment->ID, '_edd_payment_purchase_key', true ) ); + $this->assertEquals( self::$payment->key, edd_get_payment_key( self::$payment->ID ) ); + + // Try and retrieve the transaction ID + $this->assertEquals( self::$payment->transaction_id, edd_get_payment_meta( self::$payment->ID, '_edd_payment_transaction_id' ) ); + + $user_info = edd_get_payment_meta_user_info( self::$payment->ID ); + $this->assertEquals( $user_info['email'], edd_get_payment_meta( self::$payment->ID, '_edd_payment_user_email' ) ); + } + + public function test_update_payment_meta() { + $payment = new \EDD_Payment( self::$payment->ID ); + $this->assertEquals( $payment->key, $payment->get_meta( '_edd_payment_purchase_key' ) ); + + $new_value = 'test12345'; + $this->assertNotEquals( $payment->key, $new_value ); + + $payment->key = $new_value; + $ret = $payment->save(); + + $this->assertTrue( $ret ); + $this->assertEquals( $new_value, $payment->key ); + + $payment->email = 'test@test.com'; + $ret = $payment->save(); + + $this->assertTrue( $ret ); + + $this->assertEquals( 'test@test.com', $payment->email ); + } + + public function test_update_payment_meta_bc() { + $old_value = self::$payment->key; + $this->assertEquals( $old_value, edd_get_payment_meta( self::$payment->ID, '_edd_payment_purchase_key' ) ); + + $new_value = 'test12345'; + $this->assertNotEquals( $old_value, $new_value ); + + $ret = edd_update_payment_meta( self::$payment->ID, '_edd_payment_purchase_key', $new_value ); + + $this->assertTrue( $ret ); + + $this->assertEquals( $new_value, edd_get_payment_meta( self::$payment->ID, '_edd_payment_purchase_key' ) ); + + $ret = edd_update_payment_meta( self::$payment->ID, '_edd_payment_user_email', 'test@test.com' ); + + $this->assertTrue( $ret ); + + $user_info = edd_get_payment_meta_user_info( self::$payment->ID ); + $this->assertEquals( 'test@test.com', edd_get_payment_meta( self::$payment->ID, '_edd_payment_user_email' ) ); + } + + public function test_update_payment_data() { + self::$payment->date = date( 'Y-m-d H:i:s' ); + self::$payment->save(); + $meta = self::$payment->get_meta(); + + $this->assertSame( self::$payment->date, $meta['date'] ); + } + + public function test_get_payment_currency_code() { + $this->assertEquals( 'USD', self::$payment->currency ); + $this->assertEquals( 'US Dollars ($)', edd_get_payment_currency( self::$payment->ID ) ); + + $total1 = edd_currency_filter( edd_format_amount( self::$payment->total ), self::$payment->currency ); + $total2 = edd_currency_filter( edd_format_amount( self::$payment->total ) ); + + $this->assertEquals( '$120.00', $total1 ); + $this->assertEquals( '$120.00', $total2 ); + } + + public function test_get_payment_currency_code_bc() { + $this->assertEquals( 'USD', edd_get_payment_currency_code( self::$payment->ID ) ); + $this->assertEquals( 'US Dollars ($)', edd_get_payment_currency( self::$payment->ID ) ); + + $total1 = edd_currency_filter( edd_format_amount( edd_get_payment_amount( self::$payment->ID ) ), edd_get_payment_currency_code( self::$payment->ID ) ); + $total2 = edd_currency_filter( edd_format_amount( edd_get_payment_amount( self::$payment->ID ) ) ); + + $this->assertEquals( '$120.00', $total1 ); + $this->assertEquals( '$120.00', $total2 ); + } + + public function test_is_guest_payment() { + $this->assertFalse( edd_is_guest_payment( self::$payment->ID ) ); + + $guest_payment_id = Helpers\EDD_Helper_Payment::create_simple_guest_payment(); + $this->assertTrue( edd_is_guest_payment( $guest_payment_id ) ); + } + + public function test_is_guest_payment_with_order() { + $order = edd_get_order( self::$payment->ID ); + $this->assertFalse( edd_is_guest_payment( $order ) ); + + $guest_payment_id = Helpers\EDD_Helper_Payment::create_simple_guest_payment(); + $guest_order = edd_get_order( $guest_payment_id ); + $this->assertTrue( edd_is_guest_payment( $guest_order ) ); + } + + public function test_get_payment() { + $payment = edd_get_payment( self::$payment->ID ); + $this->assertTrue( property_exists( $payment, 'ID' ) ); + $this->assertTrue( property_exists( $payment, 'cart_details' ) ); + $this->assertTrue( property_exists( $payment, 'user_info' ) ); + $this->assertEquals( $payment->ID, self::$payment->ID ); + $payment->transaction_id = 'a1b2c3d4e5'; + $payment->save(); + + $payment_2 = edd_get_payment( 'a1b2c3d4e5', true ); + $this->assertTrue( property_exists( $payment_2, 'ID' ) ); + $this->assertTrue( property_exists( $payment_2, 'cart_details' ) ); + $this->assertTrue( property_exists( $payment_2, 'user_info' ) ); + $this->assertEquals( $payment_2->ID, self::$payment->ID ); + } + + public function test_payments_date_query() { + /** + * @internal There's a caching issue when running the test suite so we have to clear everything at the beginning + * of this test. + */ + $component = edd_get_component_interface( 'order', 'table' ); + + if ( $component instanceof \EDD\Database\Table ) { + $component->truncate(); + } + + $payment_id_1 = Helpers\EDD_Helper_Payment::create_simple_payment_with_date( date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ) ); + Helpers\EDD_Helper_Payment::create_simple_payment_with_date( date( 'Y-m-d H:i:s', strtotime( '-4 days' ) ) ); + Helpers\EDD_Helper_Payment::create_simple_payment_with_date( date( 'Y-m-d H:i:s', strtotime( '-5 days' ) ) ); + Helpers\EDD_Helper_Payment::create_simple_payment_with_date( date( 'Y-m-d H:i:s', strtotime( '-1 month' ) ) ); + + $payments_query = new \EDD_Payments_Query( array( + 'start_date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'end_date' => date( 'Y-m-d H:i:s' ), + 'output' => 'orders', + ) ); + + $payments = $payments_query->get_payments(); + + $this->assertEquals( 1, count( $payments ) ); + $this->assertEquals( $payment_id_1, $payments[0]->ID ); + + self::$payment = edd_get_payment( Helpers\EDD_Helper_Payment::create_simple_payment() ); + } +} diff --git a/tests/orders/tests-refunds.php b/tests/orders/tests-refunds.php new file mode 100644 index 00000000000..39d838f3ecf --- /dev/null +++ b/tests/orders/tests-refunds.php @@ -0,0 +1,342 @@ +order->create_many( 5 ); + + foreach ( self::$orders as $order ) { + edd_add_order_adjustment( array( + 'object_type' => 'order', + 'object_id' => $order, + 'type' => 'discount', + 'description' => '5OFF', + 'subtotal' => 0, + 'total' => 5, + ) ); + } + } + + /** + * @covers ::edd_is_order_refundable + */ + public function test_is_order_refundable_should_return_true() { + $this->assertTrue( edd_is_order_refundable( self::$orders[0] ) ); + } + + /** + * @covers ::edd_is_order_refund_window_passed + */ + public function test_is_order_refund_window_passed_return_true() { + $order = parent::edd()->order->create_and_get( array( + 'date_refundable' => '2000-01-01 00:00:00', + ) ); + + $this->assertTrue( edd_is_order_refund_window_passed( $order->id ) ); + } + + /** + * @covers ::edd_is_order_refundable_by_override + */ + public function test_is_order_refundable_by_override_return_true() { + $order = parent::edd()->order->create_and_get( array( + 'date_refundable' => '2000-01-01 00:00:00', + ) ); + + add_filter( 'edd_is_order_refundable_by_override', '__return_true' ); + + $this->assertTrue( edd_is_order_refundable_by_override( $order->id ) ); + } + + /** + * @covers ::edd_get_order_total + */ + public function test_get_order_total_should_be_120() { + $this->assertSame( 120.0, edd_get_order_total( self::$orders[0] ) ); + } + + /** + * @covers ::edd_get_order_item_total + */ + public function test_get_order_item_total_should_be_120() { + $this->assertSame( 120.0, edd_get_order_item_total( array( self::$orders[0] ), 1 ) ); + } + + /** + * @covers ::edd_refund_order + */ + public function test_refund_order() { + + // Refund order entirely. + $refunded_order = edd_refund_order( self::$orders[0] ); + + // Check that a new order ID was returned. + $this->assertNotInstanceOf( 'WP_Error', $refunded_order ); + $this->assertGreaterThan( 0, $refunded_order ); + + // Fetch original order. + $o = edd_get_order( self::$orders[0] ); + + // Check a valid Order object was returned. + $this->assertInstanceOf( 'EDD\Orders\Order', $o ); + + // Verify status. + $this->assertSame( 'refunded', $o->status ); + + // Verify type. + $this->assertSame( 'sale', $o->type ); + + // Verify total. + $this->assertSame( 120.0, floatval( $o->total ) ); + + // Fetch refunded order. + $r = edd_get_order( $refunded_order ); + + // Check a valid Order object was returned. + $this->assertInstanceOf( 'EDD\Orders\Order', $r ); + + // Verify status. + $this->assertSame( 'complete', $r->status ); + + // Verify type. + $this->assertSame( 'refund', $r->type ); + + // Verify total. + $this->assertSame( -120.0, floatval( $r->total ) ); + } + + /** + * @covers ::edd_refund_order + */ + public function test_refund_order_returns_wp_error_if_refund_amount_exceeds_max() { + $order = edd_get_order( self::$orders[1] ); + + $to_refund = array(); + + foreach( $order->items as $order_item ) { + $to_refund[] = array( + 'order_item_id' => $order_item->id, + 'subtotal' => $order_item->subtotal * 2, + 'tax' => $order_item->tax, + 'total' => $order_item->total * 2 + ); + } + + $refund_id = edd_refund_order( $order->id, $to_refund ); + + $this->assertInstanceOf( 'WP_Error', $refund_id ); + + $this->assertEquals( 'refund_validation_error', $refund_id->get_error_code() ); + $this->assertStringContainsString( 'The maximum refund subtotal', $refund_id->get_error_message() ); + } + + /** + * @covers ::edd_refund_order + * @covers ::edd_get_order_total + */ + public function test_partially_refund_order() { + $order = edd_get_order( self::$orders[1] ); + + $to_refund = array(); + + foreach( $order->items as $order_item ) { + // Only refund half the subtotal / tax for the order item. This creates a partial refund. + $to_refund[] = array( + 'order_item_id' => $order_item->id, + 'subtotal' => ( $order_item->subtotal - $order_item->discount ) / 2, + 'tax' => $order_item->tax / 2, + 'total' => $order_item->total / 2 + ); + } + + $refund_id = edd_refund_order( $order->id, $to_refund ); + + $this->assertGreaterThan( 0, $refund_id ); + + // Fetch original order. + $o = edd_get_order( $order->id ); + + // Check a valid Order object was returned. + $this->assertInstanceOf( 'EDD\Orders\Order', $o ); + + // Verify status. + $this->assertSame( 'partially_refunded', $o->status ); + + // Verify type. + $this->assertSame( 'sale', $o->type ); + + // Verify original total. + $this->assertEquals( 120.0, floatval( $o->total ) ); + + // Verify total minus refunded amount. + $this->assertEquals( 60.0, edd_get_order_total( $o->id ) ); + + // Fetch refunded order. + $r = edd_get_order( $refund_id ); + + // Check a valid Order object was returned. + $this->assertInstanceOf( 'EDD\Orders\Order', $r ); + + // Verify status. + $this->assertSame( 'complete', $r->status ); + + // Verify type. + $this->assertSame( 'refund', $r->type ); + + // Verify total. + $this->assertEquals( -60.0, floatval( $r->total ) ); + } + + public function test_partial_refund_with_free_download_remaining() { + $order_id = self::$orders[2]; + $oid = edd_add_order_item( array( + 'order_id' => $order_id, + 'product_id' => 17, + 'product_name' => 'Free Download', + 'status' => 'inherit', + 'amount' => 0, + 'subtotal' => 0, + 'discount' => 0, + 'tax' => 0, + 'total' => 0, + 'quantity' => 1, + ) ); + + $to_refund = array(); + $order = edd_get_order( $order_id ); + foreach ( $order->items as $order_item ) { + if ( $order_item->total > 0 ) { + $to_refund[] = array( + 'order_item_id' => $order_item->id, + 'subtotal' => ( $order_item->subtotal - $order_item->discount ), + 'tax' => $order_item->tax, + 'total' => $order_item->total, + ); + } + } + + $refund_id = edd_refund_order( $order->id, $to_refund ); + + // Fetch original order. + $o = edd_get_order( $order->id ); + + $this->assertSame( 'partially_refunded', $o->status ); + } + + /** + * @covers ::edd_get_refundability_types + */ + public function test_get_refundability_types() { + $expected = array( + 'refundable' => __( 'Refundable', 'easy-digital-downloads' ), + 'nonrefundable' => __( 'Non-Refundable', 'easy-digital-downloads' ), + ); + + $this->assertEqualSetsWithIndex( $expected, edd_get_refundability_types() ); + } + + /** + * @covers ::edd_get_refund_date() + */ + public function test_get_refund_date() { + + // Static date to ensure unit tests don't fail if this test runs for longer than 1 second. + $date = '2010-01-01 00:00:00'; + + $this->assertSame( Carbon::parse( $date )->addDays( 30 )->toDateTimeString(), edd_get_refund_date( $date ) ); + } + + /** + * @covers \EDD\Orders\Refund_Validator::validate_and_calculate_totals + * @covers \EDD\Orders\Refund_Validator::get_refunded_order_items + * @throws \Exception + */ + public function test_refund_validator_all_returns_original_amounts() { + $order = edd_get_order( self::$orders[1] ); + $validator = new Refund_Validator( $order, 'all', 'all' ); + $validator->validate_and_calculate_totals(); + + $this->assertEquals( ( $order->subtotal - $order->discount ), $validator->subtotal ); + $this->assertEquals( $order->tax, $validator->tax ); + $this->assertEquals( $order->total, $validator->total ); + + $order_item_ids = wp_list_pluck( $order->items, 'id' ); + $refund_item_ids = wp_list_pluck( $validator->get_refunded_order_items(), 'parent' ); + + sort( $order_item_ids ); + sort( $refund_item_ids ); + + $this->assertEquals( $order_item_ids, $refund_item_ids ); + } + + /** + * An Invalid_Argument exception is thrown if the `order_item_id` argument is missing. + * + * @covers \EDD\Orders\Refund_Validator::validate_and_format_order_items + */ + public function test_refund_validator_throws_exception_missing_order_item_id() { + $order = edd_get_order( self::$orders[1] ); + + $this->expectException( Invalid_Argument::class ); + + $validator = new Refund_Validator( $order, array( + array( + 'subtotal' => 100, + 'tax' => 20, + 'total' => 120 + ) + ), 'all' ); + + $exception = $this->getexpectException(); + $this->assertStringContainsString( 'order_item_id', $exception->getMessage() ); + } + + /** + * An Invalid_Argument exception is thrown if the `subtotal` argument is missing. + * + * @covers \EDD\Orders\Refund_Validator::validate_and_format_order_items + * @covers \EDD\Orders\Refund_Validator::validate_required_fields + */ + public function test_refund_validator_throws_exception_missing_subtotal() { + $order = edd_get_order( self::$orders[1] ); + + $this->expectException( Invalid_Argument::class ); + + $validator = new Refund_Validator( $order, array( + array( + 'order_item_id' => $order->items[0]->id, + 'tax' => $order->items[0]->tax, + 'total' => $order->items[0]->total + ) + ), 'all' ); + + $exception = $this->getexpectException(); + $this->assertStringContainsString( 'subtotal', $exception->getMessage() ); + } +} diff --git a/tests/orders/tests-stats.php b/tests/orders/tests-stats.php new file mode 100644 index 00000000000..7ce9d3a1c0a --- /dev/null +++ b/tests/orders/tests-stats.php @@ -0,0 +1,356 @@ +order->create_many( 5 ); + + // Refund two of those orders. + for ( $i = 0; $i < 2; $i++ ) { + self::$refunds[] = edd_refund_order( self::$orders[ $i ] ); + } + + // Add a variable download with price ID 0 to order 3. + edd_add_order_item( + array( + 'order_id' => 3, + 'product_id' => 2, + 'price_id' => 0, + 'product_name' => 'Variable Test Download Product - Simple', + 'status' => 'complete', + 'amount' => 20, + 'subtotal' => 20, + 'discount' => 0, + 'tax' => 0, + 'total' => 20, + 'quantity' => 1, + ) + ); + } + + /** + * @covers ::get_order_earnings + */ + public function test_get_order_earnings() { + $earnings = self::$stats->get_order_earnings(); + + $this->assertSame( 600.0, $earnings ); + } + + /** + * @covers ::get_order_earnings + */ + public function test_get_order_earnings_with_range_last_year_should_be_0() { + $earnings = self::$stats->get_order_earnings( array( + 'range' => 'last_year', + ) ); + + $this->assertSame( 0.00, $earnings ); + } + + /** + * @covers ::get_order_earnings + */ + public function test_get_order_earnings_with_range_last_30_days_should_be_600() { + $earnings = self::$stats->get_order_earnings( array( + 'range' => 'last_30_days', + ) ); + + $this->assertSame( 600.00, $earnings ); + } + + /** + * @covers ::get_order_earnings + */ + public function test_get_order_earnings_with_range_this_year_should_be_600() { + $earnings = self::$stats->get_order_earnings( array( + 'range' => 'this_year', + ) ); + + $this->assertSame( 600.00, $earnings ); + } + + /** + * @covers ::get_order_count + */ + public function test_get_order_count() { + $count = self::$stats->get_order_count(); + + $this->assertSame( 5, $count ); + } + + /** + * @covers ::get_order_count + */ + public function test_get_order_count_with_range_last_year_should_be_0() { + $count = self::$stats->get_order_count( array( + 'range' => 'last_year', + ) ); + + $this->assertSame( 0, $count ); + } + + /** + * @covers ::get_order_count + */ + public function test_get_order_count_with_range_last_30_days_should_be_5() { + $count = self::$stats->get_order_count( array( + 'range' => 'last_30_days', + ) ); + + $this->assertSame( 5, $count ); + } + + /** + * @covers ::get_order_count + */ + public function test_get_order_count_with_range_this_year_should_be_5() { + $count = self::$stats->get_order_count( array( + 'range' => 'this_year', + ) ); + + $this->assertSame( 5, $count ); + } + + /** + * @covers ::get_order_refund_count + */ + public function test_get_order_refund_count_with_range_last_year_should_be_0() { + $count = self::$stats->get_order_refund_count( array( + 'range' => 'last_year', + ) ); + + $this->assertSame( 0, $count ); + } + + /** + * @covers ::get_order_refund_count + */ + public function test_get_order_refund_count_with_range_this_year_should_be_2() { + $count = self::$stats->get_order_refund_count( array( + 'range' => 'this_year', + ) ); + + $this->assertSame( 2, $count ); + } + + /** + * @covers ::get_order_item_refund_count + */ + public function test_get_order_item_refund_count_with_range_last_year_should_be_0() { + $count = self::$stats->get_order_item_refund_count( array( + 'range' => 'last_year', + ) ); + + $this->assertSame( 0, $count ); + } + + /** + * @covers ::get_order_item_refund_count + */ + public function test_get_order_item_refund_count_with_range_this_year_should_be_2() { + $count = self::$stats->get_order_item_refund_count( array( + 'range' => 'this_year', + ) ); + + $this->assertSame( 2, $count ); + } + + /** + * @covers ::get_order_item_count + */ + public function test_get_order_item_count_no_price_id_should_be_3() { + $count = self::$stats->get_order_item_count( + array( + 'product_id' => 1, + ) + ); + + $this->assertSame( 3, $count ); + } + + /** + * @covers ::get_order_item_count + */ + public function test_get_order_item_count_null_price_id_should_be_3() { + $count = self::$stats->get_order_item_count( + array( + 'product_id' => 1, + 'price_id' => null, + ) + ); + + $this->assertSame( 3, $count ); + } + + /** + * @covers ::get_order_item_count + */ + public function test_get_order_item_count_invalid_price_id_should_be_0() { + $count = self::$stats->get_order_item_count( + array( + 'product_id' => 1, + 'price_id' => 2, + ) + ); + + $this->assertSame( 0, $count ); + } + + /** + * @covers ::get_order_item_count + */ + public function test_get_order_item_count_zero_price_id_should_be_1() { + $count = self::$stats->get_order_item_count( + array( + 'product_id' => 2, + 'price_id' => 0, + ) + ); + + $this->assertSame( 1, $count ); + } + + /** + * @covers ::get_order_item_refund_count + */ + public function test_get_order_item_refund_count_with_invalid_price_id_should_be_0() { + $count = self::$stats->get_order_item_refund_count( + array( + 'product_id' => 1, + 'price_id' => 2, + ) + ); + + $this->assertSame( 0, $count ); + } + + /** + * @covers ::get_order_refund_amount + */ + public function test_get_order_refund_amount_with_range_last_year_should_be_0() { + $earnings = self::$stats->get_order_refund_amount( array( + 'range' => 'last_year', + ) ); + + $this->assertSame( 0.00, $earnings ); + } + + /** + * @covers ::get_order_refund_amount + */ + public function test_get_order_refund_amount_with_range_this_year_should_be_240() { + $earnings = self::$stats->get_order_refund_amount( array( + 'range' => 'this_year', + ) ); + + $this->assertSame( 240.00, $earnings ); + } + + /** + * @covers ::get_refund_rate + */ + public function test_get_get_refund_rate_with_range_last_year_should_be_0() { + $refund_rate = self::$stats->get_refund_rate( array( + 'range' => 'last_year', + 'output' => 'raw', + 'status' => edd_get_gross_order_statuses(), + ) ); + + $this->assertSame( 0, $refund_rate ); + } + + /** + * @covers ::get_refund_rate + */ + public function test_get_get_refund_rate_with_range_this_year_should_be_40() { + $refund_rate = self::$stats->get_refund_rate( array( + 'range' => 'this_year', + 'output' => 'raw', + 'status' => edd_get_gross_order_statuses(), + ) ); + + $this->assertSame( 40.0, $refund_rate ); + } + + /** + * @covers ::generate_relative_data + */ + public function test_generate_relative_data_should_be_without_change() { + $relative_data = self::$stats->generate_relative_data( 100, 100 ); + + $this->assertTrue( $relative_data['no_change'] ); + } + + /** + * @covers ::generate_relative_data + */ + public function test_generate_relative_data_should_not_be_comparable() { + $relative_data = self::$stats->generate_relative_data( 100, 0 ); + + $this->assertFalse( $relative_data['comparable'] ); + } + + /** + * @covers ::generate_relative_data + */ + public function test_generate_relative_data_should_be_positive_change() { + $relative_data = self::$stats->generate_relative_data( 100, 50 ); + + $this->assertTrue( $relative_data['positive_change'] ); + } + + /** + * @covers ::generate_relative_data + */ + public function test_generate_relative_data_should_be_positive_reversed_change() { + $relative_data = self::$stats->generate_relative_data( 50, 100, true ); + + $this->assertTrue( $relative_data['positive_change'] ); + } + + /** + * @covers ::generate_relative_data + */ + public function test_generate_relative_data_should_be_negative_change() { + $relative_data = self::$stats->generate_relative_data( 50, 100 ); + + $this->assertFalse( $relative_data['positive_change'] ); + } +} diff --git a/tests/orders/tests-trash.php b/tests/orders/tests-trash.php new file mode 100644 index 00000000000..72c10d504a4 --- /dev/null +++ b/tests/orders/tests-trash.php @@ -0,0 +1,128 @@ +order->create_many( 5 ); + + foreach ( self::$orders as $order ) { + edd_add_order_adjustment( array( + 'object_type' => 'order', + 'object_id' => $order, + 'type' => 'discount', + 'description' => '5OFF', + 'subtotal' => 0, + 'total' => 5, + ) ); + } + } + + /** + * @covers ::edd_is_order_trashable + */ + public function test_is_order_trashable_should_return_true() { + $this->assertTrue( edd_is_order_trashable( self::$orders[0] ) ); + } + + /** + * @covers ::edd_is_order_trashable + */ + public function test_is_order_trashable_should_return_false() { + self::$order_queries->update_item( self::$orders[1], array( 'status' => 'trash' ) ); + $this->assertFalse( edd_is_order_trashable( self::$orders[1] ) ); + + // Reset this order ID's status + self::$order_queries->update_item( self::$orders[1], array( 'status' => 'complete' ) ); + } + + /** + * @covers ::edd_trash_order + */ + public function test_trash_order_should_return_true() { + $order = edd_get_order( self::$orders[0] ); + $previous_status = $order->status; + + $this->assertTrue( edd_trash_order( self::$orders[0] ) ); + + $order = edd_get_order( self::$orders[0] ); + $this->assertSame( 'trash', $order->status ); + + // Update the status of any order to 'trashed'. + $order_items = edd_get_order_items( array( + 'order_id' => self::$orders[0], + 'no_found_rows' => true, + ) ); + + foreach ( $order_items as $order_item ) { + $this->assertSame( 'trash', $order_item->status ); + } + + $this->assertSame( $previous_status, edd_get_order_meta( self::$orders[0], '_pre_trash_status', true ) ); + } + + public function test_is_order_restorable_should_return_false() { + $this->assertFalse( edd_is_order_restorable( self::$orders[0] ) ); + } + + public function test_is_order_restorable_should_return_true() { + edd_trash_order( self::$orders[0] ); + $this->assertTrue( edd_is_order_restorable( self::$orders[0] ) ); + } + + public function test_restore_order() { + $order = edd_get_order( self::$orders[0] ); + $previous_status = $order->status; + + edd_trash_order( self::$orders[0] ); + + $this->assertTrue( edd_is_order_restorable( self::$orders[0] ) ); + + $this->assertTrue( edd_restore_order( self::$orders[0] ) ); + $order = edd_get_order( self::$orders[0] ); + $this->assertSame( $previous_status, $order->status ); + $this->assertEmpty( edd_get_order_meta( self::$orders[0], '_pre_trash_status', true ) ); + } + + public function test_trash_order_with_refunds() { + edd_refund_order( self::$orders[0] ); + $refunds = edd_get_orders( array( 'parent' => self::$orders[0] ) ); + + $this->assertTrue( edd_is_order_trashable( self::$orders[0] ) ); + $this->assertTrue( edd_is_order_trashable( self::$orders[0] ) ); + + edd_trash_order( self::$orders[0] ); + $order = edd_get_order( self::$orders[0] ); + $refunds = edd_get_orders( array( 'parent' => self::$orders[0] ) ); + $this->assertSame( 'trash', $order->status ); + + $this->assertFalse( edd_is_order_trashable( $refunds[0]->id ) ); + $this->assertSame( 'trash', $refunds[0]->status ); + } + +} diff --git a/tests/phpunit/class-ajax-unittestcase.php b/tests/phpunit/class-ajax-unittestcase.php new file mode 100644 index 00000000000..f5cced2c0f7 --- /dev/null +++ b/tests/phpunit/class-ajax-unittestcase.php @@ -0,0 +1,57 @@ +set_role( 'administrator' ); + wp_update_user( array( 'ID' => 1, 'first_name' => 'Admin', 'last_name' => 'User' ) ); + add_filter( 'edd_log_email_errors', '__return_false' ); + } + + public static function tearDownAfterClass(): void { + self::_delete_all_edd_data(); + + parent::tearDownAfterClass(); + } + + protected static function edd() { + static $factory = null; + if ( ! $factory ) { + $factory = new Factory(); + } + return $factory; + } + + protected static function _delete_all_edd_data() { + $components = EDD()->components; + + foreach ( $components as $component ) { + $thing = $component->get_interface( 'table' ); + + if ( $thing instanceof \EDD\Database\Table ) { + $thing->truncate(); + } + } + } +} diff --git a/tests/phpunit/class-edd-unittestcase.php b/tests/phpunit/class-edd-unittestcase.php new file mode 100644 index 00000000000..30afc32d592 --- /dev/null +++ b/tests/phpunit/class-edd-unittestcase.php @@ -0,0 +1,290 @@ +add_cap( 'view_shop_sensitive_data' ); + $admin_role->add_cap( 'export_shop_reports' ); + $admin_role->add_cap( 'manage_shop_settings' ); + $admin_role->add_cap( 'manage_shop_discounts' ); + + $current_user = new \WP_User( 1 ); + $current_user->set_role( 'administrator' ); + wp_update_user( + array( + 'ID' => 1, + 'first_name' => 'Admin', + 'last_name' => 'User', + ) + ); + + add_filter( 'edd_log_email_errors', '__return_false' ); + } + + public static function tearDownAfterClass(): void { + self::_delete_all_edd_data(); + + delete_option( 'gmt_offset' ); + + EDD()->utils->get_gmt_offset( true ); + + parent::tearDownAfterClass(); + } + + /** + * Runs before each test method. + */ + public function setUp(): void { + parent::setUp(); + + $this->expectDeprecatedEDD(); + } + + /** + * Sets up logic for the @expectEDDeprecated annotation for deprecated elements in EDD. + */ + function expectDeprecatedEDD() { + $annotations = $this->getAnnotations(); + foreach ( array( 'class', 'method' ) as $depth ) { + if ( ! empty( $annotations[ $depth ]['expectEDDeprecated'] ) ) { + $this->expected_deprecated = array_merge( $this->expected_deprecated, $annotations[ $depth ]['expectEDDeprecated'] ); + } + } + add_action( 'edd_deprecated_function_run', array( $this, 'deprecated_function_run' ), 10, 3 ); + add_action( 'edd_deprecated_argument_run', array( $this, 'deprecated_function_run' ), 10, 3 ); + add_action( 'edd_deprecated_hook_run', array( $this, 'deprecated_function_run' ), 10, 3 ); + add_action( 'edd_deprecated_function_trigger_error', '__return_false' ); + add_action( 'edd_deprecated_argument_trigger_error', '__return_false' ); + add_action( 'edd_deprecated_hook_trigger_error', '__return_false' ); + } + + protected static function edd() { + static $factory = null; + if ( ! $factory ) { + $factory = new Factory(); + } + return $factory; + } + + protected static function _delete_all_edd_data() { + edd_setup_components(); + + foreach ( EDD()->components as $component ) { + $thing = $component->get_interface( 'table' ); + + if ( $thing instanceof \EDD\Database\Table ) { + $thing->truncate(); + } + + $thing = $component->get_interface( 'meta' ); + + if ( $thing instanceof \EDD\Database\Table ) { + $thing->truncate(); + } + } + + $edd_taxonomies = array( 'download_category', 'download_tag' ); + $edd_post_types = array( 'download' ); + foreach ( $edd_post_types as $post_type ) { + + $edd_taxonomies = array_merge( $edd_taxonomies, get_object_taxonomies( $post_type ) ); + $items = get_posts( + array( + 'post_type' => $post_type, + 'post_status' => 'any', + 'numberposts' => -1, + 'fields' => 'ids', + ) + ); + + if ( $items ) { + foreach ( $items as $item ) { + wp_delete_post( $item, true ); + } + } + } + + global $wpdb; + /** Delete All the Terms & Taxonomies */ + foreach ( array_unique( array_filter( $edd_taxonomies ) ) as $taxonomy ) { + + $terms = $wpdb->get_results( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ('%s') ORDER BY t.name ASC", $taxonomy ) ); + + // Delete Terms. + if ( $terms ) { + foreach ( $terms as $term ) { + $wpdb->delete( $wpdb->term_relationships, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) ); + $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) ); + $wpdb->delete( $wpdb->terms, array( 'term_id' => $term->term_id ) ); + } + } + + // Delete Taxonomies. + $wpdb->delete( $wpdb->term_taxonomy, array( 'taxonomy' => $taxonomy ), array( '%s' ) ); + } + + /** Delete the Plugin Pages */ + $edd_created_pages = array( 'purchase_page', 'success_page', 'failure_page', 'purchase_history_page' ); + foreach ( $edd_created_pages as $p ) { + if ( ! empty( $edd_settings[ $p ] ) ) { + wp_delete_post( $p, true ); + } + } + + /** Delete all the Plugin Options */ + $edd_options = array( + 'edd_completed_upgrades', + 'edd_default_api_version', + 'edd_earnings_total', + 'edd_earnings_total_without_tax', + 'edd_settings', + 'edd_tracking_notice', + 'edd_tax_rates', + 'edd_use_php_sessions', + 'edd_version', + 'edd_version_upgraded_from', + 'edd_notification_req_timeout', + 'edd_pass_licenses', + 'edd_pass_data', + 'edd_tokenizer_signing_key', + 'edd_use_php_sessions', + 'edd_licensed_extensions', + 'edd_activation_date', + 'edd_pro_activation_date', + 'edd_onboarding_completed', + 'edd_onboarding_started', + 'edd_onboarding_latest_step', + + // Widgets + 'widget_edd_product_details', + 'widget_edd_cart_widget', + 'widget_edd_categories_tags_widget', + + // Deprecated 3.0.0 + 'wp_edd_customers_db_version', + 'wp_edd_customermeta_db_version', + '_edd_table_check', + ); + foreach ( $edd_options as $option ) { + delete_option( $option ); + } + + $site_options = array( + 'edd_all_extension_data', + 'edd_extension_tag_1578_data', + 'edd_extension_product_28530_data', + 'edd_extension_product_375153_data', + 'edd_extension_product_37976_data', + 'edd_pro_license', + 'edd_pro_license_key', + ); + foreach ( $site_options as $site_option ) { + delete_site_option( $site_option ); + } + + /** Delete Capabilities */ + EDD()->roles->remove_caps(); + + /** Delete the Roles */ + $edd_roles = array( 'shop_manager', 'shop_accountant', 'shop_worker', 'shop_vendor' ); + foreach ( $edd_roles as $role ) { + remove_role( $role ); + } + + /** Cleanup Cron Events */ + wp_clear_scheduled_hook( 'edd_daily_scheduled_events' ); + wp_clear_scheduled_hook( 'edd_daily_cron' ); + wp_clear_scheduled_hook( 'edd_weekly_cron' ); + wp_clear_scheduled_hook( 'edd_email_summary_cron' ); + wp_clear_scheduled_hook( 'edd_weekly_scheduled_events' ); + + // Remove any transients we've left behind + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_edd\_%'" ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '\_site\_transient\_edd\_%'" ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_timeout\_edd\_%'" ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '\_site\_transient\_timeout\_edd\_%'" ); + } + + /** + * Checks if all items in the array are of the given type. + * + * @param string $type Type to check against. + * @param array $actual Supplied array to check. + */ + public function assertStringContainsStringOnlyType( $type, $actual ) { + $standard_types = array( + 'numeric', + 'integer', + 'int', + 'float', + 'string', + 'boolean', + 'bool', + 'null', + 'array', + 'object', + 'resource', + 'scalar', + ); + + if ( in_array( $type, $standard_types, true ) ) { + if ( class_exists( 'PHPUnit\Framework\Constraint\isType' ) ) { + $constraint = new \PHPUnit\Framework\Constraint\isType( $type ); + } else { + $constraint = new \PHPUnit_Framework_Constraint_IsType( $type ); + } + } else { + if ( class_exists( 'PHPUnit\Framework\Constraint\IsInstanceOf' ) ) { + $constraint = new \PHPUnit\Framework\Constraint\IsInstanceOf( $type ); + } else { + $constraint = new \PHPUnit_Framework_Constraint_IsInstanceOf( $type ); + } + } + + foreach ( $actual as $item ) { + if ( class_exists( '\PHPUnit\Framework\Assert' ) ) { + \PHPUnit\Framework\Assert::assertThat( $item, $constraint ); + } else { + \PHPUnit_Framework_Assert::assertThat( $item, $constraint ); + } + } + } + +} diff --git a/tests/phpunit/factories/class-edd-factory-for-api-request-logs.php b/tests/phpunit/factories/class-edd-factory-for-api-request-logs.php new file mode 100644 index 00000000000..7a4062e1b32 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-api-request-logs.php @@ -0,0 +1,60 @@ +default_generation_definitions = array( + 'user_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'api_key' => new \WP_UnitTest_Generator_Sequence( 'b8062c469b938352k3bg4656999ed%d' ), + 'token' => new \WP_UnitTest_Generator_Sequence( 'b8062c469b938352k3bg4656999ed%d' ), + 'request' => new \WP_UnitTest_Generator_Sequence( 'edd-api=sales&key=b8062c469b938352k3bg4656999ed%1$d&token=b8062c469b938352k3bg4656999ed%1$d' ), + 'version' => 'v2', + 'ip' => '10.1.1.1', + 'time' => new \WP_UnitTest_Generator_Sequence( '0.0%d' ), + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD\Logs\Api_Request_Log|false + */ + function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); + } + + function create_object( $args ) { + return edd_add_api_request_log( $args ); + } + + function update_object( $log_id, $fields ) { + return edd_update_api_request_log( $log_id, $fields ); + } + + public function delete( $log_id ) { + edd_delete_api_request_log( $log_id ); + } + + public function delete_many( $logs ) { + foreach ( $logs as $log ) { + $this->delete( $log ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $log_id Log ID. + * + * @return \EDD\Logs\Api_Request_Log|false + */ + function get_object_by_id( $log_id ) { + return edd_get_api_request_log( $log_id ); + } +} diff --git a/tests/phpunit/factories/class-edd-factory-for-customer-addresses.php b/tests/phpunit/factories/class-edd-factory-for-customer-addresses.php new file mode 100644 index 00000000000..dd223a85647 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-customer-addresses.php @@ -0,0 +1,62 @@ +default_generation_definitions = array( + 'customer_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'type' => new \WP_UnitTest_Generator_Sequence( 'type%d' ), + 'status' => new \WP_UnitTest_Generator_Sequence( 'status%d' ), + 'address' => new \WP_UnitTest_Generator_Sequence( 'address%d' ), + 'address2' => new \WP_UnitTest_Generator_Sequence( 'address2%d' ), + 'city' => new \WP_UnitTest_Generator_Sequence( 'city%d' ), + 'region' => new \WP_UnitTest_Generator_Sequence( 'region%d' ), + 'postal_code' => new \WP_UnitTest_Generator_Sequence( 'postal_code%d' ), + 'country' => new \WP_UnitTest_Generator_Sequence( 'country%d' ), + ); + } + + public function create_object( $args ) { + return edd_add_customer_address( $args ); + } + + public function update_object( $customer_id, $fields ) { + return edd_update_customer_address( $customer_id, $fields ); + } + + public function delete( $customer_id ) { + edd_delete_customer_address( $customer_id ); + } + + public function delete_many( $customers ) { + foreach ( $customers as $customer ) { + $this->delete( $customer ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param int $customer_address_id Customer address ID. + * + * @return \EDD\Customers\Customer_Address|false + */ + public function get_object_by_id( $customer_address_id ) { + return edd_fetch_customer_address( $customer_address_id ); + } +} diff --git a/tests/phpunit/factories/class-edd-factory-for-customer-email-addresses.php b/tests/phpunit/factories/class-edd-factory-for-customer-email-addresses.php new file mode 100644 index 00000000000..e3560fe0047 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-customer-email-addresses.php @@ -0,0 +1,57 @@ +default_generation_definitions = array( + 'customer_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'type' => new \WP_UnitTest_Generator_Sequence( 'type%d' ), + 'status' => new \WP_UnitTest_Generator_Sequence( 'status%d' ), + 'email' => new \WP_UnitTest_Generator_Sequence( 'user%d@edd.test' ), + ); + } + + public function create_object( $args ) { + return edd_add_customer_email_address( $args ); + } + + public function update_object( $customer_id, $fields ) { + return edd_update_customer_email_address( $customer_id, $fields ); + } + + public function delete( $customer_email_address_id ) { + edd_delete_customer_email_address( $customer_email_address_id ); + } + + public function delete_many( $customers ) { + foreach ( $customers as $customer ) { + $this->delete( $customer ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $customer_email_address_id Customer email address ID. + * + * @return \EDD\Customers\Customer_Email_Address|false + */ + public function get_object_by_id( $customer_email_address_id ) { + return edd_get_customer( $customer_email_address_id ); + } +} diff --git a/tests/phpunit/factories/class-edd-factory-for-customers.php b/tests/phpunit/factories/class-edd-factory-for-customers.php new file mode 100644 index 00000000000..58e79d3f282 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-customers.php @@ -0,0 +1,55 @@ +default_generation_definitions = array( + 'name' => new \WP_UnitTest_Generator_Sequence( 'User %s' ), + 'email' => new \WP_UnitTest_Generator_Sequence( 'user%d@edd.test' ), + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD_Customer|false + */ + function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); + } + + function create_object( $args ) { + return edd_add_customer( $args ); + } + + function update_object( $customer_id, $fields ) { + return edd_update_customer( $customer_id, $fields ); + } + + public function delete( $customer_id ) { + edd_delete_customer( $customer_id ); + } + + public function delete_many( $customers ) { + foreach ( $customers as $customer ) { + $this->delete( $customer ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $customer_id Customer ID. + * + * @return \EDD_Customer|false + */ + function get_object_by_id( $customer_id ) { + return edd_get_customer( $customer_id ); + } +} diff --git a/tests/phpunit/factories/class-edd-factory-for-discounts.php b/tests/phpunit/factories/class-edd-factory-for-discounts.php new file mode 100644 index 00000000000..b8134b90c08 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-discounts.php @@ -0,0 +1,50 @@ +default_generation_definitions = array( + 'product_id' => new \WP_UnitTest_Generator_Sequence( '%f' ), + 'file_id' => new \WP_UnitTest_Generator_Sequence( '%f' ), + 'order_id' => new \WP_UnitTest_Generator_Sequence( '%f' ), + 'price_id' => '1', + 'customer_id' => new \WP_UnitTest_Generator_Sequence( '%f' ), + 'ip' => '10.1.1.1', + 'user_agent' => 'PHPUnit/Unix', + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD\Logs\File_Download_Log|false + */ + function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); + } + + function create_object( $args ) { + return edd_add_file_download_log( $args ); + } + + function update_object( $log_id, $fields ) { + return edd_update_file_download_log( $log_id, $fields ); + } + + public function delete( $log_id ) { + edd_delete_file_download_log( $log_id ); + } + + public function delete_many( $logs ) { + foreach ( $logs as $log ) { + $this->delete( $log ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $log_id Log ID. + * + * @return \EDD\Logs\File_Download_Log|false + */ + function get_object_by_id( $log_id ) { + return edd_get_file_download_log( $log_id ); + } +} \ No newline at end of file diff --git a/tests/phpunit/factories/class-edd-factory-for-logs.php b/tests/phpunit/factories/class-edd-factory-for-logs.php new file mode 100644 index 00000000000..b68ecdf4dad --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-logs.php @@ -0,0 +1,58 @@ +default_generation_definitions = array( + 'object_id' => new \WP_UnitTest_Generator_Sequence( '%f' ), + 'object_type' => 'download', + 'type' => 'sale', + 'title' => new \WP_UnitTest_Generator_Sequence( 'Log title %s' ), + 'content' => new \WP_UnitTest_Generator_Sequence( 'Log message %s' ), + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD\Logs\Log|false + */ + function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); + } + + function create_object( $args ) { + return edd_add_log( $args ); + } + + function update_object( $log_id, $fields ) { + return edd_update_log( $log_id, $fields ); + } + + public function delete( $log_id ) { + edd_delete_log( $log_id ); + } + + public function delete_many( $logs ) { + foreach ( $logs as $log ) { + $this->delete( $log ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $log_id Log ID. + * + * @return \EDD\Logs\Log|false + */ + function get_object_by_id( $log_id ) { + return edd_get_log( $log_id ); + } +} diff --git a/tests/phpunit/factories/class-edd-factory-for-notes.php b/tests/phpunit/factories/class-edd-factory-for-notes.php new file mode 100644 index 00000000000..0dc26c8e7b2 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-notes.php @@ -0,0 +1,57 @@ +default_generation_definitions = array( + 'object_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'object_type' => 'payment', + 'content' => new \WP_UnitTest_Generator_Sequence( 'Payment status changed for object with ID: %d' ), + 'user_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD\Notes\Note|false + */ + function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); + } + + function create_object( $args ) { + return edd_add_note( $args ); + } + + function update_object( $note_id, $fields ) { + return edd_update_note( $note_id, $fields ); + } + + public function delete( $note_id ) { + edd_delete_note( $note_id ); + } + + public function delete_many( $notes ) { + foreach ( $notes as $note ) { + $this->delete( $note ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $note_id Note ID. + * + * @return \EDD\Notes\Note|false + */ + function get_object_by_id( $note_id ) { + return edd_get_note( $note_id ); + } +} \ No newline at end of file diff --git a/tests/phpunit/factories/class-edd-factory-for-order-addresses.php b/tests/phpunit/factories/class-edd-factory-for-order-addresses.php new file mode 100644 index 00000000000..42830e30ff1 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-order-addresses.php @@ -0,0 +1,62 @@ +default_generation_definitions = array( + 'order_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'name' => new \WP_UnitTest_Generator_Sequence( 'name%d' ), + 'status' => new \WP_UnitTest_Generator_Sequence( 'status%d' ), + 'address' => new \WP_UnitTest_Generator_Sequence( 'address%d' ), + 'address2' => new \WP_UnitTest_Generator_Sequence( 'address2%d' ), + 'city' => new \WP_UnitTest_Generator_Sequence( 'city%d' ), + 'region' => new \WP_UnitTest_Generator_Sequence( 'region%d' ), + 'postal_code' => new \WP_UnitTest_Generator_Sequence( 'postal_code%d' ), + 'country' => new \WP_UnitTest_Generator_Sequence( 'country%d' ), + ); + } + + public function create_object( $args ) { + return edd_add_order_address( $args ); + } + + public function update_object( $order_address_id, $fields ) { + return edd_update_order_address( $order_address_id, $fields ); + } + + public function delete( $order_address_id ) { + edd_delete_order_address( $order_address_id ); + } + + public function delete_many( $order_addresses ) { + foreach ( $order_addresses as $order_address ) { + $this->delete( $order_address ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $order_address_id Order address ID. + * + * @return \EDD\Orders\Order_Address|false + */ + public function get_object_by_id( $order_address_id ) { + return edd_get_order_address_by( $order_address_id ); + } +} diff --git a/tests/phpunit/factories/class-edd-factory-for-order-adjustments.php b/tests/phpunit/factories/class-edd-factory-for-order-adjustments.php new file mode 100644 index 00000000000..8e87193e923 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-order-adjustments.php @@ -0,0 +1,63 @@ +default_generation_definitions = array( + 'object_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'object_type' => 'order', + 'type_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'type' => new \WP_UnitTest_Generator_Sequence( 'Adjustment Type %d' ), + 'description' => new \WP_UnitTest_Generator_Sequence( 'Adjustment Description %d' ), + 'subtotal' => new \WP_UnitTest_Generator_Sequence( '%f' ), + 'tax' => new \WP_UnitTest_Generator_Sequence( '%f' ), + 'total' => new \WP_UnitTest_Generator_Sequence( '%f' ), + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD\Orders\Order_Adjustment|false + */ + public function create_and_get( $args = array(), $generation_definitions = null ) { + $this->default_generation_definitions['date_created'] = date( 'Y-m-d h:i:s', mt_rand( 1, time() ) ); + + return parent::create_and_get( $args, $generation_definitions ); + } + + public function create_object( $args ) { + return edd_add_order_adjustment( $args ); + } + + public function update_object( $order_adjustment_id, $fields ) { + return edd_update_order_adjustment( $order_adjustment_id, $fields ); + } + + public function delete( $order_adjustment_id ) { + edd_delete_order_adjustment( $order_adjustment_id ); + } + + public function delete_many( $order_adjustments ) { + foreach ( $order_adjustments as $order_adjustment ) { + $this->delete( $order_adjustment ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $order_adjustment_id Order ID. + * + * @return \EDD\Orders\Order|false + */ + public function get_object_by_id( $order_adjustment_id ) { + return edd_get_order_adjustment( $order_adjustment_id ); + } +} \ No newline at end of file diff --git a/tests/phpunit/factories/class-edd-factory-for-order-items.php b/tests/phpunit/factories/class-edd-factory-for-order-items.php new file mode 100644 index 00000000000..11d4ca59a5f --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-order-items.php @@ -0,0 +1,68 @@ +default_generation_definitions = array( + 'order_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'product_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'product_name' => 'Test Product', + 'price_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'cart_index' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'type' => 'download', + 'status' => 'inherit', + 'quantity' => 1, + 'amount' => 20, + 'subtotal' => 20, + 'tax' => 5, + 'discount' => 5, + 'total' => 20, + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD\Orders\Order|false + */ + public function create_and_get( $args = array(), $generation_definitions = null ) { + $this->default_generation_definitions['date_created'] = date( 'Y-m-d h:i:s', mt_rand( 1, time() ) ); + + return parent::create_and_get( $args, $generation_definitions ); + } + + public function create_object( $args ) { + return edd_add_order_item( $args ); + } + + public function update_object( $order_item_id, $fields ) { + return edd_update_order_item( $order_item_id, $fields ); + } + + public function delete( $order_item_id ) { + edd_delete_order( $order_item_id ); + } + + public function delete_many( $order_items ) { + foreach ( $order_items as $order_item ) { + $this->delete( $order_item ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $order_item_id Order ID. + * + * @return \EDD\Orders\Order|false + */ + public function get_object_by_id( $order_item_id ) { + return edd_get_order_item( $order_item_id ); + } +} \ No newline at end of file diff --git a/tests/phpunit/factories/class-edd-factory-for-order-transactions.php b/tests/phpunit/factories/class-edd-factory-for-order-transactions.php new file mode 100644 index 00000000000..595135ec7c8 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-order-transactions.php @@ -0,0 +1,59 @@ +default_generation_definitions = array( + 'object_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'object_type' => new \WP_UnitTest_Generator_Sequence( 'order%d' ), + 'transaction_id' => new \WP_UnitTest_Generator_Sequence( 'transaction%d' ), + 'gateway' => new \WP_UnitTest_Generator_Sequence( 'gateway%d' ), + 'status' => new \WP_UnitTest_Generator_Sequence( 'status%d' ), + 'total' => new \WP_UnitTest_Generator_Sequence( '%f' ), + ); + } + + public function create_object( $args ) { + return edd_add_order_transaction( $args ); + } + + public function update_object( $order_transaction_id, $fields ) { + return edd_update_order_transaction( $order_transaction_id, $fields ); + } + + public function delete( $order_transaction_id ) { + edd_delete_order_transaction( $order_transaction_id ); + } + + public function delete_many( $order_transactions ) { + foreach ( $order_transactions as $order_transaction ) { + $this->delete( $order_transaction ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $order_transaction_id Order transaction ID. + * + * @return \EDD\Orders\Order_Transaction|false + */ + public function get_object_by_id( $order_transaction_id ) { + return edd_get_order_transaction( $order_transaction_id ); + } +} \ No newline at end of file diff --git a/tests/phpunit/factories/class-edd-factory-for-orders.php b/tests/phpunit/factories/class-edd-factory-for-orders.php new file mode 100644 index 00000000000..015673b86c8 --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory-for-orders.php @@ -0,0 +1,99 @@ +default_generation_definitions = array( + 'status' => 'complete', + 'type' => 'sale', + 'date_completed' => edd_get_utc_date_string(), + 'date_refundable' => EDD()->utils->date( 'now' )->addDays( 30 )->toDateTimeString(), + 'user_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'customer_id' => new \WP_UnitTest_Generator_Sequence( '%d' ), + 'email' => new \WP_UnitTest_Generator_Sequence( 'user%d@edd.test' ), + 'ip' => '10.1.1.1', + 'gateway' => 'manual', + 'mode' => 'live', + 'currency' => 'USD', + 'payment_key' => md5( 'edd' ), + 'subtotal' => 100, + 'tax' => 25, + 'discount' => 5, + 'total' => 120, + ); + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param array $args + * @param null $generation_definitions + * + * @return \EDD\Orders\Order|false + */ + public function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); + } + + public function create_object( $args ) { + $order_id = edd_add_order( $args ); + $order = edd_get_order( $order_id ); + + $oid = edd_add_order_item( array( + 'order_id' => $order_id, + 'product_id' => 1, + 'product_name' => 'Simple Download', + 'status' => $order->status, + 'amount' => 100, + 'subtotal' => 100, + 'discount' => 5, + 'tax' => 25, + 'total' => 120, + 'quantity' => 1, + ) ); + + edd_add_order_adjustment( array( + 'object_type' => 'order', + 'object_id' => $order_id, + 'type' => 'tax_rate', + 'total' => '0.25', + ) ); + + edd_add_order_address( array( + 'order_id' => $order_id, + 'first_name' => 'John', + 'last_name' => 'Smith', + 'country' => 'US', + ) ); + + return $order_id; + } + + public function update_object( $order_id, $fields ) { + return edd_update_order( $order_id, $fields ); + } + + public function delete( $order_id ) { + edd_destroy_order( $order_id ); + } + + public function delete_many( $orders ) { + foreach ( $orders as $order ) { + $this->delete( $order ); + } + } + + /** + * Stub out copy of parent method for IDE type hinting purposes. + * + * @param $order_id Order ID. + * + * @return \EDD\Orders\Order|false + */ + public function get_object_by_id( $order_id ) { + return edd_get_order( $order_id ); + } +} diff --git a/tests/phpunit/factories/class-edd-factory.php b/tests/phpunit/factories/class-edd-factory.php new file mode 100644 index 00000000000..878907a782a --- /dev/null +++ b/tests/phpunit/factories/class-edd-factory.php @@ -0,0 +1,93 @@ +api_request_log = new Factory\API_Request_Log( $this ); + $this->discount = new Factory\Discount( $this ); + $this->customer = new Factory\Customer( $this ); + $this->customer_address = new Factory\Customer_Address( $this ); + $this->customer_email_address = new Factory\Customer_Email_Address( $this ); + $this->file_download_log = new Factory\File_Download_Log( $this ); + $this->log = new Factory\Log( $this ); + $this->note = new Factory\Note( $this ); + $this->order = new Factory\Order( $this ); + $this->order_address = new Factory\Order_Address( $this ); + $this->order_item = new Factory\Order_Item( $this ); + $this->order_adjustment = new Factory\Order_Adjustment( $this ); + $this->order_transaction = new Factory\Order_Transaction( $this ); + } +} \ No newline at end of file diff --git a/tests/phpunit/factory.php b/tests/phpunit/factory.php new file mode 100644 index 00000000000..0c4c6edbc17 --- /dev/null +++ b/tests/phpunit/factory.php @@ -0,0 +1,17 @@ +getMockBuilder( 'EDD\\Reports\\Data\\Charts\\v2\\Bar_Dataset' ) + ->setMethods( null ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertEqualSets( $expected, $bar_dataset->get_fields() ); + } + +} diff --git a/tests/reports/data/charts/v2/tests-dateset.php b/tests/reports/data/charts/v2/tests-dateset.php new file mode 100644 index 00000000000..69e1d184ade --- /dev/null +++ b/tests/reports/data/charts/v2/tests-dateset.php @@ -0,0 +1,229 @@ +mock_Dataset = $this->get_Dataset_mock( 'test' ); + } + + /** + * @covers ::__construct() + */ + public function test_Dataset_should_implement_Error_Logger_Interface() { + $this->assertInstanceOf( 'EDD\\Utils\\Error_Logger_Interface', $this->mock_Dataset ); + } + + /** + * @covers ::get_id() + * @covers ::set_id() + */ + public function test_get_id_should_return_the_dataset_id_supplied_during_instantiation() { + $this->assertSame( 'test', $this->mock_Dataset->get_id() ); + } + + /** + * @covers ::get_endpoint() + * + * @group edd_reports_endpoints + */ + public function test_get_endpoint_should_retrieve_an_Endpoint_object() { + $this->assertInstanceOf( 'EDD\\Reports\\Data\\Endpoint', $this->mock_Dataset->get_endpoint() ); + } + + /** + * @covers ::get_fields() + */ + public function test_get_fields_should_return_the_dataset_fields() { + $this->assertEqualSets( array(), $this->mock_Dataset->get_fields() ); + } + + /** + * @covers ::get_global_fields() + */ + public function test_get_global_fields_should_return_the_global_fields() { + $expected = array( + 'label', 'xAxisID', 'yAxisID', 'data', + 'backgroundColor', 'borderColor', 'borderWidth', + ); + + $this->assertEqualSets( $expected, $this->mock_Dataset->get_global_fields() ); + } + + /** + * @covers ::get_all_fields() + */ + public function test_get_all_fields_should_return_fields_and_global_fields_combined() { + // Abstract fields are empty, so just expect the global fields. + $expected = array( + 'label', 'xAxisID', 'yAxisID', 'data', + 'backgroundColor', 'borderColor', 'borderWidth', + ); + + $this->assertEqualSets( $expected, $this->mock_Dataset->get_all_fields() ); + } + + /** + * @covers ::get_data() + */ + public function test_get_data_should_retrieve_data_from_the_attached_endpoint_based_on_the_dataset_id() { + $expected = array( 40, 20, 30, 10 ); + + $this->assertEqualSets( $expected, $this->mock_Dataset->get_data() ); + } + + /** + * @covers ::validate() + */ + public function test_validate_should_have_errors_if_no_data() { + $dataset = $this->get_Dataset_mock( 'test', array( + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return; + }, + 'options' => array(), + ), + ), + ) ); + + $this->assertTrue( $dataset->has_errors() ); + } + + /** + * @covers ::validate() + */ + public function test_validate_should_log_missing_chart_data_error_if_no_data() { + $dataset = $this->get_Dataset_mock( 'test', array( + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return; + }, + 'options' => array(), + ), + ), + ) ); + + $this->assertTrue( in_array( 'missing_chart_data', $dataset->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::validate() + */ + public function test_validate_should_add_data_key_to_options_if_successful() { + $this->assertArrayHasKey( 'data', $this->mock_Dataset->get_options() ); + } + + /** + * @covers ::parse_data_for_output() + */ + public function test_parse_data_for_output_should_return_single_axis_keyed_data_for_single_level_non_pie_data() { + $expected = array(); + $data = array(); + + for ( $day = 1; $day < 5; $day++ ) { + $timezone = new \DateTimeZone( edd_get_timezone_id() ); + $date = new \DateTime( strtotime( 'Y-m-' . $day ) ); + $data[] = $date->getTimestamp(); + + $date_on_chart = new \DateTime( '@' . $date->getTimestamp() ); + $expected[] = array( + 'x' => $date_on_chart->setTimeZone( $timezone )->format( 'Y-m-d H:i:s' ), + ); + } + + $this->assertEqualSetsWithIndex( $expected, $this->mock_Dataset->parse_data_for_output( $data ) ); + } + + /** + * @covers ::parse_data_for_output() + */ + public function test_parse_data_for_output_should_return_double_axis_keyed_data_for_double_level_non_pie_data() { + $expected = array(); + $data = array(); + + for ( $day = 1; $day < 5; $day++ ) { + $timezone = new \DateTimeZone( edd_get_timezone_id() ); + $date = new \DateTime( strtotime( 'Y-m-' . $day ) ); + $data[] = array( $date->getTimestamp(), $day * 5 ); + + $date_on_chart = new \DateTime( '@' . $date->getTimestamp() ); + $expected[] = array( + 'x' => $date_on_chart->setTimeZone( $timezone )->format( 'Y-m-d H:i:s' ), + 'y' => $day * 5, + ); + } + + $this->assertEqualSetsWithIndex( $expected, $this->mock_Dataset->parse_data_for_output( $data ) ); + } + + /** + * Mocks a Dataset fixture. + * + * @return \EDD\Reports\Data\Charts\v2\Dataset + */ + protected function get_Dataset_mock( $dataset_id, $endpoint_args = array() ) { + $defaults = array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() use ( $dataset_id ) { + return array( + $dataset_id => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 50, + 'datasets' => array( + $dataset_id => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + ), + ), + ) + ); + + $endpoint_args = array_merge( $defaults, $endpoint_args ); + + return $this->getMockForAbstractClass( + 'EDD\\Reports\\Data\\Charts\\v2\\Dataset', + array( $dataset_id, new Chart_Endpoint( $endpoint_args ), $endpoint_args['views']['chart']['options'] ) + ); + } +} diff --git a/tests/reports/data/charts/v2/tests-line-dataset.php b/tests/reports/data/charts/v2/tests-line-dataset.php new file mode 100644 index 00000000000..6919cf66c92 --- /dev/null +++ b/tests/reports/data/charts/v2/tests-line-dataset.php @@ -0,0 +1,39 @@ +getMockBuilder( 'EDD\\Reports\\Data\\Charts\\v2\\Line_Dataset' ) + ->setMethods( null ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertEqualSets( $expected, $line_dataset->get_fields() ); + } + +} diff --git a/tests/reports/data/charts/v2/tests-manifest.php b/tests/reports/data/charts/v2/tests-manifest.php new file mode 100644 index 00000000000..29bf0a50b56 --- /dev/null +++ b/tests/reports/data/charts/v2/tests-manifest.php @@ -0,0 +1,546 @@ +mock_Manifest = $this->get_Manifest_mock( 'test' ); + } + + /** + * @covers ::__construct() + */ + public function test_Manifest_should_implement_Error_Logger_Interface() { + $this->assertInstanceOf( 'EDD\\Utils\\Error_Logger_Interface', $this->mock_Manifest ); + } + + /** + * @covers ::get_type() + * @covers ::set_type() + */ + public function test_get_type_should_return_the_chart_type() { + $this->assertSame( 'pie', $this->mock_Manifest->get_type() ); + } + + /** + * @covers ::get_endpoint() + * @covers ::set_endpoint() + */ + public function test_get_endpoint_should_return_a_Chart_Endpoint() { + $this->assertInstanceOf( 'EDD\\Reports\\Data\\Chart_Endpoint', $this->mock_Manifest->get_endpoint() ); + } + + /** + * @covers ::get_options() + */ + public function test_get_options_should_retrieve_the_options_minus_the_labels_and_datasets() { + $expected = array( + 'cutoutPercentage' => 50, + ); + + $this->assertEqualSetsWithIndex( $expected, $this->mock_Manifest->get_options() ); + } + + /** + * @covers ::get_datasets() + */ + public function test_get_datasets_should_retrieve_an_array_with_Dataset_values() { + $this->assertStringContainsStringOnlyType( 'EDD\\Reports\\Data\\Charts\\v2\\Dataset', $this->mock_Manifest->get_datasets() ); + } + + /** + * @covers ::get_datasets() + */ + public function test_get_datasets_should_be_keyed_with_strings() { + $keys = array_keys( $this->mock_Manifest->get_datasets() ); + + $this->assertStringContainsStringOnlyType( 'string', $keys ); + } + + /** + * @covers ::has_datasets() + */ + public function test_has_datasets_should_return_false_if_no_datasets() { + $manifest = $this->get_Manifest_mock( 'test', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 50, + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + ), + ), + ) + ) ); + + $this->assertFalse( $manifest->has_datasets() ); + } + + /** + * @covers ::has_datasets() + */ + public function test_has_datasets_should_return_true_if_there_are_datasets() { + $this->assertTrue( $this->mock_Manifest->has_datasets() ); + } + + /** + * @covers ::get_labels() + * @covers ::set_labels() + */ + public function test_get_label_should_retrieve_labels_if_set() { + $expected = array( 'First', 'Second', 'Third', 'Fourth' ); + + $this->assertEqualSets( $expected, $this->mock_Manifest->get_labels() ); + } + + /** + * @covers ::get_labels() + * @covers ::set_labels() + */ + public function test_get_labels_should_retrieve_an_empty_array_if_not_set() { + $manifest = $this->get_Manifest_mock( 'test', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 50, + ), + ), + ) + ) ); + + $this->assertEqualSets( array(), $manifest->get_labels() ); + } + + /** + * @covers ::has_labels() + */ + public function test_has_labels_should_return_true_if_labels() { + $this->assertTrue( $this->mock_Manifest->has_labels() ); + } + + /** + * @covers ::has_labels() + */ + public function test_has_labels_should_return_false_if_no_labels() { + $manifest = $this->get_Manifest_mock( 'test', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 50, + ), + ), + ) + ) ); + + $this->assertFalse( $manifest->has_labels() ); + } + + /** + * @covers ::add_dataset() + */ + public function test_add_dataset_should_return_false_on_failure() { + $manifest = $this->get_Manifest_mock( 'test', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'fake', + ), + ), + ) ); + + $this->assertFalse( $manifest->add_dataset( 'test', array() ) ); + } + + /** + * @covers ::get_dataset_handler() + */ + public function test_get_dataset_handler_should_return_an_empty_handler_string_for_an_invalid_type() { + $manifest = $this->get_Manifest_mock( 'test', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'fake', + ), + ), + ) ); + + $this->assertSame( '', $manifest->get_dataset_handler() ); + } + + /** + * @covers ::get_dataset_handler() + */ + public function test_get_dataset_handler_should_return_a_handler_string_for_a_valid_type() { + $this->assertSame( 'EDD\\Reports\\Data\\Charts\\v2\\Pie_Dataset', $this->mock_Manifest->get_dataset_handler() ); + } + + /** + * @covers ::get_chart_data() + */ + public function test_get_chart_data_with_pie_chart_type_should_contain_a_labels_key() { + $this->assertArrayHasKey( 'labels', $this->mock_Manifest->get_chart_data() ); + } + + /** + * @covers ::get_chart_data() + */ + public function test_get_chart_data_with_a_mismatched_dataset_will_skip_including_it() { + $manifest = $this->get_Manifest_mock( 'foo', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 50, + 'datasets' => array( + 'bar' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + ), + ), + ) + ) ); + + $this->assertArrayNotHasKey( 'datasets', $manifest->get_chart_data() ); + } + + /** + * @covers ::get_chart_options() + */ + public function test_get_chart_options_default_pie_options_should_match() { + $expected = array( + 'datasets' => array( + 'test' => array( + 'label' => 'Sales', + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + 'labels' => array( + 0 => 'First', + 1 => 'Second', + 2 => 'Third', + 3 => 'Fourth', + ), + 'animation' => array( + 'duration' => 0, + ), + 'responsive' => true, + 'legend' => array( + 'position' => 'left', + ), + 'cutoutPercentage' => 50, + ); + + $this->assertEqualSetsWithIndex( $expected, $this->mock_Manifest->get_chart_options() ); + } + + /** + * @covers ::get_chart_options() + */ + public function test_get_chart_options_default_non_pie_options_should_match_and_include_the_endpoint_label() { + $manifest = $this->get_Manifest_mock( 'foo', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'line', + 'options' => array( + 'datasets' => array( + 'foo' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + ), + ), + ) + ) ); + + $day_by_day = Reports\get_dates_filter_day_by_day(); + $hour_by_hour = Reports\get_dates_filter_hour_by_hour(); + + $time_unit = 'month'; + $time_format = 'MMM YYYY'; + + if ( $hour_by_hour ) { + $time_unit = 'hour'; + $time_format = 'hA'; + } elseif ( $day_by_day ) { + $time_unit = 'day'; + $time_format = 'MMM D'; + } + + $expected = array( + 'animation' => array( + 'duration' => 0, + ), + 'responsive' => true, + 'hoverMode' => 'index', + 'stacked' => false, + 'title' => array( + 'display' => false, + 'text' => 'Foo Dataset' + ), + 'scales' => array( + 'xAxes' => array( + array( + 'type' => 'time', + 'display' => true, + 'ticks' => array( + 'source' => 'auto', + 'maxRotation' => 0, + ), + 'position' => 'bottom', + 'time' => array( + 'unit' => $time_unit, + 'tooltipFormat' => $time_format, + ), + ), + ), + 'yAxes' => array( + array( + 'type' => 'linear', + 'display' => true, + 'position' => 'left', + 'ticks' => array( + 'formattingType' => 'format', + 'beginAtZero' => true, + 'suggestedMin' => 0, + ), + ), + ), + ), + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + 'datasets' => array( + 'foo' => array( + 'label' => 'Sales', + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $manifest->get_chart_options() ); + } + + /** + * @covers ::is_pie_manifest() + */ + public function test_is_pie_manifest_should_return_true_if_type_is_pie() { + $this->assertTrue( $this->mock_Manifest->is_pie_manifest() ); + } + + /** + * @covers ::is_pie_manifest() + */ + public function test_is_pie_manifest_should_return_true_if_type_is_doughnut() { + $manifest = $this->get_Manifest_mock( 'foo', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'doughnut', + 'options' => array( + 'cutoutPercentage' => 50, + 'datasets' => array( + 'bar' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + ), + ), + ) + ) ); + + $this->assertTrue( $manifest->is_pie_manifest() ); + } + + /** + * @covers ::is_pie_manifest() + */ + public function test_is_pie_manifest_should_return_false_if_type_is_not_pie_or_doughnut() { + $manifest = $this->get_Manifest_mock( 'foo', array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() { + return array( + 'test' => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'line', + 'options' => array( + 'cutoutPercentage' => 50, + 'datasets' => array( + 'bar' => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + ), + ), + ) + ) ); + + $this->assertFalse( $manifest->is_pie_manifest() ); + } + + /** + * Mocks a Manifest fixture. + * + * @return \EDD\Reports\Data\Charts\v2\Manifest + */ + protected function get_Manifest_mock( $dataset_id, $endpoint_args = array() ) { + $defaults = array( + 'id' => 'test_endpoint', + 'label' => __( 'Foo Dataset', 'easy-digital-downloads' ), + 'views' => array( + 'chart' => array( + 'data_callback' => function() use ( $dataset_id ) { + return array( + $dataset_id => array( 40, 20, 30, 10 ), + ); + }, + 'type' => 'pie', + 'options' => array( + 'cutoutPercentage' => 50, + 'datasets' => array( + $dataset_id => array( + 'label' => __( 'Sales', 'easy-digital-downloads' ), + 'backgroundColor' => array( + 'rgb(234,16,109)', + 'rgb(98,133,193)', + 'rgb(151,99,143)', + 'rgb(244,10,43)', + ), + ), + ), + 'labels' => array( 'First', 'Second', 'Third', 'Fourth' ), + ), + ), + ) + ); + + $endpoint_args = array_merge( $defaults, $endpoint_args ); + + return $this->getMockForAbstractClass( + 'EDD\\Reports\\Data\\Charts\\v2\\Manifest', + array( new Chart_Endpoint( $endpoint_args ) ) + ); + } + +} diff --git a/tests/reports/data/charts/v2/tests-pie-dataset.php b/tests/reports/data/charts/v2/tests-pie-dataset.php new file mode 100644 index 00000000000..e9baeed0826 --- /dev/null +++ b/tests/reports/data/charts/v2/tests-pie-dataset.php @@ -0,0 +1,35 @@ +getMockBuilder( 'EDD\\Reports\\Data\\Charts\\v2\\Pie_Dataset' ) + ->setMethods( null ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertEqualSets( $expected, $pie_dataset->get_fields() ); + } + +} diff --git a/tests/reports/data/tests-base-object.php b/tests/reports/data/tests-base-object.php new file mode 100644 index 00000000000..8de20e439cc --- /dev/null +++ b/tests/reports/data/tests-base-object.php @@ -0,0 +1,175 @@ +mock_Base_Object( array( + 'id' => 'foo', + 'label' => 'Foo', + ) ); + + $this->assertTrue( method_exists( $object, 'display' ) ); + } + + /** + * @covers ::get_id() + */ + public function test_get_id_when_created_without_an_id_should_return_null() { + $object = $this->mock_Base_Object( array( + 'label' => 'Foo' + ) ); + + $this->assertNull( $object->get_id() ); + } + + /** + * @covers ::get_id() + * @covers ::set_id() + */ + public function test_get_id_when_created_with_an_id_should_return_that_id() { + $object = $this->mock_Base_Object( array( + 'id' => 'foo', + 'label' => 'Foo', + ) ); + + $this->assertSame( 'foo', $object->get_id() ); + } + + /** + * @covers ::get_label() + */ + public function test_get_label_when_created_without_a_label_should_return_null() { + $object = $this->mock_Base_Object( array( + 'id' => 'foo', + ) ); + + $this->assertNull( $object->get_label() ); + } + + /** + * @covers ::get_label() + * @covers ::set_label() + */ + public function test_get_label_when_created_with_a_label_should_return_that_label() { + $object = $this->mock_Base_Object( array( + 'id' => 'foo', + 'label' => 'Foo', + ) ); + + $this->assertSame( 'Foo', $object->get_label() ); + } + + /** + * @covers ::set_props() + * @group edd_errors + */ + public function test_set_props_with_missing_id_should_flag_WP_Error() { + $object = $this->mock_Base_Object( array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'something' ), + 'display_callback' => '__return_false', + 'data_callback' => '__return_false', + ), + ), + ) ); + + $this->assertTrue( $object->has_errors() ); + } + + /** + * @covers ::set_props() + * @group edd_errors + */ + public function test_set_props_with_missing_id_should_flag_WP_Error_including_code_missing_object_id() { + $object = $this->mock_Base_Object( array( + 'label' => 'Foo', + ) ); + + $this->assertTrue( in_array( 'missing_object_id', $object->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::set_props() + * @group edd_errors + */ + public function test_set_props_with_missing_object_label_should_flag_WP_Error() { + $object = $this->mock_Base_Object( array( + 'id' => 'foo', + ) ); + + $this->assertTrue( $object->has_errors() ); + } + + /** + * @covers ::set_props() + * @group edd_errors + */ + public function test_set_props_with_missing_object_label_should_flag_WP_Error_including_code_missing_object_label() { + $object = $this->mock_Base_Object( array( + 'id' => 'foo', + ) ); + + $this->assertTrue( in_array( 'missing_object_label', $object->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::has_errors() + */ + public function test_has_errors_if_no_errors_should_return_false() { + // Add a completely valid endpoint. + $object = $this->mock_Base_Object( array( + 'id' => 'foo', + 'label' => 'Foo', + ) ); + + $this->assertFalse( $object->has_errors() ); + } + + /** + * @covers ::has_errors() + */ + public function test_has_errors_if_errors_should_return_true() { + $object = $this->mock_Base_Object( array( 'bar' ) ); + + $this->assertTrue( $object->has_errors() ); + } + + /** + * @covers ::get_errors() + */ + public function test_get_errors_should_return_WP_Error_object() { + $object = $this->mock_Base_Object( array( 'bar' ) ); + + $this->assertWPError( $object->get_errors() ); + } + + /** + * Mocks a copy of the Base_Object abstract class. + * + * @param array $args + * @return \EDD\Reports\Data\Base_Object Mocked Base_Object instance. + */ + protected function mock_Base_Object( $args ) { + return $this->getMockForAbstractClass( '\EDD\Reports\Data\Base_Object', array( $args ) ); + } + +} diff --git a/tests/reports/data/tests-chart-endpoint.php b/tests/reports/data/tests-chart-endpoint.php new file mode 100644 index 00000000000..944546186a8 --- /dev/null +++ b/tests/reports/data/tests-chart-endpoint.php @@ -0,0 +1,29 @@ +assertSame( 'chart', $endpoint->get_view() ); + } + +} diff --git a/tests/reports/data/tests-endpoint-registry.php b/tests/reports/data/tests-endpoint-registry.php new file mode 100644 index 00000000000..aedfa51d812 --- /dev/null +++ b/tests/reports/data/tests-endpoint-registry.php @@ -0,0 +1,404 @@ +registry = new Endpoint_Registry(); + } + + /** + * Runs after each test to reset the items array. + * + * @access public + */ + public function tearDown(): void { + $this->registry->exchangeArray( array() ); + + parent::tearDown(); + } + + /** + * @covers ::instance() + */ + public function test_static_registry_should_have_instance_method() { + $this->assertTrue( method_exists( $this->registry, 'instance' ) ); + } + + /** + * @covers ::__call() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_id_should_return_an_empty_array() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' reports endpoint does not exist." ); + + $result = $this->registry->get_endpoint( 'foo' ); + + $this->assertEqualSets( array(), $result ); + } + + /** + * @covers ::__call() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_id_should_throw_an_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' reports endpoint does not exist." ); + + $this->registry->get_endpoint( 'foo' ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_endpoint_with_valid_endpoint_id_should_return_that_endpoint() { + $expected = array( + 'id' => 'foo', + 'label' => 'Foo', + 'priority' => 10, + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false', + 'display_callback' => '\\EDD\\Reports\\default_display_tile', + 'display_args' => array( + 'type' => '' , + 'context' => 'primary', + 'comparison_label' => __( 'All time', 'easy-digital-downloads' ), + ) + ), + ) + ); + + // Add a test endpoint. + $this->registry->register_endpoint( 'foo', $expected ); + + $this->assertEqualSetsWithIndex( $expected, $this->registry->get_endpoint( 'foo' ) ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_unregister_endpoint_with_invalid_endpoint_id_should_affect_no_change() { + // Add a test endpoint. + $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'priority' => 10, + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_empty_array' + ), + ), + ) ); + + $this->registry->unregister_endpoint( 'bar' ); + + $this->assertEqualSets( array( 'foo' ), array_keys( $this->registry->get_endpoints() ) ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_unregister_endpoint_with_valid_endpoint_id_should_unregister_that_endpoint() { + // Add a test endpoint. + $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_empty_array' + ), + ) + ) ); + + $this->registry->unregister_endpoint( 'foo' ); + + $this->assertEqualSets( array(), $this->registry->get_endpoints() ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_endpoints_with_no_sort_should_return_endpoints_in_order_of_registration() { + + $this->add_test_endpoints(); + + $endpoint_ids = array_keys( $this->registry->get_endpoints() ); + + $this->assertEqualSets( array( 'foo', 'bar' ), $endpoint_ids ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_endpoints_with_invalid_sort_should_return_endpoints_in_order_of_registration() { + + $this->add_test_endpoints(); + + $endpoint_ids = array_keys( $this->registry->get_endpoints( 'fake_sort' ) ); + + $this->assertEqualSets( array( 'foo', 'bar' ), $endpoint_ids ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_endpoints_with_ID_sort_should_return_endpoints_in_alphabetical_order_by_ID() { + + $this->add_test_endpoints(); + + $endpoint_ids = array_keys( $this->registry->get_endpoints( 'ID' ) ); + + $this->assertEqualSets( array( 'bar', 'foo' ), $endpoint_ids ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_endpoints_with_priority_sort_should_return_endpoints_in_order_of_priority() { + + $this->add_test_endpoints(); + + $endpoint_ids = array_keys( $this->registry->get_endpoints( 'priority' ) ); + + $this->assertEqualSets( array( 'bar', 'foo' ), $endpoint_ids ); + } + + /** + * @covers ::register_endpoint() + */ + public function test_register_endpoint_with_empty_attributes_should_return_false() { + $this->expectException( 'EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->assertFalse( $this->registry->register_endpoint( 'foo', array() ) ); + } + + /** + * @covers ::register_endpoint() + * @group edd_errors + * @throws \EDD_Exception + */ + public function test_register_endpoint_with_empty_label_should_throw_exception() { + $this->expectException( 'EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->expectExceptionMessage( "The 'label' parameter for the 'foo' item is missing or invalid in 'EDD\Reports\Registry::validate_attributes'." ); + + $this->registry->register_endpoint( 'foo', array( + 'label' => '' + ) ); + } + + /** + * @covers ::register_endpoint() + * @group edd_errors + * @throws \EDD_Exception + */ + public function test_register_endpoint_with_empty_views_should_throw_exception() { + $this->expectException( 'EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->expectExceptionMessage( "The 'views' parameter for the 'foo' item is missing or invalid in 'EDD\Reports\Registry::validate_attributes'." ); + + $added = $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + ) ); + } + + /** + * @covers ::register_endpoint() + * @throws \EDD_Exception + */ + public function test_register_endpoint_with_no_priority_should_set_priority_10() { + $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_empty_array' + ) + ), + ) ); + + $report = $this->registry->get_endpoint( 'foo' ); + + $this->assertSame( 10, $report['priority'] ); + } + + /** + * @covers ::register_endpoint() + * @throws \EDD_Exception + */ + public function test_register_endpoint_with_priority_should_set_that_priority() { + $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'priority' => 15, + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_empty_array' + ) + ), + ) ); + + $report = $this->registry->get_endpoint( 'foo' ); + + $this->assertSame( 15, $report['priority'] ); + } + + /** + * @covers ::register_endpoint() + */ + public function test_register_endpoint_if_successful_should_return_true() { + $result = $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_empty_array', + ), + ), + ) ); + + $this->assertTrue( $result ); + } + + /** + * @covers ::build_endpoint() + */ + public function test_build_endpoint_with_Endpoint_object_should_return_that_object_unchanged() { + $endpoint = $this->mock_Endpoint( array( + 'view' => 'tile', + 'atts' => array() + ) ); + + $this->assertInstanceOf( '\EDD\Reports\Data\Endpoint', $endpoint ); + } + + /** + * @covers ::build_endpoint() + * @group edd_errors + */ + public function test_build_endpoint_with_invalid_endpoint_id_should_return_WP_Error_code_invalid_endpoint() { + /** @var \WP_Error $result */ + $result = $this->registry->build_endpoint( 'fake', '' ); + + $this->assertSame( 'invalid_endpoint', $result->get_error_code() ); + } + + /** + * @covers ::build_endpoint() + * @throws \EDD_Exception + */ + public function test_build_endpoint_with_valid_endpoint_id_valid_type_should_return_an_Endpoint_object() { + $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'some_value' ), + 'display_callback' => '__return_false', + 'data_callback' => '__return_false', + ), + ), + ) ); + + $result = $this->registry->build_endpoint( 'foo', 'tile' ); + + $this->assertInstanceOf( 'EDD\Reports\Data\Endpoint', $result ); + } + + /** + * @covers ::build_endpoint() + * @group edd_errors + * @throws \EDD_Exception + */ + public function test_build_endpoint_with_valid_endpoint_id_invalid_type_should_return_WP_Error_including_invalid_view_error_code() { + $this->add_test_endpoints(); + + $result = $this->registry->build_endpoint( 'foo', 'fake' ); + + $this->assertTrue( in_array( 'invalid_view', $result->get_error_codes() ) ); + } + + /** + * @covers ::validate_views() + * @throws \EDD_Exception + */ + public function test_validate_views_with_invalid_view_should_throw_exception() { + $this->expectException( 'EDD\Reports\Exceptions\Invalid_View' ); + $this->expectExceptionMessage( "The 'fake' view for the 'foo' item is missing or invalid in 'EDD\Reports\Data\Endpoint_Registry::validate_views'" ); + + $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'fake' => array( + 'display_callback' => '__return_false', + 'data_callback' => '__return_false', + ), + ) + ) ); + } + + + /** + * Adds two test endpoints for use with get_endpoints() tests. + * + * @throws \EDD_Exception + */ + protected function add_test_endpoints() { + $this->registry->register_endpoint( 'foo', array( + 'label' => 'Foo', + 'priority' => 10, + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_empty_array' + ), + ) + ) ); + + $this->registry->register_endpoint( 'bar', array( + 'label' => 'Bar', + 'priority' => 5, + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_empty_array' + ), + ) + ) ); + } + + /** + * Mocks a copy of the Endpoint abstract class. + * + * @param array $args + * @return \EDD\Reports\Data\Endpoint Mocked Endpoint instance. + */ + protected function mock_Endpoint( $args ) { + return $this->getMockForAbstractClass( '\EDD\Reports\Data\Endpoint', array( $args ) ); + } + +} diff --git a/tests/reports/data/tests-endpoint-view-registry.php b/tests/reports/data/tests-endpoint-view-registry.php new file mode 100644 index 00000000000..aaca7ff8a8e --- /dev/null +++ b/tests/reports/data/tests-endpoint-view-registry.php @@ -0,0 +1,187 @@ +registry = new \EDD\Reports\Data\Endpoint_View_Registry(); + } + + /** + * Runs after each test to reset the items array. + * + * @access public + */ + public function tearDown(): void { + $this->registry->exchangeArray( array() ); + + parent::tearDown(); + } + + /** + * @covers ::instance() + */ + public function test_static_registry_should_have_instance_method() { + $this->assertTrue( method_exists( $this->registry, 'instance' ) ); + } + + /** + * @covers ::__call() + */ + public function test_get_endpoint_view_with_invalid_endpoint_view_id_should_return_an_empty_array() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' endpoint view does not exist." ); + + $result = $this->registry->get_endpoint_view( 'foo' ); + + $this->assertEqualSets( array(), $result ); + } + + /** + * @covers ::__call() + */ + public function test_get_endpoint_view_with_invalid_report_id_should_throw_an_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' endpoint view does not exist." ); + + $this->registry->get_endpoint_view( 'foo' ); + } + + /** + * @covers ::__call() + */ + public function test_get_endpoint_view_with_valid_view_id_should_return_that_report() { + // Add core views. + $this->add_core_views(); + + $expected = $this->get_expected_view( 'tile' ); + + $this->assertEqualSetsWithIndex( $expected, $this->registry->get_endpoint_view( 'tile' ) ); + } + + /** + * @covers ::__call() + */ + public function test_remove_item_should_affect_no_change() { + // Add core views. + $this->add_core_views(); + + // Remove an invalid report. + $this->registry->remove_item( 'tile' ); + + $this->assertEqualSetsWithIndex( $this->registry->get_core_views(), $this->registry->get_endpoint_views() ); + } + + /** + * @covers ::register_endpoint_view() + * @throws \EDD_Exception + */ + public function test_register_endpoint_view_with_non_core_view_id_should_throw_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' endpoint view is invalid." ); + + $this->registry->register_endpoint_view( 'foo', array() ); + } + + /** + * @covers ::register_endpoint_view() + * @throws \EDD_Exception + */ + public function test_register_endpoint_view_with_group_callback_should_set_that_group_callback() { + $expected = '__return_false'; + + $this->registry->register_endpoint_view( 'tile', array( + 'group_callback' => $expected, + ) ); + + $view = $this->registry->get_endpoint_view( 'tile' ); + + $this->assertSame( $expected, $view['group_callback'] ); + } + + /** + * @covers ::register_endpoint_view() + * @throws \EDD_Exception + */ + public function test_register_endpoint_view_with_handler_should_set_that_handler() { + $expected = '__return_false'; + + $this->registry->register_endpoint_view( 'tile', array( + 'handler' => $expected, + ) ); + + $view = $this->registry->get_endpoint_view( 'tile' ); + + $this->assertSame( $expected, $view['handler'] ); + } + + /** + * @covers ::register_endpoint_view() + * @throws \EDD_Exception + */ + public function test_register_endpoint_view_with_fields_display_callback_should_set_that_display_callback() { + $expected = '__return_false'; + + $this->registry->register_endpoint_view( 'tile', array( + 'fields' => array( + 'display_callback' => $expected, + ), + ) ); + + $view = $this->registry->get_endpoint_view( 'tile' ); + + $this->assertSame( $expected, $view['fields']['display_callback'] ); + } + + /** + * Helper to add a foo report to the registry. + */ + protected function add_core_views() { + $core_views = $this->registry->get_core_views(); + + try { + foreach ( $core_views as $view_id => $atts ) { + $this->registry->register_endpoint_view( $view_id, $atts ); + } + } catch ( \EDD_Exception $exception ) {} + } + + /** + * Helper to retrieve the expected value for retrieving a single report entry or all entries. + * + * @param string $as Optional. The method by which to determine which `$expected` array to return. + * Accepts 'entry' or 'all_reports'. Default 'entry'. + * @return array Array for comparison. + */ + protected function get_expected_view( $as = 'tile' ) { + return $this->registry->get_core_view( $as ); + } + +} diff --git a/tests/reports/data/tests-endpoint.php b/tests/reports/data/tests-endpoint.php new file mode 100644 index 00000000000..2a1f5e5e48e --- /dev/null +++ b/tests/reports/data/tests-endpoint.php @@ -0,0 +1,330 @@ +registry = new \EDD\Reports\Data\Endpoint_Registry(); + } + + /** + * Runs after each test to reset the items array. + * + * @access public + */ + public function tearDown(): void { + $this->registry->exchangeArray( array() ); + + parent::tearDown(); + } + + /** + * @covers ::get_view() + */ + public function test_get_view_when_created_with_invalid_view_should_be_null() { + $endpoint = $this->mock_Endpoint( array() ); + + $this->assertNull( $endpoint->get_view() ); + } + + /** + * @covers ::check_view() + * @group edd_errors + */ + public function test_check_view_with_invalid_view_should_flag_WP_Error() { + // Execute the invisible method via the constructor. + $endpoint = $this->mock_Endpoint( array() ); + + $this->assertTrue( $endpoint->has_errors() ); + } + + /** + * @covers ::check_view() + * @group edd_errors + */ + public function test_check_view_with_invalid_view_should_flag_WP_Error_including_code_invalid_view() { + // Execute the invisible method via the constructor. + $endpoint = $this->mock_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + ) ); + + $errors = $endpoint->get_errors(); + + $this->assertStringContainsString( 'invalid_view', $errors->get_error_code() ); + } + + /** + * @covers ::get_display_args() + */ + public function test_get_display_args_when_created_without_display_args_should_return_an_empty_array() { + $endpoint = $this->mock_Endpoint( array() ); + + $this->assertEqualSets( array(), $endpoint->get_display_args() ); + } + + /** + * @covers ::get_display_args() + * @covers ::set_display_args() + */ + public function test_get_display_args_when_created_with_display_args_should_return_those_args() { + $expected = array( 'something', 'goes', 'here' ); + + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_callback' => function() { + echo 'Hello, world!'; + }, + 'display_args' => $expected + ), + ), + ) ); + + $this->assertEqualSets( $expected, $endpoint->get_display_args() ); + } + + /** + * @covers ::get_display_callback() + */ + public function test_get_display_callback_when_created_without_display_callback_should_return_null() { + $endpoint = $this->mock_Endpoint( array() ); + + $this->assertNull( $endpoint->get_display_callback() ); + } + + /** + * @covers ::get_display_callback() + * @covers ::set_display_callback() + */ + public function test_get_display_callback_when_created_with_display_callback_should_return_that_callback() { + $endpoint = new Tile_Endpoint( array( + 'views' => array( + 'tile' => array( + 'display_callback' => '__return_false' + ), + ), + ) ); + + $this->assertSame( '__return_false', $endpoint->get_display_callback() ); + } + + /** + * @covers ::get_data_callback() + */ + public function test_get_data_callback_when_created_without_data_callback_should_return_null() { + $endpoint = $this->mock_Endpoint( array() ); + + $this->assertNull( $endpoint->get_data_callback() ); + } + + /** + * @covers ::get_data_callback() + * @covers ::set_data_callback() + */ + public function test_get_data_callback_when_created_with_data_callback_should_return_that_callback() { + $endpoint = new Tile_Endpoint( array( + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false' + ), + ), + ) ); + + $this->assertSame( '__return_false', $endpoint->get_data_callback() ); + } + + /** + * @covers ::set_display_args() + * @group edd_errors + */ + public function test_set_display_args_with_non_array_display_args_should_flag_WP_Error_including_code_invalid_view_arg_type() { + // Execute the invisible method via the constructor. + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => 'something', + 'display_callback' => '__return_false', + 'data_callback' => '__return_false', + ), + ), + ) ); + + $errors = $endpoint->get_errors(); + + $this->assertTrue( in_array( 'invalid_view_arg_type', $errors->get_error_codes() ) ); + } + + /** + * @covers ::set_display_props() + * @group edd_errors + */ + public function test_set_display_props_with_empty_view_display_callback_should_flag_WP_Error() { + // Execute the invisible method via the constructor. + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'something' ), + 'data_callback' => '__return_false', + ), + ), + ) ); + + $this->assertTrue( $endpoint->has_errors() ); + } + + /** + * @covers ::set_display_props() + * @group edd_errors + */ + public function test_set_display_props_with_empty_view_display_callback_should_flag_WP_Error_including_code_missing_display_callback() { + // Execute the invisible method via the constructor. + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'something' ), + 'data_callback' => '__return_false', + ), + ), + ) ); + + $errors = $endpoint->get_errors(); + + $this->assertTrue( in_array( 'missing_display_callback', $errors->get_error_codes() ) ); + } + + /** + * @covers ::set_display_callback() + * @group edd_errors + */ + public function test_set_display_callback_with_non_callable_display_callback_should_flag_WP_Error_including_code_invalid_view_arg_type() { + // Execute the invisible method via the constructor. + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'something' ), + 'display_callback' => 'something', + 'data_callback' => '__return_false', + ), + ), + ) ); + + $errors = $endpoint->get_errors(); + + $this->assertTrue( in_array( 'invalid_view_arg_type', $errors->get_error_codes() ) ); + } + + /** + * @covers ::set_display_props() + * @group edd_errors + */ + public function test_set_display_props_with_empty_view_data_callback_should_flag_WP_Error() { + // Execute the invisible method via the constructor. + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'something' ), + 'display_callback' => '__return_false', + ), + ), + ) ); + + $this->assertTrue( $endpoint->has_errors() ); + } + + /** + * @covers ::set_display_props() + * @group edd_errors + */ + public function test_set_display_props_with_empty_view_data_callback_should_flag_WP_Error_including_code_missing_data_callback() { + // Execute the invisible method via the constructor. + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'something' ), + 'display_callback' => '__return_false', + ), + ), + ) ); + + $errors = $endpoint->get_errors(); + + $this->assertTrue( in_array( 'missing_data_callback', $errors->get_error_codes() ) ); + } + + /** + * @covers ::set_data_callback() + * @group edd_errors + */ + public function test_set_data_callback_with_non_callable_data_callback_should_flag_WP_Error_including_code_invalid_view_arg_type() { + // Execute the invisible method via the constructor. + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'something' ), + 'display_callback' => '__return_false', + 'data_callback' => 'something', + ), + ), + ) ); + + $errors = $endpoint->get_errors(); + + $this->assertTrue( in_array( 'invalid_view_arg_type', $errors->get_error_codes() ) ); + } + + /** + * Mocks a copy of the Endpoint abstract class. + * + * @param array $args + * @return \EDD\Reports\Data\Endpoint Mocked Endpoint instance. + */ + protected function mock_Endpoint( $args ) { + return $this->getMockForAbstractClass( '\EDD\Reports\Data\Endpoint', array( $args ) ); + } + +} diff --git a/tests/reports/data/tests-report.php b/tests/reports/data/tests-report.php new file mode 100644 index 00000000000..1164720a1da --- /dev/null +++ b/tests/reports/data/tests-report.php @@ -0,0 +1,564 @@ + 'Foo', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false', + 'display_callback' => '__return_false', + ), + ), + ) ); + + Reports\add_report( 'foo', array( + 'label' => 'Foo', + 'capability' => 'exist', + 'endpoints' => array( + 'tiles' => array( 'foo' ), + ), + 'filters' => array( 'dates' ), + ) ); + + self::$_original_report = self::$report = Reports\get_report( 'foo' ); + } + + /** + * Runs after every test. + */ + public function tearDown(): void { + // Reset $report after every test. + self::$report = self::$_original_report; + + parent::tearDown(); + } + + /** + * @covers \EDD\Reports\Data\Report + * @group edd_errors + */ + public function test_Report_with_empty_endpoints_should_flag_WP_Error() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'capability' => 'view_shop_reports', + 'endpoints' => array(), + 'filters' => array( 'dates' ), + ) ); + + $this->assertTrue( $report->has_errors() ); + } + + /** + * @covers ::$endpoints + * @group edd_errors + */ + public function test_Report_with_empty_endpoints_should_flag_WP_Error_including_code_missing_endpoints() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'capability' => 'view_shop_reports', + 'endpoints' => array(), + 'filters' => array( 'dates' ), + ) ); + + $this->assertTrue( in_array( 'missing_endpoints', $report->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::$capability + * @group edd_errors + */ + public function test_Report_with_empty_capability_should_flag_WP_Error_including_code_missing_capability() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => '', + 'filters' => array( 'dates' ), + ) ); + + $this->assertTrue( in_array( 'missing_capability', $report->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::get_filters() + */ + public function test_get_filters_with_empty_filters_from_registry_should_return_no_filters() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + ) ); + + $this->assertEqualSets( array(), $report->get_filters() ); + } + + /** + * @covers ::get_filters() + */ + public function test_get_filters_with_non_empty_valid_filters_should_return_those_filters() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + 'filters' => array( 'dates', 'products' ), + ) ); + + $this->assertEqualSets( array( 'dates', 'products' ), $report->get_filters() ); + } + + /** + * @covers ::get_filters() + */ + public function test_Report_get_filters_with_a_valid_non_dates_filter_should_not_include_dates() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + 'filters' => array( 'products' ), + ) ); + + $this->assertFalse( in_array( 'dates', $report->get_filters() ) ); + } + + /** + * @covers ::$filters + */ + public function test_Report_with_false_filters_should_return_no_filters() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + 'filters' => false, + ) ); + + $this->assertEqualSets( array(), $report->get_filters() ); + } + + /** + * @covers ::$filters + */ + public function test_Report_with_non_empty_valid_filters_should_set_those_filters() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + 'filters' => array( 'dates', 'products' ), + ) ); + + $this->assertEqualSets( array( 'dates', 'products' ), $report->get_filters() ); + } + + /** + * @covers ::$filters + * @group edd_errors + */ + public function test_Report_with_an_invalid_filter_should_flag_WP_Error_including_code_invalid_report_filter() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + 'filters' => array( 'fake' ), + ) ); + + $this->assertTrue( in_array( 'invalid_report_filter', $report->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::$display_callback + * @covers ::get_display_callback() + */ + public function test_Report_with_empty_display_callback_should_set_the_default() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + ) ); + + $this->assertSame( '\EDD\Reports\default_display_report', $report->get_display_callback() ); + } + + /** + * @covers ::$display_callback + * @covers ::get_display_callback() + */ + public function test_Report_with_non_empty_callable_display_callback_should_set_that_callback() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + 'display_callback' => '__return_false', + ) ); + + $this->assertSame( '__return_false', $report->get_display_callback() ); + } + + /** + * @covers ::$display_callback + * @group edd_errors + */ + public function test_Report_with_non_callable_display_callback_should_flag_WP_Error_including_code_invalid_report_arg_type() { + $report = new Report( array( + 'id' => 'foo', + 'label' => 'Foo', + 'endpoints' => array(), + 'capability' => 'exist', + 'display_callback' => 'fake', + ) ); + + $this->assertTrue( in_array( 'invalid_report_arg_type', $report->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::parse_endpoints() + * @throws \EDD_Exception + */ + public function test_parse_endpoints_with_empty_array_should_add_no_new_endpoints() { + $report = new Report( array() ); + + $report->parse_endpoints( array() ); + + $this->assertEqualSets( array(), $report->get_endpoints() ); + } + + /** + * @covers ::parse_endpoints() + * @throws \EDD_Exception + */ + public function test_parse_endpoints_with_invalid_view_group_should_throw_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'fake' view group does not correspond to a known endpoint view type." ); + + self::$report->parse_endpoints( array( 'fake' => array() ) ); + } + + /** + * @covers ::parse_view_groups() + */ + public function test_parse_view_groups_should_return_group_view_key_value_pairs() { + $expected = array( + 'tiles' => 'tile', + 'charts' => 'chart', + 'tables' => 'table', + ); + + $this->assertEqualSetsWithIndex( $expected, self::$report->parse_view_groups() ); + } + + /** + * @covers ::validate_endpoint() + */ + public function test_validate_endpoint_passed_a_WP_Error_object_should_add_a_new_error_to_errors() { + self::$report->validate_endpoint( 'tiles', new \WP_Error( 'foo' ) ); + + $this->assertTrue( in_array( 'foo', self::$report->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::validate_endpoint() + */ + public function test_validate_endpoint_passed_an_endpoint_with_errors_should_add_that_error() { + $endpoint = new Tile_Endpoint( array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array() + ) ); + + self::$report->validate_endpoint( 'tiles', $endpoint ); + + $this->assertTrue( in_array( 'invalid_endpoint', self::$report->get_errors()->get_error_codes() ) ); + } + + /** + * @covers ::validate_endpoint() + */ + public function test_validate_endpoint_passed_a_legitimate_endpoint_should_add_it_to_the_endpoints_array() { + $endpoint = new Tile_Endpoint( array( + 'id' => 'bar', + 'label' => 'Bar', + 'views' => array( + 'tile' => array( + 'display_callback' => '__return_false', + 'data_callback' => '__return_false', + ), + ), + ) ); + + self::$report->validate_endpoint( 'tiles', $endpoint ); + + $this->assertArrayHasKey( 'bar', self::$report->get_endpoints( 'tiles' ) ); + } + + /** + * @covers ::get_endpoints() + */ + public function test_get_endpoints_with_empty_view_group_should_return_all_endpoints() { + + Reports\register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false', + 'display_callback' => '__return_false', + ), + ), + ) ); + + Reports\register_endpoint( 'bar', array( + 'label' => 'Bar', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false', + 'display_callback' => '__return_false', + ), + ), + ) ); + + Reports\add_report( 'foo', array( + 'id' => 'foo', + 'label' => 'Foo', + 'capability' => 'exist', + 'endpoints' => array( + 'tiles' => array( 'foo', 'bar' ), + ), + 'filters' => array( 'dates' ), + ) ); + + $report = Reports\get_report( 'foo' ); + + $all_endpoints = $report->get_endpoints(); + + $actual = array(); + + foreach ( $all_endpoints as $view_group => $endpoints ) { + foreach ( $endpoints as $endpoint_id => $endpoint ) { + $actual[] = $endpoint_id; + } + } + + $this->assertEqualSets( array( 'foo', 'bar' ), $actual ); + } + + /** + * @covers ::get_endpoints() + */ + public function test_get_endpoints_with_invalid_view_group_should_return_all_endpoints() { + Reports\register_endpoint( 'foo', array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false', + 'display_callback' => '__return_false', + ), + ), + ) ); + + Reports\register_endpoint( 'bar', array( + 'id' => 'bar', + 'label' => 'Bar', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false', + 'display_callback' => '__return_false', + ), + ), + ) ); + + Reports\add_report( 'foo', array( + 'label' => 'Foo', + 'capability' => 'exist', + 'endpoints' => array( + 'tiles' => array( 'foo', 'bar' ), + ), + 'filters' => array( 'dates' ), + ) ); + + $report = Reports\get_report( 'foo' ); + + $all_endpoints = $report->get_endpoints( 'fake' ); + + $actual = array(); + + foreach ( $all_endpoints as $view_group => $endpoints ) { + foreach ( $endpoints as $endpoint_id => $endpoint ) { + $actual[] = $endpoint_id; + } + } + + $this->assertEqualSets( array( 'foo', 'bar' ), $actual ); + } + + /** + * @covers ::get_endpoints() + */ + public function test_get_endpoints_with_valid_view_group_should_return_all_endpoints() { + Reports\register_endpoint( 'foo', array( + 'id' => 'foo', + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'data_callback' => '__return_false', + 'display_callback' => '__return_false', + ), + ), + ) ); + + Reports\add_report( 'foo', array( + 'label' => 'Foo', + 'capability' => 'exist', + 'endpoints' => array( + 'tiles' => array( 'foo' ), + ), + 'filters' => array( 'dates' ), + ) ); + + $report = Reports\get_report( 'foo' ); + + $tiles = $report->get_endpoints( 'tiles' ); + + $actual = array(); + + foreach ( $tiles as $endpoint_id => $endpoint ) { + $actual[] = $endpoint_id; + } + + $this->assertEqualSets( array( 'foo' ), $actual ); + } + + /** + * @covers ::has_endpoints() + */ + public function test_has_endpoints_with_empty_group_and_endpoints_should_return_true() { + $this->assertTrue( self::$report->has_endpoints() ); + } + + /** + * @covers ::has_endpoints() + */ + public function test_has_endpoints_with_group_and_matching_endpoints_should_return_true() { + $this->assertTrue( self::$report->has_endpoints( 'tiles' ) ); + } + + /** + * @covers ::has_endpoints() + */ + public function test_has_endpoints_with_group_and_no_matching_endpoints_should_return_false() { + $this->assertFalse( self::$report->has_endpoints( 'fake' ) ); + } + + /** + * @covers ::get_endpoint() + */ + public function test_get_endpoint_with_valid_endpoint_valid_view_group_should_return_that_endpoint() { + $endpoint = self::$report->get_endpoint( 'foo', 'tiles' ); + + $this->assertSame( 'foo', $endpoint->get_id() ); + } + + /** + * @covers ::get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_valid_endpoint_invalid_view_group_should_return_WP_Error() { + $endpoint_or_error = self::$report->get_endpoint( 'foo', 'fake' ); + + $this->assertWPError( $endpoint_or_error ); + } + + /** + * @covers ::get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_valid_endpoint_invalid_view_group_should_return_WP_Error_including_code_invalid_report_endpoint() { + $endpoint_or_error = self::$report->get_endpoint( 'foo', 'fake' ); + + $this->assertTrue( in_array( 'invalid_report_endpoint', $endpoint_or_error->get_error_codes() ) ); + } + + /** + * @covers ::get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_valid_view_group_should_return_WP_Error() { + $endpoint_or_error = self::$report->get_endpoint( 'fake', 'tiles' ); + + $this->assertWPError( $endpoint_or_error ); + } + + /** + * @covers ::get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_valid_view_group_should_return_WP_Error_including_code_invalid_report_endpoint() { + $endpoint_or_error = self::$report->get_endpoint( 'fake', 'tiles' ); + + $this->assertTrue( in_array( 'invalid_report_endpoint', $endpoint_or_error->get_error_codes() ) ); + } + + /** + * @covers ::get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_invalid_view_group_should_return_WP_Error() { + $endpoint_or_error = self::$report->get_endpoint( 'fake', 'fake' ); + + $this->assertWPError( $endpoint_or_error ); + } + + /** + * @covers ::get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_invalid_view_group_should_return_WP_Error_including_code_invalid_report_endpoint() { + $endpoint_or_error = self::$report->get_endpoint( 'fake', 'fake' ); + + $this->assertTrue( in_array( 'invalid_report_endpoint', $endpoint_or_error->get_error_codes() ) ); + } + +} diff --git a/tests/reports/data/tests-reports-registry.php b/tests/reports/data/tests-reports-registry.php new file mode 100644 index 00000000000..012d2c6ff71 --- /dev/null +++ b/tests/reports/data/tests-reports-registry.php @@ -0,0 +1,400 @@ +registry = new \EDD\Reports\Data\Report_Registry(); + } + + /** + * Runs after each test to reset the items array. + * + * @access public + */ + public function tearDown(): void { + $this->registry->exchangeArray( array() ); + + parent::tearDown(); + } + + /** + * @covers ::instance() + */ + public function test_static_registry_should_have_instance_method() { + $this->assertTrue( method_exists( $this->registry, 'instance' ) ); + } + + /** + * @covers ::__call() + */ + public function test_get_report_with_invalid_report_id_should_return_an_empty_array() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' report does not exist." ); + + $result = $this->registry->get_report( 'foo' ); + + $this->assertEqualSets( array(), $result ); + } + + /** + * @covers ::__call() + */ + public function test_get_report_with_invalid_report_id_should_throw_an_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' report does not exist." ); + + $this->registry->get_report( 'foo' ); + } + + /** + * @covers ::__call() + */ + public function test_get_report_with_valid_report_id_should_return_that_report() { + // Add a test report. + $this->add_test_report(); + + $expected = $this->get_expected_report(); + + $this->assertEqualSetsWithIndex( $expected, $this->registry->get_report( 'foo' ) ); + } + + /** + * @covers ::__call() + */ + public function test_remove_report_with_invalid_report_should_affect_no_change() { + // Add a report. + $this->add_test_report(); + + // Remove an invalid report. + $this->registry->remove_report( 'bar' ); + + $expected = $this->get_expected_report( 'all_reports' ); + + $this->assertEqualSetsWithIndex( $expected, $this->registry->get_reports() ); + } + + /** + * @covers ::__call() + */ + public function test_remove_report_with_valid_report_should_remove_that_report() { + // Add a report. + $this->add_test_report(); + + // Remove it. + $this->registry->remove_report( 'foo' ); + + $this->assertEqualSets( array(), $this->registry->get_reports() ); + } + + /** + * @covers ::add_report() + */ + public function test_add_report_with_empty_attributes_should_return_false() { + $this->expectException( 'EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->expectExceptionMessage( "The 'label' parameter for the 'foo' item is missing or invalid in 'EDD\Reports\Registry::validate_attributes'." ); + $this->assertFalse( $this->registry->add_report( 'foo', array() ) ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_with_empty_label_should_throw_exception() { + $this->expectException( 'EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->expectExceptionMessage( "The 'label' parameter for the 'foo' item is missing or invalid in 'EDD\Reports\Registry::validate_attributes'." ); + + $this->registry->add_report( 'foo', array( + 'label' => '', + 'endpoints' => array( + 'tiles' => array(), + ), + 'filters' => array( 'dates' ), + ) ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_with_empty_endpoints_should_throw_exception() { + $this->expectException( 'EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->expectExceptionMessage( "The 'endpoints' parameter for the 'foo' item is missing or invalid in 'EDD\Reports\Registry::validate_attributes'." ); + + $added = $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'endpoints' => array(), + 'filters' => array( 'dates' ), + ) ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_with_invalid_filter_should_throw_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' report contains one or more invalid filters." ); + + $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'endpoints' => array( + 'tiles' => array(), + ), + 'filters' => array( 'bar' ), + ) ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_with_non_string_filter_should_throw_exception() { + $this->expectException( 'EDD\Utils\Exception' ); + $this->expectExceptionMessage( "The 'foo' report contains one or more invalid filters." ); + + $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'endpoints' => array( + 'tiles' => array() + ), + 'filters' => array( 123 ), + ) ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_with_valid_filter_should_return_true() { + $added = $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'endpoints' => array( + 'tiles' => array(), + ), + ) ); + + $this->assertTrue( $added ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_without_filters_should_default_to_date_filter() { + $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'endpoints' => array( + 'tiles' => array(), + ), + ) ); + + $report = $this->registry->get_report( 'foo' ); + + $this->assertEqualSets( array( 'dates', 'taxes' ), $report['filters'] ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_with_no_priority_should_set_priority_10() { + $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'endpoints' => array( + 'tiles' => array(), + ), + ) ); + + $report = $this->registry->get_report( 'foo' ); + + $this->assertSame( 10, $report['priority'] ); + } + + /** + * @covers ::add_report() + * @throws \EDD_Exception + */ + public function test_add_report_with_priority_should_set_that_priority() { + $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'priority' => 15, + 'endpoints' => array( + 'tiles' => array() + ), + 'filters' => array( 'dates' ), + ) ); + + $report = $this->registry->get_report( 'foo' ); + + $this->assertSame( 15, $report['priority'] ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_reports_with_no_sort_should_return_reports_in_order_of_registration() { + + $this->add_test_reports_for_sort(); + + $result = array_keys( $this->registry->get_reports() ); + + $this->assertEqualSets( array( 'foo', 'bar' ), $result ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_reports_with_invalid_sort_should_return_reports_in_order_of_registration() { + + $this->add_test_reports_for_sort(); + + $result = array_keys( $this->registry->get_reports( 'fake_sort' ) ); + + $this->assertEqualSets( array( 'foo', 'bar' ), $result ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_reports_with_ID_sort_should_return_reports_in_alphabetical_order_by_ID() { + + $this->add_test_reports_for_sort(); + + $result = array_keys( $this->registry->get_reports( 'ID' ) ); + + $this->assertEqualSets( array( 'bar', 'foo' ), $result ); + } + + /** + * @covers ::__call() + * @throws \EDD_Exception + */ + public function test_get_reports_with_priority_sort_should_return_reports_in_order_of_priority() { + + $this->add_test_reports_for_sort(); + + $result = array_keys( $this->registry->get_reports( 'priority' ) ); + + $this->assertEqualSets( array( 'bar', 'foo' ), $result ); + } + + /** + * Helper to add a foo report to the registry. + */ + protected function add_test_report() { + try { + $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'endpoints' => array( + 'tiles' => array() + ), + 'filters' => array( 'dates' ), + ) ); + } catch ( \EDD_Exception $exception ) {} + } + + /** + * Helper to retrieve the expected value for retrieving a single report entry or all entries. + * + * @param string $as Optional. The method by which to determine which `$expected` array to return. + * Accepts 'entry' or 'all_reports'. Default 'entry'. + * @return array Array for comparison. + */ + protected function get_expected_report( $as = 'entry' ) { + if ( ! in_array( $as, array( 'entry', 'all_reports' ) ) ) { + $as = 'entry'; + } + + switch( $as ) { + case 'entry': + $expected = array( + 'id' => 'foo', + 'label' => 'Foo', + 'priority' => 10, + 'group' => 'core', + 'capability' => 'view_shop_reports', + 'endpoints' => array( + 'tiles' => array() + ), + 'filters' => array( 'dates' ), + ); + break; + + case 'all_reports': + $expected = array( + 'foo' => array( + 'id' => 'foo', + 'label' => 'Foo', + 'priority' => 10, + 'group' => 'core', + 'capability' => 'view_shop_reports', + 'endpoints' => array( + 'tiles' => array() + ), + 'filters' => array( 'dates' ), + ) + ); + break; + + default: + $expected = array(); + break; + } + + return $expected; + } + + /** + * Adds two test reports for use when comparing sorting results. + * + * @throws \EDD_Exception + */ + protected function add_test_reports_for_sort() { + $this->registry->add_report( 'foo', array( + 'label' => 'Foo', + 'priority' => 10, + 'endpoints' => array( + 'tiles' => array() + ), + 'filters' => array( 'dates' ), + ) ); + + $this->registry->add_report( 'bar', array( + 'label' => 'Bar', + 'priority' => 5, + 'endpoints' => array( + 'tiles' => array() + ), + 'filters' => array( 'dates' ), + ) ); + } +} diff --git a/tests/reports/data/tests-table-endpoint.php b/tests/reports/data/tests-table-endpoint.php new file mode 100644 index 00000000000..09fff6405a0 --- /dev/null +++ b/tests/reports/data/tests-table-endpoint.php @@ -0,0 +1,29 @@ +assertSame( 'table', $endpoint->get_view() ); + } + +} diff --git a/tests/reports/data/tests-tile-endpoint.php b/tests/reports/data/tests-tile-endpoint.php new file mode 100644 index 00000000000..4b18c90fd5e --- /dev/null +++ b/tests/reports/data/tests-tile-endpoint.php @@ -0,0 +1,29 @@ +assertSame( 'tile', $endpoint->get_view() ); + } + +} diff --git a/tests/reports/tests-init.php b/tests/reports/tests-init.php new file mode 100644 index 00000000000..3c8e5149d14 --- /dev/null +++ b/tests/reports/tests-init.php @@ -0,0 +1,66 @@ +assertFileExists( $path_to_file ); + } + + /** + * Data provider for test_bootstrap method. + */ + public function _test_bootstrap_dp() { + $reports_dir = EDD_PLUGIN_DIR . 'includes/reports/'; + + return array( + // Functions. + array( $reports_dir . 'reports-functions.php' ), + + // Exceptions. + array( $reports_dir . 'exceptions/class-invalid-parameter.php' ), + array( $reports_dir . 'exceptions/class-invalid-view.php' ), + array( $reports_dir . 'exceptions/class-invalid-view-parameter.php' ), + + // Dependencies. + array( $reports_dir . 'class-registry.php' ), + array( $reports_dir . 'data/class-base-object.php' ), + + // Reports. + array( $reports_dir . 'data/class-report-registry.php' ), + array( $reports_dir . 'data/class-report.php' ), + + // Endpoints. + array( $reports_dir . 'data/class-endpoint.php' ), + array( $reports_dir . 'data/class-tile-endpoint.php' ), + array( $reports_dir . 'data/class-table-endpoint.php' ), + array( $reports_dir . 'data/class-chart-endpoint.php' ), + array( $reports_dir . 'data/class-endpoint-registry.php' ), + + // Chart Dependencies. + array( $reports_dir . 'data/charts/v2/class-manifest.php' ), + array( $reports_dir . 'data/charts/v2/class-dataset.php' ), + array( $reports_dir . 'data/charts/v2/class-bar-dataset.php' ), + array( $reports_dir . 'data/charts/v2/class-line-dataset.php' ), + array( $reports_dir . 'data/charts/v2/class-pie-dataset.php' ), + ); + } +} diff --git a/tests/reports/tests-registry.php b/tests/reports/tests-registry.php new file mode 100644 index 00000000000..3b465015a29 --- /dev/null +++ b/tests/reports/tests-registry.php @@ -0,0 +1,78 @@ +registry = new \EDD\Reports\Registry(); + } + + /** + * Runs after each test to reset the items array. + * + * @access public + */ + public function tearDown(): void { + $this->registry->exchangeArray( array() ); + + parent::tearDown(); + } + + /** + * @covers ::validate_attributes() + * @throws \EDD_Exception + */ + public function test_validate_attributes_should_throw_exception_if_attribute_is_empty_and_not_filtered() { + $this->expectException( '\EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->expectExceptionMessage( "The 'foo' parameter for the 'some_item_id' item is missing or invalid in 'EDD\Reports\Registry::validate_attributes'." ); + + $this->registry->validate_attributes( array( 'foo' => '' ), 'some_item_id' ); + } + + /** + * @covers ::validate_attributes() + * @throws \EDD_Exception + */ + public function test_validate_attributes_should_not_throw_exception_if_attribute_is_empty_and_filtered() { + $attributes = array( + 'foo' => 'bar', + 'baz' => '' + ); + + $filter = array( 'foo' ); + + $this->expectException( '\EDD\Reports\Exceptions\Invalid_Parameter' ); + $this->expectExceptionMessage( "The 'baz' parameter for the 'some_item_id' item is missing or invalid in 'EDD\Reports\Registry::validate_attributes'." ); + + /* + * Tough to actually test for no exception, so we'll have to settle + * for testing that the first (filtered) attribute _doesn't_ throw one. + */ + $this->registry->validate_attributes( $attributes, 'some_item_id', $filter ); + } +} diff --git a/tests/reports/tests-reports-functions.php b/tests/reports/tests-reports-functions.php new file mode 100644 index 00000000000..55e6efc4853 --- /dev/null +++ b/tests/reports/tests-reports-functions.php @@ -0,0 +1,897 @@ +utils->date(); + } + + /** + * Runs after every test method. + */ + public function tearDown(): void { + unset( $_REQUEST['filter_from'] ); + unset( $_REQUEST['filter_to'] ); + unset( $_REQUEST['range'] ); + + /** @var \EDD\Reports\Data\Report_Registry|\WP_Error $registry */ + $registry = EDD()->utils->get_registry( 'reports' ); + $registry->exchangeArray( array() ); + + parent::tearDown(); + } + + /** + * @covers \EDD\Reports\get_current_report() + */ + public function test_get_current_report_should_use_the_value_of_the_tab_var_when_set() { + $_REQUEST['view'] = 'overview'; + + $this->assertSame( 'overview', Reports\get_current_report() ); + } + + /** + * @covers \EDD\Reports\get_current_report() + */ + public function test_get_current_report_should_use_the_sanitized_value_of_the_tab_var_when_set() { + $_REQUEST['view'] = 'sales/figures'; + + $this->assertSame( 'salesfigures', Reports\get_current_report() ); + } + + /** + * @covers \EDD\Reports\get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_id_should_return_WP_Error() { + $result = Reports\get_endpoint( 'fake', 'tile' ); + + $this->assertWPError( $result ); + } + + /** + * @covers \EDD\Reports\get_endpoint() + * @group edd_errors + */ + public function test_get_endpoint_with_invalid_endpoint_id_should_return_WP_Error_code_invalid_endpoint() { + $result = Reports\get_endpoint( 'fake', 'tile' ); + + $this->assertSame( 'invalid_endpoint', $result->get_error_code() ); + } + + /** + * @covers \EDD\Reports\get_endpoint() + * @throws \EDD_Exception + */ + public function test_get_endpoint_with_valid_endpoint_id_valid_type_should_return_an_Endpoint_object() { + Reports\register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'some_value' ), + 'display_callback' => '__return_false', + 'data_callback' => '__return_false', + ), + ), + ) ); + + $registry = EDD()->utils->get_registry( 'reports:endpoints' ); + + $result = Reports\get_endpoint( 'foo', 'tile' ); + + $this->assertInstanceOf( 'EDD\Reports\Data\Endpoint', $result ); + } + + /** + * @covers \EDD\Reports\get_endpoint() + * @group edd_errors + * @throws \EDD_Exception + */ + public function test_get_endpoint_with_valid_endpoint_id_invalid_type_should_return_WP_Error_including_invalid_view_error_code() { + Reports\register_endpoint( 'foo', array( + 'label' => 'Foo', + 'views' => array( + 'tile' => array( + 'display_args' => array( 'some_value' ), + 'display_callback' => '__return_false', + 'data_callback' => '__return_false', + ), + ), + ) ); + + $result = Reports\get_endpoint( 'foo', 'fake' ); + + $this->assertSame( 'invalid_view', $result->get_error_code() ); + } + + /** + * @covers \EDD\Reports\parse_endpoint_views() + */ + public function test_get_endpoint_views_should_return_the_defaults() { + $views = Reports\get_endpoint_views(); + + $this->assertEqualSets( array( 'tile', 'chart', 'table' ), array_keys( $views ) ); + } + + /** + * @covers \EDD\Reports\parse_endpoint_views() + */ + public function test_parse_endpoint_views_with_invalid_view_should_leave_it_intact() { + $expected = array( + 'fake' => array( + 'display_callback' => '__return_false' + ), + ); + + $this->assertEqualSetsWithIndex( $expected, Reports\parse_endpoint_views( $expected ) ); + } + + /** + * @covers \EDD\Reports\parse_endpoint_views() + */ + public function test_parse_endpoint_views_with_valid_view_should_inject_defaults() { + $expected = array( + 'tile' => array( + 'data_callback' => '__return_zero', + 'display_callback' => 'EDD\Reports\default_display_tile', + 'display_args' => array( + 'type' => '' , + 'context' => 'primary', + 'comparison_label' => __( 'All time', 'easy-digital-downloads' ), + ), + ), + ); + + $views = array( + 'tile' => array( + 'data_callback' => '__return_zero', + ), + ); + + $this->assertEqualSetsWithIndex( $expected, Reports\parse_endpoint_views( $views ) ); + } + + /** + * @covers \EDD\Reports\parse_endpoint_views() + */ + public function test_parse_endpoint_views_should_strip_invalid_fields() { + $views = array( + 'tile' => array( + 'fake_field' => 'foo', + ), + ); + + $result = Reports\parse_endpoint_views( $views ); + + $this->assertArrayNotHasKey( 'fake_field', $result['tile'] ); + } + + /** + * @covers \EDD\Reports\parse_endpoint_views() + */ + public function test_parse_endpoint_views_should_inject_default_display_args() { + $expected = array( + 'type' => 'number', + 'context' => 'primary', + 'comparison_label' => __( 'All time', 'easy-digital-downloads' ), + ); + + $views = array( + 'tile' => array( + 'display_args' => array( + 'type' => 'number', + ) + ) + ); + + $result = Reports\parse_endpoint_views( $views ); + + $this->assertEqualSetsWithIndex( $expected, $result['tile']['display_args'] ); + } + + /** + * @covers \EDD\Reports\validate_endpoint_view() + */ + public function test_validate_endpoint_view_with_valid_view_should_return_true() { + $this->assertTrue( Reports\validate_endpoint_view( 'tile' ) ); + } + + /** + * @covers \EDD\Reports\validate_endpoint_view() + */ + public function test_validate_endpoint_view_with_invalid_view_should_return_false() { + $this->assertFalse( Reports\validate_endpoint_view( 'fake' ) ); + } + + /** + * @covers \EDD\Reports\get_endpoint_handler() + */ + public function test_get_endpoint_handler_with_valid_view_should_return_the_handler() { + $expected = 'EDD\Reports\Data\Tile_Endpoint'; + + $this->assertSame( $expected, Reports\get_endpoint_handler( 'tile' ) ); + } + + /** + * @covers \EDD\Reports\get_endpoint_handler() + */ + public function test_get_endpoint_handler_with_invalid_view_should_return_empty() { + $this->assertSame( '', Reports\get_endpoint_handler( 'fake' ) ); + } + + /** + * @covers \EDD\Reports\get_endpoint_group_callback() + */ + public function test_get_endpoint_group_callback_with_tile_view_should_return_that_group_callback() { + $expected = 'EDD\Reports\default_display_tiles_group'; + + $this->assertSame( $expected, Reports\get_endpoint_group_callback( 'tile' ) ); + } + + /** + * @covers \EDD\Reports\get_endpoint_group_callback() + */ + public function test_get_endpoint_group_callback_with_table_view_should_return_that_group_callback() { + $expected = 'EDD\Reports\default_display_tables_group'; + + $this->assertSame( $expected, Reports\get_endpoint_group_callback( 'table' ) ); + } + + /** + * @covers \EDD\Reports\get_endpoint_group_callback() + */ + public function test_get_endpoint_group_callback_with_invalid_view_should_return_an_empty_string() { + $this->assertSame( '', Reports\get_endpoint_group_callback( 'fake' ) ); + } + + /** + * @covers \EDD\Reports\get_filters() + */ + public function test_get_filters_should_return_records_for_all_official_filters() { + $expected = array( 'dates', 'products', 'product_categories', 'taxes', 'gateways', 'discounts', 'regions', 'countries', 'currencies' ); + + $this->assertEqualSets( $expected, array_keys( Reports\get_filters() ) ); + } + + /** + * @covers \EDD\Reports\validate_filter() + */ + public function test_validate_filter_with_valid_filter_should_return_true() { + $this->assertTrue( Reports\validate_filter( 'dates' ) ); + } + + /** + * @covers \EDD\Reports\validate_filter() + */ + public function test_validate_filter_with_invalid_filter_should_return_false() { + $this->assertFalse( Reports\validate_filter( 'fake' ) ); + } + + /** + * @covers \EDD\Reports\get_filter_value() + */ + public function test_get_filter_value_with_invalid_filter_should_return_an_empty_string() { + $this->assertSame( '', Reports\get_filter_value( 'fake' ) ); + } + + /** + * @covers \EDD\Reports\get_filter_value() + */ + public function test_get_filter_value_with_a_valid_filter_should_retrieve_that_filters_value() { + $expected = array( + 'from' => date( 'Y-m-d 00:00:00' ), + 'to' => date( 'Y-m-d 23:59:59' ), + 'range' => 'today', + 'relative_range' => 'previous_period', + ); + + $_GET['range'] = 'today'; + $_GET['relative_range'] = 'previous_period'; + $_GET['filter_from'] = $expected['from']; + $_GET['filter_to'] = $expected['to']; + + $this->assertEqualSetsWithIndex( $expected, Reports\get_filter_value( 'dates' ) ); + } + + /** + * @covers \EDD\Reports\get_dates_filter_options() + * @group edd_dates + */ + public function test_get_dates_filter_options_should_match_defaults() { + $expected = array( + 'other' => __( 'Custom', 'easy-digital-downloads' ), + 'today' => __( 'Today', 'easy-digital-downloads' ), + 'yesterday' => __( 'Yesterday', 'easy-digital-downloads' ), + 'this_week' => __( 'This Week', 'easy-digital-downloads' ), + 'last_week' => __( 'Last Week', 'easy-digital-downloads' ), + 'last_30_days' => __( 'Last 30 Days', 'easy-digital-downloads' ), + 'this_month' => __( 'Month to Date', 'easy-digital-downloads' ), + 'last_month' => __( 'Last Month', 'easy-digital-downloads' ), + 'this_quarter' => __( 'Quarter to Date', 'easy-digital-downloads' ), + 'last_quarter' => __( 'Last Quarter', 'easy-digital-downloads' ), + 'this_year' => __( 'Year to Date', 'easy-digital-downloads' ), + 'last_year' => __( 'Last Year', 'easy-digital-downloads' ), + ); + + $this->assertEqualSetsWithIndex( $expected, Reports\get_dates_filter_options() ); + } + + /** + * @covers \EDD\Reports\get_dates_filter() + * @group edd_dates + */ + public function test_get_dates_filter_should_return_strings() { + $expected = array( + 'start' => self::$date->copy()->startOfMonth()->toDateTimeString(), + 'end' => self::$date->copy()->endOfDay()->toDateTimeString(), + 'range' => 'this_month', + ); + + $result = Reports\get_dates_filter(); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $result ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\get_dates_filter() + * @group edd_dates + */ + public function test_get_dates_filter_objects_as_values_should_return_objects() { + $expected = array( + 'start' => self::$date->copy()->subDay( 30 )->startOfDay(), + 'end' => self::$date->copy()->endOfDay(), + ); + + $result = Reports\get_dates_filter( 'objects' ); + + $this->assertInstanceOf( '\EDD\Utils\Date', $result['start'] ); + $this->assertInstanceOf( '\EDD\Utils\Date', $result['end'] ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_this_month_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->startOfMonth()->toDateTimeString(), + 'end' => self::$date->copy()->endOfDay()->toDateTimeString(), + 'range' => 'this_month', + ); + + $result = Reports\parse_dates_for_range( 'this_month' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_last_month_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->subMonthNoOverflow( 1 )->startOfMonth()->toDateTimeString(), + 'end' => self::$date->copy()->subMonthNoOverflow( 1 )->endOfMonth()->toDateTimeString(), + 'range' => 'last_month', + ); + + $result = Reports\parse_dates_for_range( 'last_month' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_overflow_last_month_range_should_return_those_dates() { + $overflow_day = '2020-03-30 00:00:00'; + $overflow_date = EDD()->utils->date( $overflow_day ); + + $expected = array( + 'start' => ( new \DateTime( '2020-02-01 00:00:00' ) )->format( 'Y-m-d H:i' ), + 'end' => ( new \DateTime( '2020-02-29 23:59:59' ) )->format( 'Y-m-d H:i' ), + 'range' => 'last_month', + ); + + $result = Reports\parse_dates_for_range( 'last_month', $overflow_day ); + + // Explicitly strip seconds in case the test is slow. + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_today_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->setTimezone( edd_get_timezone_id() )->startOfDay()->setTimezone( 'UTC' ), + 'end' => self::$date->copy()->setTimezone( edd_get_timezone_id() )->endOfDay()->setTimezone( 'UTC' ), + 'range' => 'today', + ); + + $result = Reports\parse_dates_for_range( 'today' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_yesterday_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->setTimezone( edd_get_timezone_id() )->subDay( 1 )->startOfDay()->setTimezone( 'UTC' ), + 'end' => self::$date->copy()->setTimezone( edd_get_timezone_id() )->subDay( 1 )->endOfDay()->setTimezone( 'UTC' ), + 'range' => 'yesterday', + ); + + $result = Reports\parse_dates_for_range( 'yesterday' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_this_week_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->startOfWeek()->toDateTimeString(), + 'end' => self::$date->copy()->endOfDay()->toDateTimeString(), + 'range' => 'this_week', + ); + + $result = Reports\parse_dates_for_range( 'this_week' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_last_week_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->subWeek( 1 )->startOfWeek()->toDateTimeString(), + 'end' => self::$date->copy()->subWeek( 1 )->endOfWeek()->toDateTimeString(), + 'range' => 'last_week', + ); + + $result = Reports\parse_dates_for_range( 'last_week' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_last_30_days_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->subDay( 30 )->startOfDay()->toDateTimeString(), + 'end' => self::$date->copy()->endOfDay()->toDateTimeString(), + 'range' => 'last_30_days', + ); + + $result = Reports\parse_dates_for_range( 'last_30_days' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_this_quarter_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->startOfQuarter()->toDateTimeString(), + 'end' => self::$date->copy()->endOfDay()->toDateTimeString(), + 'range' => 'this_quarter', + ); + + $result = Reports\parse_dates_for_range( 'this_quarter' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_last_quarter_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->subQuarter( 1 )->startOfQuarter()->toDateTimeString(), + 'end' => self::$date->copy()->subQuarter( 1 )->endOfQuarter()->toDateTimeString(), + 'range' => 'last_quarter', + ); + + $result = Reports\parse_dates_for_range( 'last_quarter' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_this_year_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->startOfYear()->toDateTimeString(), + 'end' => self::$date->copy()->endOfDay()->toDateTimeString(), + 'range' => 'this_year', + ); + + $result = Reports\parse_dates_for_range( 'this_year' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_last_year_range_should_return_those_dates() { + $expected = array( + 'start' => self::$date->copy()->subYear( 1 )->startOfYear()->toDateTimeString(), + 'end' => self::$date->copy()->subYear( 1 )->endOfYear()->toDateTimeString(), + 'range' => 'last_year', + ); + + $result = Reports\parse_dates_for_range( 'last_year' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_other_range_should_return_dates_for_request_vars() { + $dates = array( + 'from' => self::$date->copy()->subCentury( 2 )->startOfDay()->toDateTimeString(), + 'to' => self::$date->copy()->addCentury( 2 )->endOfDay()->toDateTimeString(), + ); + + $_GET['range'] = 'other'; + $_GET['relative_range'] = 'previous_period'; + $_GET['filter_from'] = $dates['from']; + $_GET['filter_to'] = $dates['to']; + + $expected = array( + 'start' => $dates['from'], + 'end' => $dates['to'], + 'range' => 'other', + ); + + $result = Reports\parse_dates_for_range( 'other' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_dates_for_range() + * @group edd_dates + */ + public function test_parse_dates_for_range_with_invalid_range_no_report_id_no_range_var_should_use_this_month() { + $expected = array( + 'start' => self::$date->copy()->startOfMonth()->toDateTimeString(), + 'end' => self::$date->copy()->endOfDay()->toDateTimeString(), + 'range' => 'this_month', + ); + + $result = Reports\parse_dates_for_range( 'fake' ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\get_dates_filter_range() + * @group edd_dates + */ + public function test_get_dates_filter_range_with_no_preset_range_should_defualt_to_last_30_days() { + $this->assertSame( 'this_month', Reports\get_dates_filter_range() ); + } + + /** + * @covers \EDD\Reports\get_dates_filter_range() + * @group edd_dates + */ + public function test_get_dates_filter_range_with_non_default_range_set_should_return_that_reports_range() { + $_GET['range'] = 'last_quarter'; + $_GET['relative_range'] = 'previous_period'; + + $this->assertSame( 'last_quarter', Reports\get_dates_filter_range() ); + } + + public function test_no_filters_should_default_to_this_month() { + // Default to last 30 days for filter value. + $dates = Reports\parse_dates_for_range( 'this_month' ); + + $expected = array( + 'from' => $dates['start']->format( 'Y-m-d' ), + 'to' => $dates['end']->format( 'Y-m-d' ), + 'range' => 'this_month', + 'relative_range' => 'previous_period', + ); + + $this->assertEqualSetsWithIndex( $expected, Reports\get_filter_value( 'dates' ) ); + } + + /** + * @covers \EDD\Reports\parse_relative_dates_for_range() + * @group edd_dates + */ + public function test_parse_relative_dates_for_previous_period_in_last_month_should_return_those_dates() { + $range = 'last_month'; + $relative_range = 'previous_period'; + + $dates = Reports\parse_dates_for_range( $range ); + $days_diff = $dates['start']->copy()->diffInDays( $dates['end'], true ) + 1; + + $expected = array( + 'start' => $dates['start']->copy()->subDays( $days_diff ), + 'end' => $dates['end']->copy()->subDays( $days_diff ), + 'range' => $range, + ); + + $result = Reports\parse_relative_dates_for_range( $range, $relative_range ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_relative_dates_for_range() + * @group edd_dates + */ + public function test_parse_relative_dates_for_previous_month_in_last_month_should_return_those_dates() { + $range = 'last_month'; + $relative_range = 'previous_month'; + + $dates = Reports\parse_dates_for_range( $range ); + $expected = array( + 'start' => $dates['start']->copy()->subMonth( 1 ), + 'end' => $dates['end']->copy()->subMonthNoOverflow( 1 ), + 'range' => $range, + ); + + $result = Reports\parse_relative_dates_for_range( $range, $relative_range ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_relative_dates_for_range() + * @group edd_dates + */ + public function test_parse_relative_dates_for_previous_quarter_in_last_month_should_return_those_dates() { + $range = 'last_month'; + $relative_range = 'previous_quarter'; + + $dates = Reports\parse_dates_for_range( $range ); + $expected = array( + 'start' => $dates['start']->copy()->subQuarter( 1 ), + 'end' => $dates['end']->copy()->subQuarter( 1 ), + 'range' => $range, + ); + + $result = Reports\parse_relative_dates_for_range( $range, $relative_range ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + /** + * @covers \EDD\Reports\parse_relative_dates_for_range() + * @group edd_dates + */ + public function test_parse_relative_dates_for_previous_year_in_last_month_should_return_those_dates() { + $range = 'last_month'; + $relative_range = 'previous_year'; + + $dates = Reports\parse_dates_for_range( $range ); + $expected = array( + 'start' => $dates['start']->copy()->subYear( 1 ), + 'end' => $dates['end']->copy()->subMonthNoOverflow( 0 )->subYear( 1 ), + 'range' => $range, + ); + + $result = Reports\parse_relative_dates_for_range( $range, $relative_range ); + + // Explicitly strip seconds in case the test is slow. + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + public function test_parse_relative_dates_for_previous_month_with_fewer_days_should_return_last_day_of_last_month() { + $range = 'this_month'; + $relative_range = 'previous_month'; + $date = '2018-03-31 00:00:00'; + $result = Reports\parse_relative_dates_for_range( $range, $relative_range, $date ); + $formatted = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEquals( '2018-02-28 23:59', $formatted['end'] ); + } + + public function test_parse_dates_for_range_with_last_quarter_range_should_return_those_dates_utc_plus() { + $date = EDD()->utils->date( 'now', 'UTC', false ); + $expected = array( + 'start' => $date->copy()->subQuarter( 1 )->startOfQuarter(), + 'end' => $date->copy()->subQuarter( 1 )->endOfQuarter(), + ); + + $date = EDD()->utils->date( 'now', 'GMT+5', false ); + $result = array( + 'start' => $date->copy()->subQuarter( 1 )->startOfQuarter(), + 'end' => $date->copy()->subQuarter( 1 )->endOfQuarter(), + ); + + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + public function test_parse_dates_for_range_with_last_quarter_range_should_return_those_dates_utc_negative() { + $date = EDD()->utils->date( 'now', 'UTC', false ); + $expected = array( + 'start' => $date->copy()->subQuarter( 1 )->startOfQuarter(), + 'end' => $date->copy()->subQuarter( 1 )->endOfQuarter(), + ); + + $date = EDD()->utils->date( 'now', 'GMT-5', false ); + $result = array( + 'start' => $date->copy()->subQuarter( 1 )->startOfQuarter(), + 'end' => $date->copy()->subQuarter( 1 )->endOfQuarter(), + ); + + $expected = $this->strip_seconds( $expected ); + $result = $this->strip_seconds( $this->objects_to_date_strings( $result ) ); + + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + public function test_gross_order_status() { + $expected = array( + 'complete', + 'refunded', + 'partially_refunded', + 'revoked', + ); + + $this->assertSame( $expected, edd_get_gross_order_statuses() ); + } + + public function test_net_order_status() { + $expected = array( + 'complete', + 'partially_refunded', + 'revoked', + ); + + $this->assertSame( $expected, edd_get_net_order_statuses() ); + } + + /** + * Strips the seconds from start and end datetime strings to guard against slow tests. + * + * @param array $dates Start/end dates array. + * @return array Start/end dates minus their seconds. + */ + protected function strip_seconds( $dates ) { + $dates['start'] = date( 'Y-m-d H:i', strtotime( $dates['start'] ) ); + $dates['end'] = date( 'Y-m-d H:i', strtotime( $dates['end'] ) ); + + return $dates; + } + + /** + * Converts start and end date objects to strings. + * + * @param array $dates Start/end date objects array. + * @return array Start/end date strings array. + */ + protected function objects_to_date_strings( $dates ) { + $dates['start'] = $dates['start']->toDateTimeString(); + $dates['end'] = $dates['end']->toDateTimeString(); + + return $dates; + } +} diff --git a/tests/taxes/tests-tax-rates-compat.php b/tests/taxes/tests-tax-rates-compat.php new file mode 100644 index 00000000000..0bee1b5acd7 --- /dev/null +++ b/tests/taxes/tests-tax-rates-compat.php @@ -0,0 +1,57 @@ + 'US', + 'state' => 'AL', + 'rate' => 15, + ), + array( + 'country' => 'US', + 'state' => 'AZ', + 'rate' => .15, + ), + array( + 'country' => 'US', + 'state' => 'TN', + 'rate' => 9.25, + ), + array( + 'country' => 'NO', + 'state' => '', + 'rate' => 25, + 'global' => 1, + ), + ); + + update_option( 'edd_tax_rates', $tax_rates ); + edd_update_option( 'enable_taxes', true ); + } + + public static function wpTearDownAfterClass() { + delete_option( 'edd_tax_rates' ); + edd_update_option( 'enable_taxes', false ); + } + + public function test_get_tax_rate_AZ_equals_0015() { + $this->assertEquals( 0.0015, edd_get_tax_rate( 'US', 'AZ' ) ); + } + + public function test_get_tax_rate_TN_equals_0925() { + $this->assertEquals( 0.0925, edd_get_tax_rate( 'US', 'TN' ) ); + } + + public function test_get_tax_rate_NO_equals_25() { + $this->assertEquals( .25, edd_get_tax_rate( 'NO', 'Norge' ) ); + } +} diff --git a/tests/taxes/tests-tax-rates.php b/tests/taxes/tests-tax-rates.php new file mode 100644 index 00000000000..d9256bd2b9b --- /dev/null +++ b/tests/taxes/tests-tax-rates.php @@ -0,0 +1,124 @@ + 'global', + 'amount' => floatval( 10 ), + ) + ); + $country_rate = edd_add_tax_rate( + array( + 'name' => 'US', + 'scope' => 'country', + 'amount' => floatval( 15 ), + ) + ); + $region_rate = edd_add_tax_rate( + array( + 'name' => 'US', + 'scope' => 'region', + 'amount' => floatval( 9.25 ), + 'description' => 'TN', + ) + ); + self::$fallback_rate = edd_get_adjustment( $fallback_rate ); + self::$country_rate = edd_get_adjustment( $country_rate ); + self::$region_rate = edd_get_adjustment( $region_rate ); + } + + public static function wpTearDownAfterClass() { + edd_delete_adjustment( self::$fallback_rate->id ); + edd_delete_adjustment( self::$country_rate->id ); + edd_delete_adjustment( self::$region_rate->id ); + } + + /** + * @covers edd_get_tax_rate_by_location + */ + public function test_edd_get_tax_rate_by_location_no_country_should_return_fallback_rate() { + $tax_rate = edd_get_tax_rate_by_location( + array( + 'country' => '', + 'region' => '', + ) + ); + + $this->assertEquals( self::$fallback_rate->id, $tax_rate->id ); + } + + /** + * @covers edd_get_tax_rate_by_location + */ + public function test_edd_get_tax_rate_by_location_random_country_should_return_fallback_rate() { + $tax_rate = edd_get_tax_rate_by_location( + array( + 'country' => 'UK', + 'region' => '', + ) + ); + + $this->assertEquals( self::$fallback_rate->id, $tax_rate->id ); + } + + /** + * @covers edd_get_tax_rate_by_location + */ + public function test_edd_get_tax_rate_by_location_this_country_should_return_country_rate() { + $tax_rate = edd_get_tax_rate_by_location( + array( + 'country' => 'US', + 'region' => 'OH', + ) + ); + + $this->assertEquals( self::$country_rate->id, $tax_rate->id ); + } + + /** + * @covers edd_get_tax_rate_by_location + */ + public function test_edd_get_tax_rate_by_location_this_state_should_return_region_rate() { + $tax_rate = edd_get_tax_rate_by_location( + array( + 'country' => 'US', + 'region' => 'TN', + ) + ); + + $this->assertEquals( self::$region_rate->id, $tax_rate->id ); + } + + public function test_edd_get_tax_rate_by_location_country_rate_with_description_returns_country_rate() { + edd_update_adjustment( + self::$country_rate->id, + array( + 'description' => 'TN', + ) + ); + $country_rate = edd_get_adjustment( self::$country_rate->id ); + $tax_rate = edd_get_tax_rate_by_location( + array( + 'country' => 'US', + 'region' => 'KS', + ) + ); + + $this->assertEquals( self::$country_rate->id, $tax_rate->id ); + } +} diff --git a/tests/taxes/tests-tax.php b/tests/taxes/tests-tax.php new file mode 100644 index 00000000000..c20c2b38a8e --- /dev/null +++ b/tests/taxes/tests-tax.php @@ -0,0 +1,399 @@ +post->create( array( + 'post_title' => 'Test Download', + 'post_type' => 'download', + 'post_status' => 'publish', + ) ); + + $meta = array( + 'edd_price' => '10.00', + ); + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + self::$download = edd_get_download( $post_id ); + + self::$order = edd_get_order( Helpers\EDD_Helper_Payment::create_simple_payment_with_tax() ); + + edd_update_order_status( self::$order->ID, 'complete' ); + + edd_update_option( 'enable_taxes', true ); + $rates_to_create = array( + array( + 'scope' => 'global', + 'name' => '', + 'amount' => 3.6, + ), + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'AL', + 'amount' => 15, + ), + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'AZ', + 'amount' => .15, + ), + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'TX', + 'amount' => .13, + ), + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'AR', + 'amount' => .09, + ), + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'HI', + 'amount' => .63, + ), + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'LA', + 'amount' => .96, + ), + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'TN', + 'amount' => 9.25, + ), + ); + foreach ( $rates_to_create as $rate ) { + $rate['amount'] = floatval( $rate['amount'] ); + + self::$rate_ids[] = edd_add_tax_rate( $rate ); + } + } + + // Disable taxes and delete rates. + public static function wpTearDownAfterClass() { + edd_update_option( 'enable_taxes', false ); + + foreach ( self::$rate_ids as $rate_id ) { + edd_delete_adjustment( $rate_id ); + } + } + + public function test_use_taxes() { + $this->assertTrue( edd_use_taxes() ); + } + + public function test_get_tax_rates() { + $this->assertIsArray( edd_get_tax_rates() ); + } + + public function test_edd_get_tax_rate_is_float() { + $this->assertIsFloat( edd_get_tax_rate( 'US', 'AL' ) ); + } + + public function test_edd_get_tax_rate_state_returns_region_rate() { + $this->assertEquals( '0.15', edd_get_tax_rate( 'US', 'AL' ) ); + } + + public function test_edd_get_tax_rate_other_region_KS_is_fallback() { + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'US', 'KS' ), 3 ) ); + } + + public function test_edd_get_tax_rate_other_region_AK_is_fallback() { + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'US', 'AK' ), 3 ) ); + } + + public function test_edd_get_tax_rate_other_region_CA_is_fallback() { + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'US', 'CA' ), 3 ) ); + } + + public function test_edd_get_tax_rate_other_country_JP_is_fallback() { + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'JP' ), 3 ) ); + } + + public function test_edd_get_tax_rate_other_country_BR_is_fallback() { + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'BR' ), 3 ) ); + } + + public function test_edd_get_tax_rate_other_country_CN_is_fallback() { + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'CN' ), 3 ) ); + } + + public function test_edd_get_tax_rate_other_country_HK_is_fallback() { + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'HK' ), 3 ) ); + } + + public function test_get_tax_rate_AZ_equals_0015() { + $this->assertEquals( 0.0015, edd_get_tax_rate( 'US', 'AZ' ) ); + } + + public function test_get_tax_rate_TX_equals_13() { + $this->assertEquals( 0.0013, edd_get_tax_rate( 'US', 'TX' ) ); + } + + public function test_get_tax_rate_AR_equals_9() { + $this->assertEquals( 0.0009, edd_get_tax_rate( 'US', 'AR' ) ); + } + + public function test_get_tax_rate_HI_equals_63() { + $this->assertEquals( 0.0063, edd_get_tax_rate( 'US', 'HI' ) ); + } + + public function test_get_tax_rate_LA_equals_96() { + $this->assertEquals( 0.0096, edd_get_tax_rate( 'US', 'LA' ) ); + } + + public function test_get_tax_rate_TN_equals_0925() { + $this->assertEquals( 0.0925, edd_get_tax_rate( 'US', 'TN' ) ); + } + + public function test_get_tax_rate_by_country_returns_global_rate() { + $this->assertIsFloat( edd_get_tax_rate( 'CA', 'AB' ) ); + $this->assertEquals( 0.036, round( edd_get_tax_rate( 'CA', 'AB' ), 3 ) ); + } + + public function test_get_tax_rate_no_parameters_returns_global_rate() { + $this->assertIsFloat( edd_get_tax_rate() ); + $this->assertEquals( 0.036, round( edd_get_tax_rate(), 3 ) ); + } + + public function test_get_tax_rate_post() { + $_POST['billing_country'] = 'US'; + $_POST['state'] = 'AL'; + $this->assertEquals( '0.15', edd_get_tax_rate() ); + + // Reset to origin + unset( $_POST['billing_country'] ); + unset( $_POST['state'] ); + } + + public function test_get_tax_rate_user_address() { + $this->setExpectedIncorrectUsage( 'add_user_meta()/update_user_meta()' ); + $this->setExpectedIncorrectUsage( 'get_user_meta()' ); + + global $current_user; + + $current_user = new \WP_User( 1 ); + $user_id = get_current_user_id(); + + update_user_meta( $user_id, '_edd_user_address', array( + 'line1' => 'First address', + 'line2' => 'Line two', + 'city' => 'MyCity', + 'zip' => '12345', + 'country' => 'US', + 'state' => 'AL', + ) ); + + $this->assertEquals( '0.15', edd_get_tax_rate() ); + } + + public function test_get_tax_rate_country() { + $country_rate_id = edd_add_tax_rate( + array( + 'name' => 'NL', + 'scope' => 'country', + 'amount' => floatval( 21 ), + ) + ); + + // Assert + $this->assertEquals( 0.21, edd_get_tax_rate( 'NL' ) ); + + // Delete the new adjustment. + edd_delete_adjustment( $country_rate_id ); + } + + public function test_get_formatted_tax_rate() { + $this->assertEquals( '3.6%', edd_get_formatted_tax_rate() ); + } + + public function test_calculate_tax() { + $this->assertEquals( 1.944, round( edd_calculate_tax( 54 ), 3 ) ); + $this->assertEquals( 1.9692, round( edd_calculate_tax( 54.7 ), 4 ) ); + $this->assertEquals( 5.5386, round( edd_calculate_tax( 153.85 ), 4 ) ); + $this->assertEquals( 9.29916, round( edd_calculate_tax( 258.31 ), 5 ) ); + $this->assertEquals( 37.41552, round( edd_calculate_tax( 1039.32 ), 5 ) ); + $this->assertEquals( 361.58724, round( edd_calculate_tax( 10044.09 ), 5 ) ); + $this->assertEquals( 0, edd_calculate_tax( - 1.50 ) ); + } + + public function test_calculate_tax_amount_AZ_equals_810() { + $this->assertEquals( 0.08, edd_format_amount( edd_calculate_tax( 54, 'US', 'AZ' ) ) ); + } + + public function test_calculate_tax_amount_TX_equals_711() { + $this->assertEquals( 0.07, edd_format_amount( edd_calculate_tax( 54.7, 'US', 'TX' ) ) ); + } + + public function test_calculate_tax_amount_AR_equals_1385() { + $this->assertEquals( 0.14, edd_format_amount( edd_calculate_tax( 153.85, 'US', 'AR' ) ) ); + } + + public function test_calculate_tax_amount_HI_equals_16274() { + $this->assertEquals( 1.63, edd_format_amount( edd_calculate_tax( 258.31, 'US', 'HI' ) ) ); + } + + public function test_calculate_tax_amount_LA_equals_99775() { + $this->assertEquals( 9.98, edd_format_amount( edd_calculate_tax( 1039.32, 'US', 'LA' ) ) ); + } + + public function test_calculate_tax_amount_TN_equals_277() { + $this->assertEquals( 2.77, edd_format_amount( edd_calculate_tax( 29.99, 'US', 'TN' ) ) ); + } + + public function test_calculate_tax_amount_price_includes_tax() { + + // Prepare test + $origin_price_include_tax = edd_get_option( 'prices_include_tax' ); + edd_update_option( 'prices_include_tax', 'yes' ); + + // Asserts + $this->assertEquals( 1.87644787645, round( edd_calculate_tax( 54 ), 11 ) ); + $this->assertEquals( 1.90077220077, round( edd_calculate_tax( 54.7 ), 11 ) ); + $this->assertEquals( 5.34613899614, round( edd_calculate_tax( 153.85 ), 11 ) ); + $this->assertEquals( 8.97602316602, round( edd_calculate_tax( 258.31 ), 11 ) ); + $this->assertEquals( 36.1153667954, round( edd_calculate_tax( 1039.32 ), 10 ) ); + $this->assertEquals( 349.02243243243356118910014629364013671875, round( edd_calculate_tax( 10044.09 ), 38 ) ); + + // Reset to origin + edd_update_option( 'prices_include_tax', $origin_price_include_tax ); + } + + public function test_get_sales_tax_for_year() { + $this->assertEquals( '11.0', edd_get_sales_tax_for_year( date( 'Y' ) ) ); + $this->assertEquals( '0', edd_get_sales_tax_for_year( date( 'Y' ) - 1 ) ); + } + + public function test_sales_tax_for_year() { + ob_start(); + edd_sales_tax_for_year( date( 'Y' ) ); + $this_year = ob_get_clean(); + + ob_start(); + edd_sales_tax_for_year( date( 'Y' ) - 1 ); + $last_year = ob_get_clean(); + + $this->assertEquals( '$11.00', $this_year ); + $this->assertEquals( '$0.00', $last_year ); + } + + public function test_prices_show_tax_on_checkout() { + $this->assertFalse( edd_prices_show_tax_on_checkout() ); + } + + public function test_prices_include_tax() { + $this->assertFalse( edd_prices_include_tax() ); + } + + public function test_is_cart_taxed() { + $this->assertTrue( edd_is_cart_taxed() ); + } + + public function test_display_tax_rates() { + $this->assertFalse( edd_display_tax_rate() ); + } + + public function test_cart_needs_tax_address_fields() { + $this->assertIsBool( edd_cart_needs_tax_address_fields() ); + $this->assertTrue( edd_cart_needs_tax_address_fields() ); + } + + public function test_cart_needs_tax_address_fields_false() { + + // Prepare test + $existing_enable_taxes = edd_get_option( 'enable_taxes' ); + edd_update_option( 'enable_taxes', false ); + + // Assert + $this->assertFalse( edd_cart_needs_tax_address_fields() ); + + // Reset to origin + edd_update_option( 'enable_taxes', $existing_enable_taxes ); + } + + public function test_download_is_exclusive_of_tax() { + $this->assertFalse( edd_download_is_tax_exclusive( self::$download->ID ) ); + } + + public function test_get_payment_tax() { + $this->assertEquals( 11.000000000, edd_get_payment_tax( self::$order->ID ), 2 ); + } + + public function test_payment_tax_updates() { + // Test backwards compat bug in issue/3324 + $this->assertEquals( 11.000000000, self::$order->tax ); + $current_meta = edd_get_payment_meta( self::$order->id ); + + edd_update_payment_meta( self::$order->id, '_edd_payment_meta', $current_meta ); + $this->assertEquals( 11.000000000, edd_get_payment_tax( self::$order->id ) ); + + // Test that when we update _edd_payment_tax, we update the _edd_payment_meta + edd_update_payment_meta( self::$order->id, '_edd_payment_tax', 10 ); + + $meta_array = edd_get_payment_meta( self::$order->id, '_edd_payment_meta', true ); + + $this->assertEquals( 10, $meta_array['tax'] ); + $this->assertEquals( 10, edd_get_payment_tax( self::$order->id ) ); + + // Test that when we update the _edd_payment_meta, we update the _edd_payment_tax + $current_meta = edd_get_payment_meta( self::$order->id, '_edd_payment_meta', true ); + $current_meta['tax'] = 20; + edd_update_payment_meta( self::$order->id, '_edd_payment_meta', $current_meta ); + $this->assertEquals( 20, edd_get_payment_tax( self::$order->id ) ); + } + + public function test_update_option_gets_new_rate_amount() { + + $tn_new_rate = edd_add_tax_rate( + array( + 'scope' => 'region', + 'name' => 'US', + 'description' => 'TN', + 'amount' => 19.25, + ) + ); + + $this->assertEquals( .1925, edd_get_tax_rate( 'US', 'TN' ) ); + } +} diff --git a/tests/tests-api.php b/tests/tests-api.php new file mode 100755 index 00000000000..8ac625936af --- /dev/null +++ b/tests/tests-api.php @@ -0,0 +1,602 @@ +init(); + flush_rewrite_rules( false ); + + self::$api = new \EDD_API(); + + self::$user_id = self::factory()->user->create( array( + 'role' => 'administrator', + ) ); + EDD()->api->user_id = self::$user_id; + $user = new \WP_User( self::$user_id ); + $user->add_cap( 'view_shop_reports' ); + $user->add_cap( 'view_shop_sensitive_data' ); + $user->add_cap( 'manage_shop_discounts' ); + + $roles = new \EDD_Roles; + $roles->add_roles(); + $roles->add_caps(); + + wp_set_current_user( self::$user_id ); + + self::$api->add_endpoint( $wp_rewrite ); + + $post_id = self::factory()->post->create( array( + 'post_title' => 'Test Download', + 'post_type' => 'download', + 'post_status' => 'publish', + ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 20, + ), + array( + 'name' => 'Advanced', + 'amount' => 100, + ), + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0, + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all', + ), + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 129.43, + '_edd_download_sales' => 59, + '_edd_download_limit_override_1' => 1, + ); + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + self::$post = get_post( $post_id ); + + $user = get_userdata( 1 ); + + $user_info = array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'discount' => 'none', + ); + + $download_details = array( + array( + 'id' => self::$post->ID, + 'options' => array( + 'price_id' => 1, + ), + ), + ); + + $total = 0; + $prices = get_post_meta( $download_details[0]['id'], 'edd_variable_prices', true ); + $item_price = $prices[1]['amount']; + $total += $item_price; + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => self::$post->ID, + 'item_number' => array( + 'id' => self::$post->ID, + 'options' => array( + 'price_id' => 1, + ), + ), + 'item_price' => 100, + 'subtotal' => 100, + 'price' => 100, + 'tax' => 0, + 'quantity' => 1, + ), + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending', + ); + + $_SERVER['REMOTE_ADDR'] = '10.0.0.0'; + + self::$payment_id = edd_insert_payment( $purchase_data ); + + edd_update_payment_status( self::$payment_id, 'complete' ); + + self::$api_output = self::$api->get_products(); + self::$api_output_sales = self::$api->get_recent_sales(); + + $_POST['edd_set_api_key'] = 1; + EDD()->api->update_key( self::$user_id ); + } + + public function setup(): void { + parent::setUp(); + + wp_set_current_user( self::$user_id ); + + add_filter( 'edd_api_output_format', function() { + return 'override'; + } ); + + // Prevents edd_die() from running. + add_action( 'edd_api_output_override', function () { + // Prevent edd_die() from stopping tests. + if ( ! defined( 'EDD_UNIT_TESTS' ) ) { + define( 'EDD_UNIT_TESTS', true ); + } + }, 10 ); + + self::$api->flush_api_output(); + } + + public function tearDown(): void { + parent::tearDown(); + + // Revoke key to ensure `update_key()` will generate a new one. + EDD()->api->revoke_api_key( self::$user_id ); + + self::$api->flush_api_output(); + } + + public function test_endpoints() { + global $wp_rewrite; + + $this->assertEquals( 'edd-api', $wp_rewrite->endpoints[0][1] ); + } + + public function test_query_vars() { + global $wp_filter; + + foreach ( $wp_filter['query_vars'][10] as $arr ) : + + if ( 'query_vars' == $arr['function'][1] ) { + $this->assertTrue( true ); + } + + endforeach; + + $out = self::$api->query_vars( array() ); + $this->assertEquals( 'token', $out[0] ); + $this->assertEquals( 'key', $out[1] ); + $this->assertEquals( 'query', $out[2] ); + $this->assertEquals( 'type', $out[3] ); + $this->assertEquals( 'product', $out[4] ); + $this->assertEquals( 'category', $out[5] ); + $this->assertEquals( 'tag', $out[6] ); + $this->assertEquals( 'term_relation', $out[7] ); + $this->assertEquals( 'number', $out[8] ); + $this->assertEquals( 'date', $out[9] ); + $this->assertEquals( 'startdate', $out[10] ); + $this->assertEquals( 'enddate', $out[11] ); + $this->assertEquals( 'customer', $out[12] ); + $this->assertEquals( 'discount', $out[13] ); + $this->assertEquals( 'format', $out[14] ); + } + + public function test_get_versions() { + $this->assertIsArray( self::$api->get_versions() ); + $this->assertArrayHasKey( 'v1', self::$api->get_versions() ); + } + + public function test_get_default_version() { + + $this->assertEquals( 'v2', self::$api->get_default_version() ); + + define( 'EDD_API_VERSION', 'v1' ); + $this->assertEquals( 'v1', self::$api->get_default_version() ); + + } + + public function test_get_queried_version() { + + global $wp_query; + + $_POST['edd_set_api_key'] = 1; + EDD()->api->update_key( self::$user_id ); + + $wp_query->query_vars['key'] = get_user_meta( self::$user_id, 'edd_user_public_key', true ); + $wp_query->query_vars['token'] = hash( 'md5', get_user_meta( self::$user_id, 'edd_user_secret_key', true ) . get_user_meta( self::$user_id, 'edd_user_public_key', true ) ); + + $wp_query->query_vars['edd-api'] = 'v1/sales'; + + try { + self::$api->process_query(); + } catch ( \WPDieException $e ) {} + $this->assertEquals( 'v1', self::$api->get_queried_version() ); + + try { + $wp_query->query_vars['edd-api'] = 'v2/sales'; + self::$api->process_query(); + } catch ( \WPDieException $e ) {} + $this->assertEquals( 'v2', self::$api->get_queried_version() ); + } + + public function test_get_products() { + $out = self::$api_output; + $this->assertArrayHasKey( 'id', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'slug', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'title', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'create_date', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'modified_date', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'status', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'link', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'permalink', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'content', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'thumbnail', $out['products'][0]['info'] ); + + $this->assertEquals( self::$post->ID, $out['products'][0]['info']['id'] ); + $this->assertEquals( 'test-download', $out['products'][0]['info']['slug'] ); + $this->assertEquals( 'Test Download', $out['products'][0]['info']['title'] ); + $this->assertEquals( 'publish', $out['products'][0]['info']['status'] ); + $this->assertEquals( self::$post->post_content, $out['products'][0]['info']['content'] ); + $this->assertEquals( '', $out['products'][0]['info']['thumbnail'] ); + $this->assertEquals( html_entity_decode( self::$post->guid ), $out['products'][0]['info']['link'] ); + $this->assertEquals( html_entity_decode( get_permalink( self::$post->ID ) ), $out['products'][0]['info']['permalink'] ); + } + + public function test_get_product_stats() { + $out = self::$api_output; + + $this->assertArrayHasKey( 'stats', $out['products'][0] ); + $this->assertArrayHasKey( 'total', $out['products'][0]['stats'] ); + $this->assertArrayHasKey( 'sales', $out['products'][0]['stats']['total'] ); + $this->assertArrayHasKey( 'earnings', $out['products'][0]['stats']['total'] ); + $this->assertArrayHasKey( 'monthly_average', $out['products'][0]['stats'] ); + $this->assertArrayHasKey( 'sales', $out['products'][0]['stats']['monthly_average'] ); + $this->assertArrayHasKey( 'earnings', $out['products'][0]['stats']['monthly_average'] ); + + $this->assertEquals( '1', $out['products'][0]['stats']['total']['sales'] ); + $this->assertEquals( 100.00, (float) $out['products'][0]['stats']['total']['earnings'] ); + $this->assertEquals( '1', $out['products'][0]['stats']['monthly_average']['sales'] ); + $this->assertEquals( 100.00, (float) $out['products'][0]['stats']['monthly_average']['earnings'] ); + } + + public function test_get_products_pricing() { + $out = self::$api_output; + $this->assertArrayHasKey( 'pricing', $out['products'][0] ); + $this->assertArrayHasKey( 'simple', $out['products'][0]['pricing'] ); + $this->assertArrayHasKey( 'advanced', $out['products'][0]['pricing'] ); + + $this->assertEquals( '20.00', $out['products'][0]['pricing']['simple'] ); + $this->assertEquals( '100.00', $out['products'][0]['pricing']['advanced'] ); + } + + public function test_get_products_files() { + $out = self::$api_output; + $this->assertArrayHasKey( 'files', $out['products'][0] ); + + foreach ( $out['products'][0]['files'] as $file ) { + $this->assertArrayHasKey( 'name', $file ); + $this->assertArrayHasKey( 'file', $file ); + $this->assertArrayHasKey( 'condition', $file ); + } + + $this->assertEquals( 'File 1', $out['products'][0]['files'][0]['name'] ); + $this->assertEquals( 'http://localhost/file1.jpg', $out['products'][0]['files'][0]['file'] ); + $this->assertEquals( 0, $out['products'][0]['files'][0]['condition'] ); + $this->assertEquals( 'File 2', $out['products'][0]['files'][1]['name'] ); + $this->assertEquals( 'http://localhost/file2.jpg', $out['products'][0]['files'][1]['file'] ); + $this->assertEquals( 'all', $out['products'][0]['files'][1]['condition'] ); + } + + + public function test_get_products_notes() { + $out = self::$api_output; + $this->assertArrayHasKey( 'notes', $out['products'][0] ); + $this->assertEquals( 'Purchase Notes', $out['products'][0]['notes'] ); + } + + public function test_get_recent_sales() { + $out = self::$api_output_sales; + $this->assertArrayHasKey( 'sales', $out ); + $this->assertArrayHasKey( 'ID', $out['sales'][0] ); + $this->assertArrayHasKey( 'key', $out['sales'][0] ); + $this->assertArrayHasKey( 'subtotal', $out['sales'][0] ); + $this->assertArrayHasKey( 'tax', $out['sales'][0] ); + $this->assertArrayHasKey( 'fees', $out['sales'][0] ); + $this->assertArrayHasKey( 'total', $out['sales'][0] ); + $this->assertArrayHasKey( 'gateway', $out['sales'][0] ); + $this->assertArrayHasKey( 'email', $out['sales'][0] ); + $this->assertArrayHasKey( 'date', $out['sales'][0] ); + $this->assertArrayHasKey( 'products', $out['sales'][0] ); + $this->assertArrayHasKey( 'name', $out['sales'][0]['products'][0] ); + $this->assertArrayHasKey( 'price', $out['sales'][0]['products'][0] ); + $this->assertArrayHasKey( 'price_name', $out['sales'][0]['products'][0] ); + + $this->assertEquals( 100.00, $out['sales'][0]['subtotal'] ); + $this->assertEquals( 0, $out['sales'][0]['tax'] ); + $this->assertEquals( 100.00, $out['sales'][0]['total'] ); + $this->assertEquals( '', $out['sales'][0]['gateway'] ); + $this->assertEquals( 'admin@example.org', $out['sales'][0]['email'] ); + $this->assertEquals( 'Test Download', $out['sales'][0]['products'][0]['name'] ); + $this->assertEquals( 100, $out['sales'][0]['products'][0]['price'] ); + $this->assertEquals( 'Advanced', $out['sales'][0]['products'][0]['price_name'] ); + } + + public function test_get_recent_sales_invalid_payment_id() { + global $wp_query; + $wp_query->query_vars['id'] = 0; + $recent_sales = self::$api->get_recent_sales(); + + $this->assertEquals( 0, $recent_sales['sales'][0]['ID'] ); + } + + public function test_update_key() { + + $_POST['edd_set_api_key'] = 1; + + EDD()->api->update_key( self::$user_id ); + + $user_public = self::$api->get_user_public_key( self::$user_id ); + $user_secret = self::$api->get_user_secret_key( self::$user_id ); + + $this->assertNotEmpty( $user_public ); + $this->assertNotEmpty( $user_secret ); + + // Backwards compatibilty check for API Keys + $this->assertEquals( $user_public, get_user_meta( self::$user_id, 'edd_user_public_key', true ) ); + $this->assertEquals( $user_secret, get_user_meta( self::$user_id, 'edd_user_secret_key', true ) ); + + } + + public function test_get_user() { + $_POST['edd_set_api_key'] = 1; + + EDD()->api->update_key( self::$user_id ); + $this->assertEquals( self::$user_id, self::$api->get_user( self::$api->get_user_public_key( self::$user_id ) ) ); + + } + + public function test_get_customers() { + try { + $out = EDD()->api->get_customers(); + + $this->assertArrayHasKey( 'customers', $out ); + $this->assertArrayHasKey( 'info', $out['customers'][0] ); + $this->assertArrayHasKey( 'id', $out['customers'][0]['info'] ); + $this->assertArrayHasKey( 'username', $out['customers'][0]['info'] ); + $this->assertArrayHasKey( 'display_name', $out['customers'][0]['info'] ); + $this->assertArrayHasKey( 'first_name', $out['customers'][0]['info'] ); + $this->assertArrayHasKey( 'last_name', $out['customers'][0]['info'] ); + $this->assertArrayHasKey( 'email', $out['customers'][0]['info'] ); + $this->assertArrayHasKey( 'stats', $out['customers'][0] ); + $this->assertArrayHasKey( 'total_purchases', $out['customers'][0]['stats'] ); + $this->assertArrayHasKey( 'total_spent', $out['customers'][0]['stats'] ); + $this->assertArrayHasKey( 'total_downloads', $out['customers'][0]['stats'] ); + + $this->assertEquals( 1, $out['customers'][0]['info']['id'] ); + $this->assertEquals( 'admin', $out['customers'][0]['info']['username'] ); + $this->assertEquals( 'admin@example.org', $out['customers'][0]['info']['email'] ); + $this->assertEquals( 1, $out['customers'][0]['stats']['total_purchases'] ); + $this->assertEquals( 100.0, $out['customers'][0]['stats']['total_spent'] ); + $this->assertEquals( 0, $out['customers'][0]['stats']['total_downloads'] ); + } catch ( \WPDieException $e ) {} + } + + public function test_missing_auth() { + global $wp_query; + + $wp_query->query_vars['key'] = ''; + $wp_query->query_vars['token'] = ''; + $wp_query->query_vars['edd-api'] = 'sales'; + + try { + self::$api->process_query(); + } catch ( \WPDieException $e ) {} + + $out = self::$api->get_output(); + + $this->assertArrayHasKey( 'error', $out ); + $this->assertEquals( 'You must specify both a token and API key!', $out['error'] ); + } + + public function test_invalid_auth() { + + global $wp_query; + + $_POST['edd_set_api_key'] = 1; + EDD()->api->update_key( self::$user_id ); + + $wp_query->query_vars['key'] = self::$api->get_user_public_key( self::$user_id ); + $wp_query->query_vars['token'] = 'bad-token-val'; + $wp_query->query_vars['edd-api'] = 'sales'; + + try { + self::$api->process_query(); + } catch ( \WPDieException $e ) {} + + $out = self::$api->get_output(); + + $this->assertArrayHasKey( 'error', $out ); + $this->assertEquals( 'Your request could not be authenticated!', $out['error'] ); + } + + public function test_invalid_key() { + global $wp_query; + + $_POST['edd_set_api_key'] = 1; + EDD()->api->update_key( self::$user_id ); + $wp_query->query_vars['key'] = 'bad-key-val'; + $wp_query->query_vars['token'] = hash( 'md5', get_user_meta( self::$user_id, 'edd_user_secret_key', true ) . get_user_meta( self::$user_id, 'edd_user_public_key', true ) ); + + $wp_query->query_vars['edd-api'] = 'sales'; + + try { + self::$api->process_query(); + } catch ( \WPDieException $e ) {} + + $out = self::$api->get_output(); + + $this->assertArrayHasKey( 'error', $out ); + $this->assertEquals( 'Invalid API key!', $out['error'] ); + } + + public function test_info() { + $out = EDD()->api->get_info(); + + $this->assertArrayHasKey( 'info', $out ); + $this->assertArrayHasKey( 'site', $out['info'] ); + $this->assertArrayHasKey( 'currency', $out['info']['site'] ); + $this->assertArrayHasKey( 'currency_position', $out['info']['site'] ); + $this->assertArrayHasKey( 'decimal_separator', $out['info']['site'] ); + $this->assertArrayHasKey( 'thousands_separator', $out['info']['site'] ); + $this->assertArrayNotHasKey( 'integrations', $out['info'] ); // By default we shouldn't have any integrations + + $this->assertArrayHasKey( 'permissions', $out['info'] ); + $this->assertTrue( $out['info']['permissions']['view_shop_reports'] ); + $this->assertTrue( $out['info']['permissions']['view_shop_sensitive_data'] ); + $this->assertTrue( $out['info']['permissions']['manage_shop_discounts'] ); + + } + + public function test_process_query() { + global $wp_query; + + $_POST['edd_set_api_key'] = 1; + self::$api->update_key( self::$user_id ); + + $wp_query->query_vars['edd-api'] = 'products'; + $wp_query->query_vars['key'] = get_user_meta( self::$user_id, 'edd_user_public_key', true ); + $wp_query->query_vars['token'] = hash( 'md5', get_user_meta( self::$user_id, 'edd_user_secret_key', true ) . get_user_meta( self::$user_id, 'edd_user_public_key', true ) ); + + try { + self::$api->process_query(); + } catch ( \WPDieException $e ) {} + + $out = self::$api->get_output(); + + $this->assertArrayHasKey( 'info', $out['products'][0] ); + $this->assertArrayHasKey( 'id', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'slug', $out['products'][0]['info'] ); + $this->assertEquals( 'test-download', $out['products'][0]['info']['slug'] ); + $this->assertArrayHasKey( 'title', $out['products'][0]['info'] ); + $this->assertEquals( 'Test Download', $out['products'][0]['info']['title'] ); + $this->assertArrayHasKey( 'create_date', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'modified_date', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'status', $out['products'][0]['info'] ); + $this->assertEquals( 'publish', $out['products'][0]['info']['status'] ); + $this->assertArrayHasKey( 'link', $out['products'][0]['info'] ); + $this->assertArrayHasKey( 'content', $out['products'][0]['info'] ); + $this->assertEquals( self::$post->post_content, $out['products'][0]['info']['content'] ); + $this->assertArrayHasKey( 'thumbnail', $out['products'][0]['info'] ); + + $this->assertArrayHasKey( 'stats', $out['products'][0] ); + $this->assertArrayHasKey( 'total', $out['products'][0]['stats'] ); + $this->assertArrayHasKey( 'sales', $out['products'][0]['stats']['total'] ); + $this->assertEquals( 1, $out['products'][0]['stats']['total']['sales'] ); + $this->assertArrayHasKey( 'earnings', $out['products'][0]['stats']['total'] ); + $this->assertEquals( 100.00, $out['products'][0]['stats']['total']['earnings'] ); + $this->assertArrayHasKey( 'monthly_average', $out['products'][0]['stats'] ); + $this->assertArrayHasKey( 'sales', $out['products'][0]['stats']['monthly_average'] ); + $this->assertEquals( 1, $out['products'][0]['stats']['monthly_average']['sales'] ); + $this->assertArrayHasKey( 'earnings', $out['products'][0]['stats']['monthly_average'] ); + $this->assertEquals( 100.00, $out['products'][0]['stats']['monthly_average']['earnings'] ); + + $this->assertArrayHasKey( 'pricing', $out['products'][0] ); + $this->assertArrayHasKey( 'simple', $out['products'][0]['pricing'] ); + $this->assertEquals( 20, $out['products'][0]['pricing']['simple'] ); + $this->assertArrayHasKey( 'advanced', $out['products'][0]['pricing'] ); + $this->assertEquals( 100, $out['products'][0]['pricing']['advanced'] ); + + $this->assertArrayHasKey( 'files', $out['products'][0] ); + $this->assertArrayHasKey( 'name', $out['products'][0]['files'][0] ); + $this->assertArrayHasKey( 'file', $out['products'][0]['files'][0] ); + $this->assertArrayHasKey( 'condition', $out['products'][0]['files'][0] ); + $this->assertArrayHasKey( 'name', $out['products'][0]['files'][1] ); + $this->assertArrayHasKey( 'file', $out['products'][0]['files'][1] ); + $this->assertArrayHasKey( 'condition', $out['products'][0]['files'][1] ); + $this->assertEquals( 'File 1', $out['products'][0]['files'][0]['name'] ); + $this->assertEquals( 'http://localhost/file1.jpg', $out['products'][0]['files'][0]['file'] ); + $this->assertEquals( 0, $out['products'][0]['files'][0]['condition'] ); + $this->assertEquals( 'File 2', $out['products'][0]['files'][1]['name'] ); + $this->assertEquals( 'http://localhost/file2.jpg', $out['products'][0]['files'][1]['file'] ); + $this->assertEquals( 'all', $out['products'][0]['files'][1]['condition'] ); + + $this->assertArrayHasKey( 'notes', $out['products'][0] ); + $this->assertEquals( 'Purchase Notes', $out['products'][0]['notes'] ); + } + + /** + * Ensures the correct discount amount is included in the recent sales endpoint. + * + * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/8246 + * + * @covers EDD_API_V2::get_recent_sales + * @covers EDD_Cart::get_item_discount_amount + */ + public function test_recent_sales_contains_correct_discount_amount() { + // Create a 20% off discount code with code `20OFF`. + Helpers\EDD_Helper_Discount::create_simple_percent_discount(); + + // Update the payment information. + $payment = edd_get_payment( self::$payment_id ); + $payment->discounted_amount = 20; + $payment->total = 80; + $payment->discounts = '20OFF'; + $payment->save(); + + $api_v2 = new \EDD_API_V2(); + $sales_output = $api_v2->get_recent_sales(); + + $this->assertEquals( 20, $sales_output['sales'][0]['discounts']['20OFF'] ); + } + +} diff --git a/tests/tests-blocks.php b/tests/tests-blocks.php new file mode 100644 index 00000000000..c993757b428 --- /dev/null +++ b/tests/tests-blocks.php @@ -0,0 +1,76 @@ +does_blocks_file_exist() ) { + $this->markTestSkipped( 'This only runs if blocks are loaded.' ); + } + + $expected = $this->wp_version_supports_edd_blocks(); + + $this->assertEquals( $expected, edd_has_core_blocks() ); + } + + public function test_block_categories_includes_edd() { + if ( ! class_exists( 'WP_Block_Editor_Context' ) ) { + $this->markTestSkipped( 'The Block Editor Context is required.' ); + } + if ( ! $this->does_blocks_file_exist() ) { + $this->markTestSkipped( 'This only runs if blocks are loaded.' ); + } + + $block_editor_context = new \WP_Block_Editor_Context( array( 'name' => 'core/edit-post' ) ); + $block_categories = get_block_categories( $block_editor_context ); + $categories = wp_list_pluck( $block_categories, 'slug' ); + $expected = $this->wp_version_supports_edd_blocks(); + + $this->assertEquals( $expected, in_array( 'easy-digital-downloads', $categories, true ) ); + } + + public function test_button_colors_update_button_class() { + if ( ! $this->does_blocks_file_exist() ) { + $this->markTestSkipped( 'This only runs if blocks are loaded.' ); + } + if ( ! $this->wp_version_supports_edd_blocks() ) { + $this->markTestSkipped( 'This only runs if the current WordPress version supports blocks.' ); + } + $button_colors = edd_update_option( 'button_colors', array( 'background' => '#333', 'text' => '#fff' ) ); + + $this->assertStringContainsString( 'has-edd-button-background-color', edd_get_button_color_class() ); + + edd_delete_option( 'button_colors' ); + } + + /** + * Checks whether the blocks files exist to be tested. + * + * @return bool + */ + private function does_blocks_file_exist() { + return file_exists( EDD_PLUGIN_DIR . 'includes/blocks/edd-blocks.php' ); + } + + /** + * Checks whether the current WordPress version supports EDD blocks in core. + * + * @return bool + */ + private function wp_version_supports_edd_blocks() { + return version_compare( get_bloginfo( 'version' ), $this->minimum_wp_version, '>=' ); + } +} diff --git a/tests/tests-cart.php b/tests/tests-cart.php new file mode 100644 index 00000000000..844db58da42 --- /dev/null +++ b/tests/tests-cart.php @@ -0,0 +1,759 @@ +user->create( array( 'role' => 'administrator' ) ) ); + + $GLOBALS['wp_rewrite']->init(); + + flush_rewrite_rules( false ); + + edd_add_rewrite_endpoints( $wp_rewrite ); + + $current_user = new \WP_User( 1 ); + $current_user->set_role( 'administrator' ); + + $post_id = static::factory()->post->create( array( + 'post_title' => 'Test Download', + 'post_type' => 'download', + 'post_status' => 'publish', + ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 20, + ), + array( + 'name' => 'Advanced', + 'amount' => 100, + ), + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0, + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all', + ), + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 129.43, + '_edd_download_sales' => 59, + '_edd_download_limit_override_1' => 1, + ); + + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + self::$download = edd_get_download( $post_id ); + + // Create a second Download. + $post_id = static::factory()->post->create( array( + 'post_title' => 'Test Download 2', + 'post_type' => 'download', + 'post_status' => 'publish', + ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 25, + ), + array( + 'name' => 'Advanced', + 'amount' => 115, + ), + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0, + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all', + ), + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 129.43, + '_edd_download_sales' => 59, + '_edd_download_limit_override_1' => 1, + ); + + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + self::$download_2 = edd_get_download( $post_id ); + + self::$discount = static::edd()->discount->create_and_get( array( + 'name' => '20 Percent Off', + 'code' => '20OFF', + 'status' => 'active', + 'type' => 'percent', + 'amount' => '20', + 'use_count' => 54, + 'max_uses' => 10, + 'min_charge_amount' => 128, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59', + ) ); + + self::$discount = static::edd()->discount->create_and_get( array( + 'name' => '8 Flat', + 'code' => '8FLAT', + 'status' => 'active', + 'type' => 'flat', + 'amount' => '8.73', + 'use_count' => 12, + 'max_uses' => 100, + 'min_charge_amount' => 0, + 'product_condition' => 'all', + 'start_date' => '2010-12-12 00:00:00', + 'end_date' => '2050-12-31 23:59:59', + ) ); + } + + public function setup(): void { + global $current_user; + + parent::setUp(); + + $current_user = new \WP_User( 1 ); + $current_user->set_role( 'administrator' ); + } + + public function tearDown(): void { + parent::tearDown(); + + edd_empty_cart(); + } + + public function test_endpoints() { + global $wp_rewrite; + + $this->assertEquals( 'edd-add', $wp_rewrite->endpoints[0][1] ); + $this->assertEquals( 'edd-remove', $wp_rewrite->endpoints[1][1] ); + } + + public function test_add_to_cart() { + $options = array( + 'price_id' => 0, + ); + + $this->assertEquals( 0, edd_add_to_cart( self::$download->ID, $options ) ); + } + + public function test_empty_cart_is_array() { + $cart_contents = edd_get_cart_contents(); + + $this->assertIsArray( $cart_contents ); + $this->assertEmpty( $cart_contents ); + } + + public function test_add_to_cart_multiple_price_ids_array() { + edd_add_to_cart( self::$download->ID, array( + 'price_id' => array( 0, 1 ), + ) ); + + $this->assertEquals( 2, count( edd_get_cart_contents() ) ); + } + + public function test_add_to_cart_multiple_price_ids_array_with_quantity() { + add_filter( 'edd_item_quantities_enabled', '__return_true' ); + $options = array( + 'price_id' => array( 0, 1 ), + 'quantity' => array( 2, 3 ), + ); + + edd_add_to_cart( self::$download->ID, $options ); + + $this->assertEquals( 2, count( edd_get_cart_contents() ) ); + $this->assertEquals( 2, edd_get_cart_item_quantity( self::$download->ID, array( 'price_id' => 0 ) ) ); + $this->assertEquals( 3, edd_get_cart_item_quantity( self::$download->ID, array( 'price_id' => 1 ) ) ); + + remove_filter( 'edd_item_quantities_enabled', '__return_true' ); + } + + public function test_add_to_cart_multiple_price_ids_string() { + edd_add_to_cart( self::$download->ID, array( + 'price_id' => '0,1', + ) ); + + $this->assertEquals( 2, count( edd_get_cart_contents() ) ); + } + + public function test_get_cart_contents() { + edd_add_to_cart( self::$download->ID, array( + 'price_id' => 0, + ) ); + + $expected = array( + '0' => array( + 'id' => self::$download->ID, + 'options' => array( + 'price_id' => 0, + ), + 'quantity' => 1, + ), + ); + + $this->assertEquals( $expected, edd_get_cart_contents() ); + } + + public function test_get_cart_content_details() { + edd_add_to_cart( self::$download->ID, array( + 'price_id' => 0, + ) ); + + $expected = array( + '0' => array( + 'name' => 'Test Download', + 'id' => self::$download->ID, + 'item_number' => array( + 'options' => array( + 'price_id' => '0', + ), + 'id' => self::$download->ID, + 'quantity' => 1, + ), + 'item_price' => 20.0, + 'quantity' => 1, + 'discount' => 0.0, + 'subtotal' => 20.0, + 'tax' => 0.0, + 'fees' => array(), + 'price' => 20.0, + ), + ); + + $this->assertEquals( $expected, edd_get_cart_content_details() ); + + // Now set a discount and test again + edd_set_cart_discount( '20OFF' ); + + $expected = array( + '0' => array( + 'name' => 'Test Download', + 'id' => self::$download->ID, + 'item_number' => array( + 'options' => array( + 'price_id' => '0', + ), + 'id' => self::$download->ID, + 'quantity' => 1, + ), + 'item_price' => 20.0, + 'quantity' => 1, + 'discount' => 4.0, + 'subtotal' => 20.0, + 'tax' => 0.0, + 'fees' => array(), + 'price' => 16.0, + ), + ); + + $this->assertEquals( $expected, edd_get_cart_content_details() ); + + // Now turn on taxes and do it again + add_filter( 'edd_use_taxes', '__return_true' ); + EDD()->cart->set_tax_rate( null ); // Clears the tax rate cache. + add_filter( 'edd_tax_rate', function () { + return 0.20; + } ); + + $expected = array( + '0' => array( + 'name' => 'Test Download', + 'id' => self::$download->ID, + 'item_number' => array( + 'options' => array( + 'price_id' => '0', + ), + 'id' => self::$download->ID, + 'quantity' => 1, + ), + 'item_price' => 20.0, + 'quantity' => 1, + 'discount' => 4.0, + 'subtotal' => 20.0, + 'tax' => 3.2, + 'fees' => array(), + 'price' => 19.2, + ), + ); + + $this->assertEquals( $expected, edd_get_cart_content_details() ); + + // Now remove the discount code and test with taxes again + edd_unset_cart_discount( '20OFF' ); + + $expected = array( + '0' => array( + 'name' => 'Test Download', + 'id' => self::$download->ID, + 'item_number' => array( + 'options' => array( + 'price_id' => '0', + ), + 'id' => self::$download->ID, + 'quantity' => 1, + ), + 'item_price' => 20.0, + 'quantity' => 1, + 'discount' => 0.0, + 'subtotal' => 20.0, + 'tax' => 4.0, + 'fees' => array(), + 'price' => 24.0, + ), + ); + + $this->assertEquals( $expected, edd_get_cart_content_details() ); + } + + public function test_get_cart_item_discounted_amount() { + + // Call without any arguments + $expected = edd_get_cart_item_discount_amount(); + $this->assertEquals( 0.00, $expected ); + + // Call with an array but missing 'id' + $expected = edd_get_cart_item_discount_amount( array( 'foo' => 'bar' ) ); + $this->assertEquals( 0.00, $expected ); + + $options = array( + 'price_id' => 0, + ); + + edd_add_to_cart( self::$download->ID, $options ); + + // Now set a discount and test again + edd_set_cart_discount( '20OFF' ); + + // Test it without a quantity + $cart_item_args = array( 'id' => self::$download->ID ); + $this->assertEquals( 0.00, edd_get_cart_item_discount_amount( $cart_item_args ) ); + + // Test it without an options array on an item with variable pricing to make sure we get 0 + $cart_item_args = array( 'id' => self::$download->ID, 'quantity' => 1 ); + $this->assertEquals( 0.00, edd_get_cart_item_discount_amount( $cart_item_args ) ); + + // Now test it with an options array properly set + $cart_item_args['options'] = $options; + $this->assertEquals( 4, edd_get_cart_item_discount_amount( $cart_item_args ) ); + + edd_unset_cart_discount( '20OFF' ); + + // Test Flat rate discounts split across multiple items. + edd_set_cart_discount( '8FLAT' ); + + edd_add_to_cart( self::$download_2->ID, $options ); + + $this->assertEquals( 3.88, edd_get_cart_item_discount_amount( array( + 'id' => self::$download->ID, + 'quantity' => 1, + 'options' => array( + 'price_id' => 0, + ), + ) ) ); + + $this->assertEquals( 4.85, edd_get_cart_item_discount_amount( array( + 'id' => self::$download_2->ID, + 'quantity' => 1, + 'options' => array( + 'price_id' => 0, + ), + ) ) ); + + edd_unset_cart_discount( '8FLAT' ); + } + + public function test_cart_quantity() { + $options = array( + 'price_id' => 0, + ); + edd_add_to_cart( self::$download->ID, $options ); + + $this->assertEquals( 1, edd_get_cart_quantity() ); + } + + public function test_get_cart_item_quantity() { + edd_empty_cart(); + + $options = array( + 'price_id' => 0, + ); + edd_add_to_cart( self::$download->ID, $options ); + + $this->assertEquals( 1, edd_get_cart_item_quantity( self::$download->ID, $options ) ); + + edd_update_option( 'item_quantities', true ); + // Add the item to the cart again + edd_add_to_cart( self::$download->ID, $options ); + + $this->assertEquals( 2, edd_get_cart_item_quantity( self::$download->ID, $options ) ); + edd_delete_option( 'item_quantities' ); + + // Now add a different price option to the cart + $options = array( + 'price_id' => 1, + ); + edd_add_to_cart( self::$download->ID, $options ); + + $this->assertEquals( 1, edd_get_cart_item_quantity( self::$download->ID, $options ) ); + } + + public function test_add_to_cart_with_quantities_enabled_on_product() { + + add_filter( 'edd_item_quantities_enabled', '__return_true' ); + + $options = array( + 'price_id' => 0, + 'quantity' => 2, + ); + edd_add_to_cart( self::$download->ID, $options ); + + $this->assertEquals( 2, edd_get_cart_item_quantity( self::$download->ID, $options ) ); + } + + public function test_add_to_cart_with_quantities_disabled_on_product() { + add_filter( 'edd_item_quantities_enabled', '__return_true' ); + + update_post_meta( self::$download->ID, '_edd_quantities_disabled', 1 ); + + $options = array( + 'price_id' => 0, + 'quantity' => 2, + ); + edd_add_to_cart( self::$download->ID, $options ); + + $this->assertEquals( 1, edd_get_cart_item_quantity( self::$download->ID, $options ) ); + } + + public function test_set_cart_item_quantity() { + edd_update_option( 'item_quantities', true ); + + $options = array( + 'price_id' => 0, + ); + + edd_add_to_cart( self::$download->ID, $options ); + edd_set_cart_item_quantity( self::$download->ID, 3, $options ); + + $this->assertEquals( 3, edd_get_cart_item_quantity( self::$download->ID, $options ) ); + + edd_delete_option( 'item_quantities' ); + } + + public function test_item_in_cart() { + $this->assertFalse( edd_item_in_cart( self::$download->ID ) ); + } + + public function test_cart_item_price() { + $this->assertEquals( '$0.00', edd_cart_item_price( 0 ) ); + } + + public function test_get_cart_item_price() { + $this->assertEquals( false, edd_get_cart_item_price( 0 ) ); + } + + public function test_remove_from_cart() { + + edd_empty_cart(); + + edd_add_to_cart( self::$download->ID ); + + $expected = array(); + $this->assertEquals( $expected, edd_remove_from_cart( 0 ) ); + } + + public function test_set_purchase_session() { + $this->assertNull( edd_set_purchase_session() ); + } + + public function test_get_purchase_session() { + $this->assertEmpty( edd_get_purchase_session() ); + } + + public function test_cart_saving_disabled() { + $this->assertTrue( edd_is_cart_saving_disabled() ); + } + + public function test_is_cart_saved_false() { + + // Test for no saved cart + $this->assertFalse( edd_is_cart_saved() ); + + // Create a saved cart then test again + $cart = array( + '0' => array( + 'id' => self::$download->ID, + 'options' => array( + 'price_id' => 0, + ), + 'quantity' => 1, + ), + ); + update_user_meta( get_current_user_id(), 'edd_saved_cart', $cart ); + + edd_update_option( 'enable_cart_saving', '1' ); + + $this->assertTrue( edd_is_cart_saved() ); + } + + public function test_restore_cart() { + + // Create a saved cart + $saved_cart = array( + '0' => array( + 'id' => self::$download->ID, + 'options' => array( + 'price_id' => 0, + ), + 'quantity' => 1, + ), + ); + update_user_meta( get_current_user_id(), 'edd_saved_cart', $saved_cart ); + + // Set the current cart contents (different from saved) + $cart = array( + '0' => array( + 'id' => self::$download->ID, + 'options' => array( + 'price_id' => 1, + ), + 'quantity' => 1, + ), + ); + EDD()->session->set( 'edd_cart', $cart ); + EDD()->cart->contents = $cart; + + edd_update_option( 'enable_cart_saving', '1' ); + $this->assertTrue( edd_restore_cart() ); + $this->assertEquals( edd_get_cart_contents(), $saved_cart ); + } + + public function test_generate_cart_token() { + $this->assertIsString( edd_generate_cart_token() ); + $this->assertTrue( 32 === strlen( edd_generate_cart_token() ) ); + } + + public function test_edd_get_cart_item_name() { + edd_add_to_cart( self::$download->ID ); + + $items = edd_get_cart_content_details(); + + $this->assertEquals( 'Test Download — Simple', edd_get_cart_item_name( $items[0] ) ); + } + + public function test_cart_total_with_global_fee() { + edd_empty_cart(); + + edd_add_to_cart( self::$download->ID, array( 'price_id' => 0 ) ); + + EDD()->fees->add_fee( 10, 'test', 'Test' ); + + $this->assertEquals( 30, EDD()->cart->get_total() ); + } + + public function test_cart_fess_total_with_global_fee() { + edd_empty_cart(); + + edd_add_to_cart( self::$download->ID ); + + EDD()->fees->add_fee( 10, 'test', 'Test' ); + + $this->assertEquals( 10, edd_get_cart_fee_total() ); + } + + public function test_cart_total_with_download_fee() { + edd_empty_cart(); + + edd_add_to_cart( self::$download->ID, array( 'price_id' => 0 ) ); + + EDD()->fees->add_fee( array( + 'amount' => 10, + 'id' => 'test', + 'label' => 'Test', + 'download_id' => self::$download->ID, + ) ); + + $this->assertEquals( 30, edd_get_cart_total() ); + } + + public function test_cart_fee_total_with_download_fee() { + edd_empty_cart(); + + edd_add_to_cart( self::$download->ID, array( 'price_id' => 0 ) ); + + EDD()->fees->add_fee( array( + 'amount' => 10, + 'id' => 'test', + 'label' => 'Test', + 'download_id' => self::$download->ID, + ) ); + + // Since it's a fee associated with an item in the cart, it affects it's pricing, not the total cart fees. + $this->assertEquals( 0, edd_get_cart_fee_total() ); + } + + public function test_cart_total_with_global_item_fee() { + edd_empty_cart(); + + edd_add_to_cart( self::$download->ID, array( 'price_id' => 0 ) ); + + EDD()->fees->add_fee( array( + 'amount' => 10, + 'id' => 'test', + 'label' => 'Test', + 'type' => 'item', + ) ); + + $this->assertEquals( 30, edd_get_cart_total() ); + } + + public function test_cart_fee_total_with_global_item_fee() { + edd_empty_cart(); + + edd_add_to_cart( self::$download->ID ); + + EDD()->fees->add_fee( array( + 'amount' => 10, + 'id' => 'test', + 'label' => 'Test', + 'type' => 'item', + ) ); + + $this->assertEquals( 10, edd_get_cart_fee_total() ); + } + + public function test_unset_cart_discount_case_insensitive() { + edd_set_cart_discount( '20off' ); + $this->assertEmpty( edd_unset_cart_discount( '20OFF' ) ); + } + + public function test_negative_fees_cart_tax() { + edd_update_option( 'enable_taxes', true ); + EDD()->cart->set_tax_rate( null ); // Clears the tax rate cache. + add_filter( 'edd_tax_rate', function () { + return 0.10; + } ); + + $options = array( + 'price_id' => 0, + ); + edd_add_to_cart( self::$download->ID, $options ); + + $fee = array( + 'amount' => -10, + 'label' => 'Sale - ' . get_the_title( self::$download->ID ), + 'id' => 'dp_0', + 'download_id' => self::$download->ID, + 'price_id' => 0, + ); + EDD()->fees->add_fee( $fee ); + + $this->assertEquals( 1.00, EDD()->cart->get_tax() ); + + edd_update_option( 'enable_taxes', false ); + } + + public function test_cart_is_empty() { + edd_empty_cart(); + $this->assertTrue( edd_is_cart_empty() ); + } + + public function test_cart_is_not_empty() { + edd_add_to_cart( self::$download->ID, array( 'price_id' => 0 ) ); + $this->assertFalse( edd_is_cart_empty() ); + } + + public function test_edd_process_add_to_cart_invalid_data_is_empty() { + edd_empty_cart(); + edd_process_add_to_cart( + array( + 'edd_options' => '', + 'edd_download_quantity' => 1, + ) + ); + $this->assertTrue( edd_is_cart_empty() ); + } + + public function test_edd_process_add_to_cart_valid_download_is_in_cart() { + edd_empty_cart(); + edd_process_add_to_cart( + array( + 'download_id' => self::$download->ID, + ) + ); + + $this->assertTrue( edd_item_in_cart( self::$download->ID ) ); + } +} diff --git a/tests/tests-checkout.php b/tests/tests-checkout.php new file mode 100755 index 00000000000..44d2c08bcb2 --- /dev/null +++ b/tests/tests-checkout.php @@ -0,0 +1,328 @@ +init(); + flush_rewrite_rules( false ); + edd_add_rewrite_endpoints( $wp_rewrite ); + + $post_id = static::factory()->post->create( array( + 'post_title' => 'Test Download', + 'post_type' => 'download', + 'post_status' => 'publish', + ) ); + + $meta = array( + 'edd_price' => '10.50', + '_edd_price_options_mode' => 'on', + '_edd_product_type' => 'default', + ); + foreach ( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + // Add our test product to the cart + $options = array( + 'name' => 'Simple', + 'amount' => '10.50', + 'quantity' => 1, + ); + + edd_add_to_cart( $post_id, $options ); + } + + /** + * Test the can checkout function + */ + public function test_can_checkout() { + $this->assertTrue( edd_can_checkout() ); + } + + /** + * Test the default 3 columns used for checkout carts. + */ + public function test_checkout_cart_columns_default() { + $this->assertSame( 3, edd_checkout_cart_columns() ); + } + + /** + * Test the default 3 columns + 1 column used for checkout carts. + */ + public function test_checkout_cart_columns_add_one() { + add_action( 'edd_checkout_table_header_first', '__return_true' ); + + $this->assertSame( 4, edd_checkout_cart_columns() ); + + remove_action( 'edd_checkout_table_header_first', '__return_true' ); + } + + /** + * Test the default 3 columns + 2 columns used for checkout carts. + */ + public function test_checkout_cart_columns_add_two() { + add_action( 'edd_checkout_table_header_first', '__return_true' ); + add_action( 'edd_checkout_table_header_first', '__return_false' ); + + $this->assertSame( 5, edd_checkout_cart_columns() ); + + remove_action( 'edd_checkout_table_header_first', '__return_true' ); + remove_action( 'edd_checkout_table_header_first', '__return_false' ); + } + + /** + * Test the filter at the bottom of + */ + public function test_checkout_cart_columns_filter() { + add_filter( 'edd_checkout_cart_columns', array( $this, 'helper_test_checkout_cart_columns_filter' ) ); + + $this->assertSame( 2, edd_checkout_cart_columns() ); + + remove_filter( 'edd_checkout_cart_columns', array( $this, 'helper_test_checkout_cart_columns_filter' ) ); + } + + /** + * Helper function for the above test, to test the filter in edd_checkout_cart_columns() + * + * @param $columns + * + * @return int + */ + public function helper_test_checkout_cart_columns_filter( $columns ) { + return 2; + } + + /** + * Test to make sure the checkout form returns the expected HTML + */ + public function test_checkout_form() { + $this->assertIsString( edd_checkout_form() ); + + $this->assertStringContainsString( '
    ', edd_checkout_form() ); + + $this->assertStringContainsString( '
    ', edd_checkout_form() ); + } + + /** + * Test to make sure the Next button is returned properly + */ + public function test_checkout_button_next() { + $this->assertIsString( edd_checkout_button_next() ); + $this->assertStringContainsString( '', edd_checkout_button_next() ); + } + + /** + * Test to make sure the purchase button is returned properly + */ + public function test_checkout_button_purchase() { + // We need activate gateways in order for this to pass. + add_filter( 'edd_enabled_payment_gateways', array( $this, 'modify_gateaways' ) ); + + $this->assertIsString( edd_checkout_button_purchase() ); + $this->assertStringContainsString( '', edd_checkout_button_purchase() ); + + remove_filter( 'edd_enabled_payment_gateways', array( $this, 'modify_gateaways' ) ); + } + + /** + * Test for retrieving banned emails + */ + public function test_edd_get_banned_emails() { + $this->assertIsArray( edd_get_banned_emails() ); + $this->assertEmpty( edd_get_banned_emails() ); + } + + /** + * Test that a specific email is banned + */ + public function test_edd_is_email_banned() { + $emails = array(); + $emails[] = 'john@test.com'; // Banned email + $emails[] = 'test2.com'; // Banned domain + $emails[] = '.zip'; // Banned TLD + + edd_update_option( 'banned_emails', $emails ); + + $this->assertTrue( edd_is_email_banned( 'john@test.com' ) ); + $this->assertTrue( edd_is_email_banned( 'john@test2.com' ) ); + $this->assertFalse( edd_is_email_banned( 'john2@test.com' ) ); + $this->assertTrue( edd_is_email_banned( 'john2@test.zip' ) ); + $this->assertFalse( edd_is_email_banned( 'john.zip@test.com' ) ); + } + + public function test_edd_is_lowercase_email_banned_with_uppcase_tld_banned() { + $emails = array(); + $emails[] = '.ZIP'; // Banned TLD + + edd_update_option( 'banned_emails', $emails ); + + $this->assertTrue( edd_is_email_banned( 'john2@test.zip' ) ); + $this->assertFalse( edd_is_email_banned( 'john.zip@test.com' ) ); + } + + public function test_edd_is_uppercase_email_banned_with_lowercase_tld_banned() { + $emails = array(); + $emails[] = '.zip'; // Banned TLD + + edd_update_option( 'banned_emails', $emails ); + + $this->assertTrue( edd_is_email_banned( 'JOHN2@test.ZIP' ) ); + $this->assertFalse( edd_is_email_banned( 'john.ZIP@test.com' ) ); + } + + /** + * Test SSL enforced checkout + */ + public function test_edd_is_ssl_enforced() { + $this->assertFalse( edd_is_ssl_enforced() ); + + edd_update_option( 'enforce_ssl', true ); + + $this->assertTrue( edd_is_ssl_enforced() ); + } + + /** + * Test SSL asset filter + */ + public function test_edd_enforced_ssl_asset_filter() { + // Test page URLs. These should not get modified + + $content = 'http://local.dev/'; + $this->assertSame( 'http://local.dev/', edd_enforced_ssl_asset_filter( $content ) ); + + $content = array( 'http://local.dev/' ); + $expected = array( 'http://local.dev/' ); + + $this->assertSame( $expected, edd_enforced_ssl_asset_filter( $content ) ); + + // Test asset URLs. + + $content = 'http://local.dev/assets/file.jpg'; + $this->assertSame( 'https://local.dev/assets/file.jpg', edd_enforced_ssl_asset_filter( $content ) ); + + $content = array( 'http://local.dev/assets/js/js_file.js' ); + $expected = array( 'https://local.dev/assets/js/js_file.js' ); + + $this->assertSame( $expected, edd_enforced_ssl_asset_filter( $content ) ); + } + + public function test_credit_card_format_methods() { + + // Test Cards, Thanks http://www.freeformatter.com/credit-card-number-generator-validator.html + $test_cards = array( + 'amex' => array( + '373727872168601', + '349197153955145', + '347051495193935', + ), + 'diners_club_carte_blanche' => array( + '30142801263033', + '30358703415790', + '30495144869936', + ), + 'diners_club_international' => array( + '36326253251158', + '36880678146963', + '36446904405472', + ), + 'jcb' => array( + '3530111333300000', + '3566002020360505', + ), + 'laser' => array( + '6304894437928605', + '6771753193657440', + '6771575180660297', + ), + 'visa_electron' => array( + '4175000419164927', + '4917758689682679', + '4913525617006584', + ), + 'visa' => array( + '4485319939801387', + '4556288114854566', + '4929098273851984', + ), + 'mastercard' => array( + '5529267381716121', + '5577967452254156', + '5255867454472922', + ), + 'maestro' => array( + '5038721445859297', + '5018250387370752', + '5020265126898844', + ), + 'discover' => array( + '6011911144758069', + '6011783671967201', + '6011427578160466', + ), + ); + + foreach ( $test_cards as $type => $cards ) { + foreach ( $cards as $card ) { + $card_type = edd_detect_cc_type( $card ); + $this->assertEquals( $type, $card_type ); + + $is_valid = edd_validate_card_number_format( $card ); + $this->assertTrue( $is_valid, $type . ' failed' ); + } + } + } + + public function test_edd_is_checkout_setting() { + $checkout_page = edd_get_option( 'purchase_page' ); + + $this->go_to( get_permalink( $checkout_page ) ); + + $this->assertTrue( edd_is_checkout() ); + } + + public function test_edd_is_checkout_shortcode() { + $post_id = $this->factory->post->create( array( + 'post_title' => 'Test Page', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => '[download_checkout]', + ) ); + + $this->go_to( get_permalink( $post_id ) ); + + do_action( 'template_redirect' ); // Necessary to trigger correct actions + + $this->assertTrue( edd_is_checkout() ); + } + + public function test_edd_is_checkout_fail() { + $post_id = $this->factory->post->create( array( + 'post_title' => 'Test Page 2', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => 'Test Page', + ) ); + + $this->go_to( get_permalink( $post_id ) ); + + do_action( 'template_redirect' ); // Necessary to trigger correct actions + + $this->assertFalse( edd_is_checkout() ); + } + + public function modify_gateaways( $gateways ) { + return array( 'test-gateway' ); + } +} diff --git a/tests/tests-countries.php b/tests/tests-countries.php new file mode 100644 index 00000000000..44d7cdf8e7c --- /dev/null +++ b/tests/tests-countries.php @@ -0,0 +1,441 @@ + '', + 'US' => 'United States', + 'CA' => 'Canada', + 'GB' => 'United Kingdom', + 'AF' => 'Afghanistan', + 'AX' => 'Åland Islands', + 'AL' => 'Albania', + 'DZ' => 'Algeria', + 'AS' => 'American Samoa', + 'AD' => 'Andorra', + 'AO' => 'Angola', + 'AI' => 'Anguilla', + 'AQ' => 'Antarctica', + 'AG' => 'Antigua and Barbuda', + 'AR' => 'Argentina', + 'AM' => 'Armenia', + 'AW' => 'Aruba', + 'AU' => 'Australia', + 'AT' => 'Austria', + 'AZ' => 'Azerbaijan', + 'BS' => 'Bahamas', + 'BH' => 'Bahrain', + 'BD' => 'Bangladesh', + 'BB' => 'Barbados', + 'BY' => 'Belarus', + 'BE' => 'Belgium', + 'BZ' => 'Belize', + 'BJ' => 'Benin', + 'BM' => 'Bermuda', + 'BT' => 'Bhutan', + 'BO' => 'Bolivia', + 'BQ' => 'Bonaire, Saint Eustatius and Saba', + 'BA' => 'Bosnia and Herzegovina', + 'BW' => 'Botswana', + 'BV' => 'Bouvet Island', + 'BR' => 'Brazil', + 'IO' => 'British Indian Ocean Territory', + 'BN' => 'Brunei Darrussalam', + 'BG' => 'Bulgaria', + 'BF' => 'Burkina Faso', + 'BI' => 'Burundi', + 'KH' => 'Cambodia', + 'CM' => 'Cameroon', + 'CV' => 'Cape Verde', + 'KY' => 'Cayman Islands', + 'CF' => 'Central African Republic', + 'TD' => 'Chad', + 'CL' => 'Chile', + 'CN' => 'China', + 'CX' => 'Christmas Island', + 'CC' => 'Cocos Islands', + 'CO' => 'Colombia', + 'KM' => 'Comoros', + 'CD' => 'Congo, Democratic People\'s Republic', + 'CG' => 'Congo, Republic of', + 'CK' => 'Cook Islands', + 'CR' => 'Costa Rica', + 'CI' => 'Cote d\'Ivoire', + 'HR' => 'Croatia/Hrvatska', + 'CU' => 'Cuba', + 'CW' => 'CuraÇao', + 'CY' => 'Cyprus', + 'CZ' => 'Czechia', + 'DK' => 'Denmark', + 'DJ' => 'Djibouti', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'TP' => 'East Timor', + 'EC' => 'Ecuador', + 'EG' => 'Egypt', + 'GQ' => 'Equatorial Guinea', + 'SV' => 'El Salvador', + 'ER' => 'Eritrea', + 'EE' => 'Estonia', + 'ET' => 'Ethiopia', + 'FK' => 'Falkland Islands', + 'FO' => 'Faroe Islands', + 'FJ' => 'Fiji', + 'FI' => 'Finland', + 'FR' => 'France', + 'GF' => 'French Guiana', + 'PF' => 'French Polynesia', + 'TF' => 'French Southern Territories', + 'GA' => 'Gabon', + 'GM' => 'Gambia', + 'GE' => 'Georgia', + 'DE' => 'Germany', + 'GR' => 'Greece', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GD' => 'Grenada', + 'GP' => 'Guadeloupe', + 'GU' => 'Guam', + 'GT' => 'Guatemala', + 'GG' => 'Guernsey', + 'GN' => 'Guinea', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HT' => 'Haiti', + 'HM' => 'Heard and McDonald Islands', + 'VA' => 'Holy See (City Vatican State)', + 'HN' => 'Honduras', + 'HK' => 'Hong Kong', + 'HU' => 'Hungary', + 'IS' => 'Iceland', + 'IN' => 'India', + 'ID' => 'Indonesia', + 'IR' => 'Iran', + 'IQ' => 'Iraq', + 'IE' => 'Ireland', + 'IM' => 'Isle of Man', + 'IL' => 'Israel', + 'IT' => 'Italy', + 'JM' => 'Jamaica', + 'JP' => 'Japan', + 'JE' => 'Jersey', + 'JO' => 'Jordan', + 'KZ' => 'Kazakhstan', + 'KE' => 'Kenya', + 'KI' => 'Kiribati', + 'KW' => 'Kuwait', + 'KG' => 'Kyrgyzstan', + 'LA' => 'Lao People\'s Democratic Republic', + 'LV' => 'Latvia', + 'LB' => 'Lebanon', + 'LS' => 'Lesotho', + 'LR' => 'Liberia', + 'LY' => 'Libyan Arab Jamahiriya', + 'LI' => 'Liechtenstein', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'MO' => 'Macau', + 'MK' => 'Macedonia', + 'MG' => 'Madagascar', + 'MW' => 'Malawi', + 'MY' => 'Malaysia', + 'MV' => 'Maldives', + 'ML' => 'Mali', + 'MT' => 'Malta', + 'MH' => 'Marshall Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MU' => 'Mauritius', + 'YT' => 'Mayotte', + 'MX' => 'Mexico', + 'FM' => 'Micronesia', + 'MD' => 'Moldova, Republic of', + 'MC' => 'Monaco', + 'MN' => 'Mongolia', + 'ME' => 'Montenegro', + 'MS' => 'Montserrat', + 'MA' => 'Morocco', + 'MZ' => 'Mozambique', + 'MM' => 'Myanmar', + 'NA' => 'Namibia', + 'NR' => 'Nauru', + 'NP' => 'Nepal', + 'NL' => 'Netherlands', + 'AN' => 'Netherlands Antilles', + 'NC' => 'New Caledonia', + 'NZ' => 'New Zealand', + 'NI' => 'Nicaragua', + 'NE' => 'Niger', + 'NG' => 'Nigeria', + 'NU' => 'Niue', + 'NF' => 'Norfolk Island', + 'KP' => 'North Korea', + 'MP' => 'Northern Mariana Islands', + 'NO' => 'Norway', + 'OM' => 'Oman', + 'PK' => 'Pakistan', + 'PW' => 'Palau', + 'PS' => 'Palestinian Territories', + 'PA' => 'Panama', + 'PG' => 'Papua New Guinea', + 'PY' => 'Paraguay', + 'PE' => 'Peru', + 'PH' => 'Philippines', + 'PN' => 'Pitcairn Island', + 'PL' => 'Poland', + 'PT' => 'Portugal', + 'PR' => 'Puerto Rico', + 'QA' => 'Qatar', + 'XK' => 'Republic of Kosovo', + 'RE' => 'Reunion Island', + 'RO' => 'Romania', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'BL' => 'Saint Barthélemy', + 'SH' => 'Saint Helena', + 'KN' => 'Saint Kitts and Nevis', + 'LC' => 'Saint Lucia', + 'MF' => 'Saint Martin (French)', + 'SX' => 'Saint Martin (Dutch)', + 'PM' => 'Saint Pierre and Miquelon', + 'VC' => 'Saint Vincent and the Grenadines', + 'SM' => 'San Marino', + 'ST' => 'São Tomé and Príncipe', + 'SA' => 'Saudi Arabia', + 'SN' => 'Senegal', + 'RS' => 'Serbia', + 'SC' => 'Seychelles', + 'SL' => 'Sierra Leone', + 'SG' => 'Singapore', + 'SK' => 'Slovak Republic', + 'SI' => 'Slovenia', + 'SB' => 'Solomon Islands', + 'SO' => 'Somalia', + 'ZA' => 'South Africa', + 'GS' => 'South Georgia', + 'KR' => 'South Korea', + 'SS' => 'South Sudan', + 'ES' => 'Spain', + 'LK' => 'Sri Lanka', + 'SD' => 'Sudan', + 'SR' => 'Suriname', + 'SJ' => 'Svalbard and Jan Mayen Islands', + 'SZ' => 'Swaziland', + 'SE' => 'Sweden', + 'CH' => 'Switzerland', + 'SY' => 'Syrian Arab Republic', + 'TW' => 'Taiwan', + 'TJ' => 'Tajikistan', + 'TZ' => 'Tanzania', + 'TH' => 'Thailand', + 'TL' => 'Timor-Leste', + 'TG' => 'Togo', + 'TK' => 'Tokelau', + 'TO' => 'Tonga', + 'TT' => 'Trinidad and Tobago', + 'TN' => 'Tunisia', + 'TR' => 'Turkey', + 'TM' => 'Turkmenistan', + 'TC' => 'Turks and Caicos Islands', + 'TV' => 'Tuvalu', + 'UG' => 'Uganda', + 'UA' => 'Ukraine', + 'AE' => 'United Arab Emirates', + 'UY' => 'Uruguay', + 'UM' => 'US Minor Outlying Islands', + 'UZ' => 'Uzbekistan', + 'VU' => 'Vanuatu', + 'VE' => 'Venezuela', + 'VN' => 'Vietnam', + 'VG' => 'Virgin Islands (British)', + 'VI' => 'Virgin Islands (USA)', + 'WF' => 'Wallis and Futuna Islands', + 'EH' => 'Western Sahara', + 'WS' => 'Western Samoa', + 'YE' => 'Yemen', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', + ); + + $this->assertEquals( $expected, edd_get_country_list() ); + } + + public function test_states_list() { + $expected = array( + '' => '', + 'AL' => 'Alabama', + 'AK' => 'Alaska', + 'AZ' => 'Arizona', + 'AR' => 'Arkansas', + 'CA' => 'California', + 'CO' => 'Colorado', + 'CT' => 'Connecticut', + 'DE' => 'Delaware', + 'DC' => 'District of Columbia', + 'FL' => 'Florida', + 'GA' => 'Georgia', + 'HI' => 'Hawaii', + 'ID' => 'Idaho', + 'IL' => 'Illinois', + 'IN' => 'Indiana', + 'IA' => 'Iowa', + 'KS' => 'Kansas', + 'KY' => 'Kentucky', + 'LA' => 'Louisiana', + 'ME' => 'Maine', + 'MD' => 'Maryland', + 'MA' => 'Massachusetts', + 'MI' => 'Michigan', + 'MN' => 'Minnesota', + 'MS' => 'Mississippi', + 'MO' => 'Missouri', + 'MT' => 'Montana', + 'NE' => 'Nebraska', + 'NV' => 'Nevada', + 'NH' => 'New Hampshire', + 'NJ' => 'New Jersey', + 'NM' => 'New Mexico', + 'NY' => 'New York', + 'NC' => 'North Carolina', + 'ND' => 'North Dakota', + 'OH' => 'Ohio', + 'OK' => 'Oklahoma', + 'OR' => 'Oregon', + 'PA' => 'Pennsylvania', + 'RI' => 'Rhode Island', + 'SC' => 'South Carolina', + 'SD' => 'South Dakota', + 'TN' => 'Tennessee', + 'TX' => 'Texas', + 'UT' => 'Utah', + 'VT' => 'Vermont', + 'VA' => 'Virginia', + 'WA' => 'Washington', + 'WV' => 'West Virginia', + 'WI' => 'Wisconsin', + 'WY' => 'Wyoming', + 'AS' => 'American Samoa', + 'CZ' => 'Canal Zone', + 'CM' => 'Commonwealth of the Northern Mariana Islands', + 'FM' => 'Federated States of Micronesia', + 'GU' => 'Guam', + 'MH' => 'Marshall Islands', + 'MP' => 'Northern Mariana Islands', + 'PW' => 'Palau', + 'PI' => 'Philippine Islands', + 'PR' => 'Puerto Rico', + 'TT' => 'Trust Territory of the Pacific Islands', + 'VI' => 'Virgin Islands', + 'AA' => 'Armed Forces - Americas', + 'AE' => 'Armed Forces - Europe, Canada, Middle East, Africa', + 'AP' => 'Armed Forces - Pacific', + ); + + $this->assertEquals( $expected, edd_get_states_list() ); + } + + public function test_provinces_list() { + $expected = array( + '' => '', + 'AB' => 'Alberta', + 'BC' => 'British Columbia', + 'MB' => 'Manitoba', + 'NB' => 'New Brunswick', + 'NL' => 'Newfoundland and Labrador', + 'NS' => 'Nova Scotia', + 'NT' => 'Northwest Territories', + 'NU' => 'Nunavut', + 'ON' => 'Ontario', + 'PE' => 'Prince Edward Island', + 'QC' => 'Quebec', + 'SK' => 'Saskatchewan', + 'YT' => 'Yukon', + ); + + $this->assertEquals( $expected, edd_get_provinces_list() ); + } + + public function test_angola_provinces_list() { + $expected = array( + '' => '', + 'BGO' => 'Bengo', + 'BGU' => 'Benguela', + 'BIE' => 'Bié', + 'CAB' => 'Cabinda', + 'CNN' => 'Cunene', + 'HUA' => 'Huambo', + 'HUI' => 'Huíla', + 'CCU' => 'Kuando Kubango', // Cuando Cubango + 'CNO' => 'Kwanza-Norte', // Cuanza Norte + 'CUS' => 'Kwanza-Sul', // Cuanza Sul + 'LUA' => 'Luanda', + 'LNO' => 'Lunda-Norte', // Lunda Norte + 'LSU' => 'Lunda-Sul', // Lunda Sul + 'MAL' => 'Malanje', // Malanje + 'MOX' => 'Moxico', + 'NAM' => 'Namibe', + 'UIG' => 'Uíge', + 'ZAI' => 'Zaire', + ); + + $this->assertSame( $expected, edd_get_angola_provinces_list() ); + } + + public function test_netherlands_provinces_list() { + $expected = array( + '' => '', + 'DR' => __( 'Drenthe', 'easy-digital-downloads' ), + 'FL' => __( 'Flevoland', 'easy-digital-downloads' ), + 'FR' => __( 'Friesland', 'easy-digital-downloads' ), + 'GE' => __( 'Gelderland', 'easy-digital-downloads' ), + 'GR' => __( 'Groningen', 'easy-digital-downloads' ), + 'LI' => __( 'Limburg', 'easy-digital-downloads' ), + 'NB' => __( 'North Brabant', 'easy-digital-downloads' ), + 'NH' => __( 'North Holland', 'easy-digital-downloads' ), + 'OV' => __( 'Overijssel', 'easy-digital-downloads' ), + 'ZH' => __( 'South Holland', 'easy-digital-downloads' ), + 'UT' => __( 'Utrecht', 'easy-digital-downloads' ), + 'ZE' => __( 'Zeeland', 'easy-digital-downloads' ), + 'BO' => __( 'Bonaire', 'easy-digital-downloads' ), + 'SA' => __( 'Saba', 'easy-digital-downloads' ), + 'SE' => __( 'Sint Eustatius', 'easy-digital-downloads' ), + ); + + $this->assertSame( $expected, edd_get_netherlands_provinces_list() ); + } + + public function test_country_name_blank() { + $this->assertEmpty( edd_get_country_name() ); + } + + public function test_country_name_us() { + $this->assertSame( 'United States', edd_get_country_name( 'US' ) ); + } + + public function test_get_state_name_oh() { + $this->assertSame( 'Ohio', edd_get_state_name( 'US', 'OH' ) ); + } + + public function test_get_state_name_aomori() { + $this->assertSame( 'Aomori', edd_get_state_name( 'JP', 'JP02' ) ); + } + + public function test_get_state_name_argentina() { + $this->assertSame( 'AR-B', edd_get_state_name( 'AR', 'AR-B' ) ); + } + + public function test_get_state_name_empty_state() { + $this->assertEmpty( edd_get_state_name( 'AR' ) ); + } + + public function test_get_state_name_empty() { + $this->assertEmpty( edd_get_state_name() ); + } +} diff --git a/tests/tests-date-functions.php b/tests/tests-date-functions.php new file mode 100644 index 00000000000..e7673b6fefe --- /dev/null +++ b/tests/tests-date-functions.php @@ -0,0 +1,296 @@ +utils->get_gmt_offset( true ); + } + + public function tearDown(): void { + $_REQUEST['range'] = ''; + + parent::tearDown(); + } + + // + // Tests + // + + /** + * @covers ::edd_date_i18n() + */ + public function test_date_i18n_with_timestamp_and_no_format_should_return_localized_date_in_date_format() { + $expected = 'January 1, 2003'; + $actual = edd_date_i18n( '01/02/2003' ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::edd_date_i18n() + */ + public function test_date_i18n_with_empty_format_should_return_localized_date_in_date_format() { + $expected = 'January 1, 2003'; + $actual = edd_date_i18n( '01/02/2003', '' ); + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::edd_date_i18n() + */ + public function test_date_i18n_with_invalid_timestamp_and_no_format_should_return_1970() { + $this->assertSame( 'December 31, 1969', edd_date_i18n( 'foo' ) ); + } + + /** + * @covers ::edd_date_i18n() + */ + public function test_date_i18n_invalid_timestamp_and_format_should_return_1970_and_respect_format() { + $this->assertSame( 'December 31, 1969 7:00 pm', edd_date_i18n( 'foo', 'datetime' ) ); + } + + /** + * @covers ::edd_get_timezone_id() + */ + public function test_get_timezone_should_return_the_current_timezone_based_on_WP_settings() { + if ( version_compare( phpversion(), '5.5', '<' ) ) { + + // Tests our logic around a shortcoming in PHP 5.3 and 5.4 with DateTimeZone + $is_dst = date( 'I' ); + $expected = timezone_name_from_abbr('', get_option( 'gmt_offset', 0 ) * HOUR_IN_SECONDS, $is_dst ); + $this->assertSame( $expected, edd_get_timezone_id() ); + + } else { + $this->assertSame( 'GMT-5', edd_get_timezone_id() ); + } + } + + /** + * @covers ::edd_get_date_format() + */ + public function test_get_date_format_empty_format_should_default_to_date_format() { + $this->assertSame( get_option( 'date_format', '' ), edd_get_date_format( '' ) ); + } + + /** + * @covers ::edd_get_date_format() + */ + public function test_get_date_format_date_should_return_date_format_value() { + $this->assertSame( get_option( 'date_format', '' ), edd_get_date_format( 'date' ) ); + } + + /** + * @covers ::edd_get_date_format() + */ + public function test_get_date_format_time_should_return_time_format_value() { + $this->assertSame( get_option( 'time_format', '' ), edd_get_date_format( 'time' ) ); + } + + /** + * @covers ::edd_get_date_format() + */ + public function test_get_date_format_datetime_should_return_date_and_time_format_values() { + $expected = get_option( 'date_format', '' ) . ' ' . get_option( 'time_format', '' ); + + $this->assertSame( $expected, edd_get_date_format( 'datetime' ) ); + } + + /** + * @covers ::edd_get_date_format() + */ + public function test_get_date_format_mysql_should_return_mysql_format() { + $this->assertSame( 'Y-m-d H:i:s', edd_get_date_format( 'mysql' ) ); + } + + /** + * @covers ::edd_get_date_format() + */ + public function test_get_date_format_non_shorthand_format_should_return_that_format() { + $this->assertSame( 'm/d/Y', edd_get_date_format( 'm/d/Y' ) ); + } + + /** + * @covers ::edd_get_report_dates() + * @expectEDDeprecated edd_get_report_dates + */ + public function test_get_report_dates_correct_this_month_at_the_end_of_the_month_utc() { + $_REQUEST['range'] = 'this_month'; + + // Since we are using GMT time, the 'end of month' is technically in next month. + $dates = edd_get_report_dates( 'UTC' ); + + /** + * We know that these will fail near the end of the month, the above is a deprecated function + * and we re-wrote a lot of the date logic with this in mind. + */ + $this->markTestIncomplete(); + + $this->assertEquals( 1, $dates['day'] ); + $this->assertEquals( date( 'n' ), $dates['m_start'] ); + $this->assertEquals( date( 'Y' ), $dates['year'] ); + $this->assertEquals( 1, $dates['day_end'] ); + $this->assertEquals( date( 'n', strtotime( '+1 month' ) ), $dates['m_end'] ); + $this->assertEquals( date( 'Y', strtotime( '+1 month' ) ), $dates['year_end'] ); + } + + /** + * @covers ::edd_get_report_dates() + * @expectEDDeprecated edd_get_report_dates + */ + public function test_get_report_dates_correct_this_month_at_the_end_of_the_month_nz() { + $_REQUEST['range'] = 'this_month'; + + $dates = edd_get_report_dates( 'Pacific/Auckland' ); + + /** + * We know that these will fail near the end of the month, the above is a deprecated function + * and we re-wrote a lot of the date logic with this in mind. + */ + $this->markTestIncomplete(); + + $auk_date = edd()->utils->date( 'now', 'Pacific/Auckland' ); + + $this->assertEquals( 1, $dates['day'] ); + $this->assertEquals( $auk_date->format( 'n' ), $dates['m_start'] ); + $this->assertEquals( $auk_date->format( 'Y' ), $dates['year'] ); + $this->assertEquals( 1, $dates['day_end'] ); + + $expected_end_month = $auk_date->format( 'n' ) + 1; + $expected_end_year = $auk_date->format( 'Y' ); + + if ( $expected_end_month > 12 ) { + $roll_over_months = $expected_end_month - 12; + $expected_end_month = $roll_over_months; + $expected_end_year++; + } + + $this->assertEquals( $expected_end_month, $dates['m_end'] ); + $this->assertEquals( $expected_end_year, $dates['year_end'] ); + } + + /** + * @covers ::edd_get_report_dates() + * @expectEDDeprecated edd_get_report_dates + */ + public function test_get_report_dates_correct_this_month_at_the_beginning_of_the_month_utc() { + $_REQUEST['range'] = 'this_month'; + + $dates = edd_get_report_dates( 'UTC' ); + + /** + * We know that these will fail near the end of the month, the above is a deprecated function + * and we re-wrote a lot of the date logic with this in mind. + */ + $this->markTestIncomplete(); + + $this->assertEquals( 1, $dates['day'] ); + $this->assertEquals( date( 'n' ), $dates['m_start'] ); + $this->assertEquals( date( 'Y' ), $dates['year'] ); + $this->assertEquals( 1, $dates['day_end'] ); + $this->assertEquals( date( 'n', strtotime( '+1 month' ) ), $dates['m_end'] ); + $this->assertEquals( date( 'Y', strtotime( '+1 month' ) ), $dates['year_end'] ); + } + + /** + * @covers ::edd_get_report_dates() + * @expectEDDeprecated edd_get_report_dates + */ + public function test_get_report_dates_correct_this_month_at_the_beginning_of_the_month_pdt() { + $_REQUEST['range'] = 'this_month'; + + $dates = edd_get_report_dates( 'America/Los_Angeles' ); + + /** + * We know that these will fail near the end of the month, the above is a deprecated function + * and we re-wrote a lot of the date logic with this in mind. + */ + $this->markTestIncomplete(); + + $this->assertEquals( 1, $dates['day'] ); + $this->assertEquals( date( 'n' ), $dates['m_start'] ); + $this->assertEquals( date( 'Y' ), $dates['year'] ); + $this->assertEquals( 1, $dates['day_end'] ); + $this->assertEquals( date( 'n', strtotime( '+1 month' ) ), $dates['m_end'] ); + $this->assertEquals( date( 'Y', strtotime( '+1 month' ) ), $dates['year_end'] ); + } + + /** + * @covers ::edd_get_report_dates() + * @expectEDDeprecated edd_get_report_dates + */ + public function test_get_report_dates_correct_this_moment_utc() { + $_REQUEST['range'] = 'this_month'; + + $current_time = current_time( 'timestamp' ); + $dates = edd_get_report_dates( 'UTC' ); + + /** + * We know that these will fail near the end of the month, the above is a deprecated function + * and we re-wrote a lot of the date logic with this in mind. + */ + $this->markTestIncomplete(); + + $this->assertEquals( 1, $dates['day'] ); + $this->assertEquals( date( 'n', $current_time ), $dates['m_start'] ); + $this->assertEquals( date( 'Y', $current_time ), $dates['year'] ); + $this->assertEquals( 1, $dates['day_end'] ); + $this->assertEquals( date( 'n', strtotime( '+1 month' ) ), $dates['m_end'] ); + $this->assertEquals( date( 'Y', strtotime( '+1 month' ) ), $dates['year_end'] ); + } + + /** + * @covers ::EDD()->utils->date() + * + */ + public function test_date_invalid_date_returns_date() { + $date = EDD()->utils->date( '::00', edd_get_timezone_id(), false ); + + $this->assertTrue( $date instanceof Date ); + } + + /** + * @covers ::EDD()->utils->get_date_string() + */ + public function test_get_date_string_valid_returns_valid_string() { + $actual = EDD()->utils->get_date_string( '2020-01-10', 13, 9 ); + + $this->assertSame( '2020-01-10 13:09:00', $actual ); + } + + /** + * @covers ::EDD()->utils->get_date_string() + */ + public function test_get_date_string_empty_returns_valid_string() { + $actual = EDD()->utils->get_date_string(); + $expected = date( 'Y-m-d' ) . ' 00:00:00'; + + $this->assertSame( $expected, $actual ); + } + + /** + * @covers ::EDD()->utils->get_date_string() + */ + public function test_get_date_string_invalid_returns_valid_string() { + $actual = EDD()->utils->get_date_string( '2020-01-100', 100, 99 ); + $expected = date( 'Y-m-d' ) . ' 23:59:00'; + + $this->assertStringContainsString( $expected, $actual ); + } +} diff --git a/tests/tests-date-object.php b/tests/tests-date-object.php new file mode 100644 index 00000000000..76994cc1fbc --- /dev/null +++ b/tests/tests-date-object.php @@ -0,0 +1,156 @@ +utils->get_gmt_offset( true ); + } + + /** + * @covers ::__construct() + */ + public function test_Date_should_extend_DateTime() { + $this->assertInstanceOf( 'DateTime', $this->get_date_instance() ); + } + + /** + * @covers ::__construct() + */ + public function test_Date_should_always_convert_date_to_WordPress_time() { + $date = $this->get_date_instance(); + $expected = gmdate( 'Y-m-d H:i:s', strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset() ); + + $this->assertSame( $expected, $date->format( 'mysql' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_empty_format_should_use_datetime_shorthand_format() { + $date = $this->get_date_instance(); + $expected = gmdate( edd_get_date_format( 'datetime' ), strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset() ); + + $this->assertSame( $expected, $date->format( '' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_true_format_should_use_datetime_shorthand_format() { + $date = $this->get_date_instance(); + $expected = gmdate( edd_get_date_format( 'datetime' ), strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset() ); + + $this->assertSame( $expected, $date->format( true ) ); + } + + /** + * @covers ::format() + */ + public function test_format_date_should_use_date_shorthand_format() { + $date = $this->get_date_instance(); + $expected = gmdate( edd_get_date_format( 'date' ), strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset() ); + + $this->assertSame( $expected, $date->format( 'date' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_time_should_use_time_shorthand_format() { + $date = $this->get_date_instance(); + $expected = gmdate( edd_get_date_format( 'time' ), strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset() ); + + $this->assertSame( $expected, $date->format( 'time' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_mysql_should_use_mysql_shorthand_format() { + $date = $this->get_date_instance(); + $expected = gmdate( edd_get_date_format( 'mysql' ), strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset() ); + + $this->assertSame( $expected, $date->format( 'mysql' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_object_should_return_Date_object() { + $date = $this->get_date_instance(); + + $this->assertEquals( $date, $date->format( 'object' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_timestamp_should_return_original_timestamp() { + $date = $this->get_date_instance(); + + $this->assertSame( strtotime( self::$date_string ), $date->format( 'timestamp' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_wp_timestamp_should_return_WP_timestamp() { + $date = $this->get_date_instance(); + $expected = strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset(); + + $this->assertSame( $expected, $date->format( 'wp_timestamp' ) ); + } + + /** + * @covers ::format() + */ + public function test_format_generic_date_format_should_format_with_that_scheme() { + $date = $this->get_date_instance(); + $expected = gmdate( 'm/d/Y', strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset() ); + + $this->assertSame( $expected, $date->format( 'm/d/Y' ) ); + } + + /** + * @covers ::getWPTimestamp() + */ + public function test_getWPTimestamp_should_return_timestamp_with_offset_applied() { + $date = $this->get_date_instance(); + $expected = strtotime( self::$date_string ) + EDD()->utils->get_gmt_offset(); + + $this->assertSame( $expected, $date->getWPTimestamp() ); + } + + /** + * Helper to retrieve a Date instance. + * + * @return \EDD\Utils\Date + */ + protected function get_date_instance() { + return new Date( self::$date_string, new \DateTimeZone( edd_get_timezone_id() ) ); + } +} diff --git a/tests/tests-easy-digital-downloads.php b/tests/tests-easy-digital-downloads.php new file mode 100755 index 00000000000..75e3db279bc --- /dev/null +++ b/tests/tests-easy-digital-downloads.php @@ -0,0 +1,195 @@ +object = EDD(); + } + + public function tearDown(): void { + parent::tearDown(); + } + + public function test_edd_instance() { + $this->assertClassHasStaticAttribute( 'instance', 'Easy_Digital_Downloads' ); + } + + /** + * @covers Easy_Digital_Downloads::setup_constants + */ + public function test_constants() { + // Plugin Folder URL + $path = str_replace( 'tests/', '', plugin_dir_url( __FILE__ ) ); + $this->assertSame( EDD_PLUGIN_URL, $path ); + + // Plugin Folder Path + $path = str_replace( 'tests/', '', plugin_dir_path( __FILE__ ) ); + $path = substr( $path, 0, -1 ); + $edd = substr( EDD_PLUGIN_DIR, 0, -1 ); + $this->assertSame( $edd, $path ); + + // Plugin Root File + $path = str_replace( 'tests/', '', plugin_dir_path( __FILE__ ) ); + $this->assertSame( EDD_PLUGIN_FILE, $path.'easy-digital-downloads.php' ); + } + + /** + * @dataProvider _test_includes_dp + * @covers ::includes() + * + * @group edd_includes + */ + public function test_includes( $path_to_file ) { + $this->assertFileExists( $path_to_file ); + } + + /** + * Data provider for test_includes(). + */ + public function _test_includes_dp() { + return array( + array( EDD_PLUGIN_DIR . 'includes/admin/settings/register-settings.php' ), + array( EDD_PLUGIN_DIR . 'includes/install.php' ), + array( EDD_PLUGIN_DIR . 'includes/actions.php' ), + array( EDD_PLUGIN_DIR . 'includes/deprecated-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/deprecated-hooks.php' ), + array( EDD_PLUGIN_DIR . 'includes/ajax-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/template-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/checkout/template.php' ), + array( EDD_PLUGIN_DIR . 'includes/checkout/functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/cart/template.php' ), + array( EDD_PLUGIN_DIR . 'includes/cart/functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/cart/actions.php' ), + array( EDD_PLUGIN_DIR . 'includes/api/class-edd-api.php' ), + array( EDD_PLUGIN_DIR . 'includes/api/class-edd-api-v1.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-cache-helper.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-fees.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-html-elements.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-logging.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-session.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-roles.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-edd-stats.php' ), + array( EDD_PLUGIN_DIR . 'includes/class-utilities.php' ), + array( EDD_PLUGIN_DIR . 'includes/formatting.php' ), + array( EDD_PLUGIN_DIR . 'includes/widgets.php' ), + array( EDD_PLUGIN_DIR . 'includes/mime-types.php' ), + array( EDD_PLUGIN_DIR . 'includes/gateways/functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/gateways/paypal-standard.php' ), + array( EDD_PLUGIN_DIR . 'includes/gateways/manual.php' ), + array( EDD_PLUGIN_DIR . 'includes/interface-edd-exception.php' ), + array( EDD_PLUGIN_DIR . 'includes/discount-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/payments/functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/payments/actions.php' ), + array( EDD_PLUGIN_DIR . 'includes/payments/class-payment-stats.php' ), + array( EDD_PLUGIN_DIR . 'includes/payments/class-payments-query.php' ), + array( EDD_PLUGIN_DIR . 'includes/misc-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/download-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/downloads/recalculations.php' ), + array( EDD_PLUGIN_DIR . 'includes/scripts.php' ), + array( EDD_PLUGIN_DIR . 'includes/post-types.php' ), + array( EDD_PLUGIN_DIR . 'includes/plugin-compatibility.php' ), + array( EDD_PLUGIN_DIR . 'includes/reports/exceptions/class-invalid-parameter.php' ), + array( EDD_PLUGIN_DIR . 'includes/emails/functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/emails/template.php' ), + array( EDD_PLUGIN_DIR . 'includes/emails/actions.php' ), + array( EDD_PLUGIN_DIR . 'includes/error-tracking.php' ), + array( EDD_PLUGIN_DIR . 'includes/user-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/query-filters.php' ), + array( EDD_PLUGIN_DIR . 'includes/tax-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/process-purchase.php' ), + array( EDD_PLUGIN_DIR . 'includes/users/login.php' ), + array( EDD_PLUGIN_DIR . 'includes/users/lost-password.php' ), + array( EDD_PLUGIN_DIR . 'includes/users/register.php' ), + array( EDD_PLUGIN_DIR . 'includes/reports/class-init.php' ), + array( EDD_PLUGIN_DIR . 'includes/utils/class-edd-exception.php' ), + array( EDD_PLUGIN_DIR . 'includes/utils/class-registry.php' ), + array( EDD_PLUGIN_DIR . 'includes/utils/interface-static-registry.php' ), + array( EDD_PLUGIN_DIR . 'includes/utils/exceptions/class-attribute-not-found.php' ), + array( EDD_PLUGIN_DIR . 'includes/utils/exceptions/class-invalid-argument.php' ), + array( EDD_PLUGIN_DIR . 'includes/utils/exceptions/class-invalid-parameter.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/admin-actions.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/class-edd-notices.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/admin-pages.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/dashboard-widgets.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/thickbox.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/upload-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/customers/class-customer-table.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/customers/customer-actions.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/customers/customer-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/customers/customers.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/downloads/dashboard-columns.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/downloads/metabox.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/downloads/contextual-help.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/discounts/contextual-help.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/discounts/discount-actions.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/discounts/discount-codes.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/payments/payments-history.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/payments/contextual-help.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/reporting/contextual-help.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/reporting/export/export-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/reporting/reports.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/reporting/graphing.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/settings/display-settings.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/settings/contextual-help.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/upgrades/upgrade-functions.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/upgrades/upgrades.php' ), + array( EDD_PLUGIN_DIR . 'includes/admin/class-edd-heartbeat.php' ), + array( EDD_PLUGIN_DIR . 'includes/process-download.php' ), + array( EDD_PLUGIN_DIR . 'includes/shortcodes.php' ), + array( EDD_PLUGIN_DIR . 'includes/theme-compatibility.php' ), + ); + } + + /** + * @dataProvider _test_includes_assets_dp + * @covers ::includes() + * + * @group edd_includes + */ + public function test_includes_assets( $path_to_file ) { + $this->assertFileExists( $path_to_file ); + } + + /** + * Data provider for test_includes_assets(). + */ + public function _test_includes_assets_dp() { + return array( + array( EDD_PLUGIN_DIR . 'assets/css/chosen.min.css' ), + array( EDD_PLUGIN_DIR . 'assets/css/edd-admin-chosen.min.css' ), + array( EDD_PLUGIN_DIR . 'assets/css/edd-admin.min.css' ), + array( EDD_PLUGIN_DIR . 'assets/images/edd-cpt-2x.png' ), + array( EDD_PLUGIN_DIR . 'assets/images/edd-cpt.png' ), + array( EDD_PLUGIN_DIR . 'assets/images/edd-icon-2x.png' ), + array( EDD_PLUGIN_DIR . 'assets/images/edd-icon.png' ), + array( EDD_PLUGIN_DIR . 'assets/images/edd-logo.png' ), + array( EDD_PLUGIN_DIR . 'assets/images/edd-media.png' ), + array( EDD_PLUGIN_DIR . 'assets/images/loading.gif' ), + array( EDD_PLUGIN_DIR . 'templates/images/loading.gif' ), + array( EDD_PLUGIN_DIR . 'assets/images/media-button.png' ), + array( EDD_PLUGIN_DIR . 'templates/images/tick.png' ), + array( EDD_PLUGIN_DIR . 'assets/images/xit.gif' ), + array( EDD_PLUGIN_DIR . 'assets/css/edd.min.css' ), + array( EDD_PLUGIN_DIR . 'templates/images/xit.gif' ), + array( EDD_PLUGIN_DIR . 'assets/js/edd-admin.js' ), + array( EDD_PLUGIN_DIR . 'assets/js/edd-ajax.js' ), + array( EDD_PLUGIN_DIR . 'assets/js/edd-checkout-global.js' ), + array( EDD_PLUGIN_DIR . 'assets/js/vendor/chosen.jquery.min.js' ), + array( EDD_PLUGIN_DIR . 'assets/js/vendor/jquery.creditcardvalidator.min.js' ), + array( EDD_PLUGIN_DIR . 'assets/js/vendor/jquery.flot.min.js' ), + + // Cannot be in /vendor/ for back-compat :( + array( EDD_PLUGIN_DIR . 'assets/js/jquery.validate.min.js' ), + ); + } +} diff --git a/tests/tests-emails.php b/tests/tests-emails.php new file mode 100755 index 00000000000..f0a05958fd3 --- /dev/null +++ b/tests/tests-emails.php @@ -0,0 +1,341 @@ +post->create( array( 'post_title' => 'Test Download', 'post_type' => 'download', 'post_status' => 'publish' ) ); + + $_variable_pricing = array( + array( + 'name' => 'Simple', + 'amount' => 20 + ), + array( + 'name' => 'Advanced', + 'amount' => 100 + ) + ); + + $_download_files = array( + array( + 'name' => 'File 1', + 'file' => 'http://localhost/file1.jpg', + 'condition' => 0 + ), + array( + 'name' => 'File 2', + 'file' => 'http://localhost/file2.jpg', + 'condition' => 'all' + ) + ); + + $meta = array( + 'edd_price' => '0.00', + '_variable_pricing' => 1, + '_edd_price_options_mode' => 'on', + 'edd_variable_prices' => array_values( $_variable_pricing ), + 'edd_download_files' => array_values( $_download_files ), + '_edd_download_limit' => 20, + '_edd_hide_purchase_link' => 1, + 'edd_product_notes' => 'Purchase Notes', + '_edd_product_type' => 'default', + '_edd_download_earnings' => 129.43, + '_edd_download_sales' => 59, + '_edd_download_limit_override_1' => 1 + ); + foreach( $meta as $key => $value ) { + update_post_meta( $post_id, $key, $value ); + } + + self::$post = get_post( $post_id ); + + /** Generate some sales */ + $user = get_userdata(1); + + $user_info = array( + 'id' => $user->ID, + 'email' => $user->user_email, + 'first_name' => 'Network', + 'last_name' => 'Administrator', + 'discount' => 'none' + ); + + $download_details = array( + array( + 'id' => self::$post->ID, + 'options' => array( + 'price_id' => 1 + ) + ) + ); + + $price = '100.00'; + + $total = 0; + + $prices = get_post_meta( $download_details[0]['id'], 'edd_variable_prices', true ); + $item_price = $prices[1]['amount']; + + $total += $item_price; + + $cart_details = array( + array( + 'name' => 'Test Download', + 'id' => self::$post->ID, + 'item_number' => array( + 'id' => self::$post->ID, + 'options' => array( + 'price_id' => 1 + ) + ), + 'discount' => 0, + 'subtotal' => 100, + 'price' => 100, + 'item_price' => 100, + 'tax' => 0, + 'quantity' => 1 + ) + ); + + $purchase_data = array( + 'price' => number_format( (float) $total, 2 ), + 'date' => date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ), + 'purchase_key' => strtolower( md5( uniqid() ) ), + 'key' => strtolower( md5( uniqid() ) ), + 'user_email' => $user_info['email'], + 'user_info' => $user_info, + 'currency' => 'USD', + 'downloads' => $download_details, + 'cart_details' => $cart_details, + 'status' => 'pending', + 'gateway' => 'manual', + 'email' => 'admin@example.org', + 'amount' => number_format( (float) $total, 2 ), + ); + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SERVER_NAME'] = 'edd-virtual.local'; + + self::$payment_id = edd_insert_payment( $purchase_data ); + } + + /** + * Test that each of the actions are added and each hooked in with the right priority + */ + public function test_email_actions() { + global $wp_filter; + + $this->assertarrayHasKey( 'edd_admin_email_notice', $wp_filter['edd_admin_sale_notice'][10] ); + $this->assertarrayHasKey( 'edd_trigger_purchase_receipt', $wp_filter['edd_complete_purchase'][999] ); + $this->assertarrayHasKey( 'edd_resend_purchase_receipt', $wp_filter['edd_email_links'][10] ); + $this->assertarrayHasKey( 'edd_send_test_email', $wp_filter['edd_send_test_email'][10] ); + } + + public function test_admin_notice_emails() { + $expected = array( 'admin@example.org' ); + + $this->assertEquals( $expected, edd_get_admin_notice_emails() ); + } + + public function test_admin_notice_disabled() { + $this->assertFalse( edd_admin_notices_disabled() ); + } + + public function test_email_templates() { + $expected = array( + 'default' => 'Default Template', + 'none' => 'No template, plain text only' + ); + + $this->assertEquals( $expected, edd_get_email_templates() ); + } + + public function test_get_template() { + $this->assertEquals( 'default', EDD()->emails->get_template() ); + } + + public function test_edd_get_default_sale_notification_email() { + $email = edd_get_default_sale_notification_email(); + + $this->assertStringContainsString( 'Hello', $email ); + $this->assertStringContainsString( 'A Downloads purchase has been made', $email ); + $this->assertStringContainsString( 'Downloads sold:', $email ); + $this->assertStringContainsString( '{download_list}', $email ); + $this->assertStringContainsString( 'Amount: {price}', $email ); + } + + public function test_email_tags_get_tags() { + $tags = edd_get_email_tags(); + + $this->assertIsArray( $tags ); + $this->assertarrayHasKey( 'download_list', $tags ); + $this->assertarrayHasKey( 'file_urls', $tags ); + $this->assertarrayHasKey( 'name', $tags ); + $this->assertarrayHasKey( 'fullname', $tags ); + $this->assertarrayHasKey( 'username', $tags ); + $this->assertarrayHasKey( 'user_email', $tags ); + $this->assertarrayHasKey( 'date', $tags ); + $this->assertarrayHasKey( 'subtotal', $tags ); + $this->assertarrayHasKey( 'tax', $tags ); + $this->assertarrayHasKey( 'price', $tags ); + $this->assertarrayHasKey( 'payment_id', $tags ); + $this->assertarrayHasKey( 'payment_method', $tags ); + $this->assertarrayHasKey( 'sitename', $tags ); + $this->assertarrayHasKey( 'receipt_link', $tags ); + } + + public function test_email_tags_add() { + edd_add_email_tag( 'sample_tag', 'A sample tag for the unit test', '__return_empty_array' ); + + $this->assertTrue( edd_email_tag_exists( 'sample_tag' ) ); + } + + public function test_email_tags_remove() { + edd_remove_email_tag( 'sample_tag' ); + + $this->assertFalse( edd_email_tag_exists( 'sample_tag' ) ); + } + + public function test_email_tags_download_list() { + $order_items = edd_get_order_items( array( 'order_id' => self::$payment_id ) ); + $this->assertStringContainsString( '' . $order_items[0]->product_name . '', edd_email_tag_download_list( self::$payment_id ) ); + $this->assertStringContainsString( '
    ", EDD()->emails->get_headers() ); + } + + public function test_get_heading() { + EDD()->emails->__set( 'heading', 'Purchase Receipt' ); + + $this->assertEquals( 'Purchase Receipt', EDD()->emails->get_heading() ); + } + + public function test_text_to_html() { + $message = "Hello, this is plain text that I am going to convert to HTML\r\n"; + $message .= "Line breaks should become BR tags.\r\n"; + + $expected = wpautop( $message ); + + EDD()->emails->content_type = 'text/html'; + $message = EDD()->emails->text_to_html( $message, EDD()->emails ); + + $this->assertEquals( $expected, $message ); + } +} diff --git a/tests/tests-errors.php b/tests/tests-errors.php new file mode 100755 index 00000000000..58e961cc820 --- /dev/null +++ b/tests/tests-errors.php @@ -0,0 +1,50 @@ +session->get( 'edd_errors' ); + + $this->assertArrayHasKey( 'invalid_email', $errors ); + $this->assertArrayHasKey( 'invalid_user', $errors ); + $this->assertArrayHasKey( 'username_incorrect', $errors ); + $this->assertArrayHasKey( 'password_incorrect', $errors ); + } + + public function test_clear_errors() { + $errors = edd_clear_errors(); + $this->assertFalse( EDD()->session->get( 'edd_errors' ) ); + } + + public function test_unset_error() { + $error = edd_unset_error( 'invalid_email' ); + $errors = EDD()->session->get( 'edd_errors' ); + + $expected = array( + 'invalid_user' => 'The user information is invalid.', + 'username_incorrect' => 'The username you entered does not exist', + 'password_incorrect' => 'The password you entered is incorrect' + ); + + $this->assertEquals( $expected, $errors ); + } +} diff --git a/tests/tests-filters.php b/tests/tests-filters.php new file mode 100755 index 00000000000..9259c477c80 --- /dev/null +++ b/tests/tests-filters.php @@ -0,0 +1,349 @@ +assertArrayHasKey( 'edd_before_download_content', $wp_filter['the_content'][10] ); + $this->assertArrayHasKey( 'edd_after_download_content', $wp_filter['the_content'][10] ); + $this->assertArrayHasKey( 'edd_filter_success_page_content', $wp_filter['the_content'][99999] ); + } + + public function test_wp_head() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_version_in_header', $wp_filter['wp_head'][10] ); + } + + public function test_template_redirect() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_disable_jetpack_og_on_checkout', $wp_filter['template_redirect'][10] ); + $this->assertArrayHasKey( 'edd_block_attachments', $wp_filter['template_redirect'][10] ); + $this->assertArrayHasKey( 'edd_process_cart_endpoints', $wp_filter['template_redirect'][100] ); + } + + public function test_init() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_get_actions', $wp_filter['init'][10] ); + $this->assertArrayHasKey( 'edd_post_actions', $wp_filter['init'][10] ); + $this->assertArrayHasKey( 'edd_add_rewrite_endpoints', $wp_filter['init'][10] ); + $this->assertArrayHasKey( 'edd_no_gateway_error', $wp_filter['init'][10] ); + $this->assertArrayHasKey( 'edd_listen_for_paypal_ipn', $wp_filter['init'][10] ); + $this->assertArrayHasKey( 'edd_setup_download_taxonomies', $wp_filter['init'][0] ); + $this->assertArrayHasKey( 'edd_register_post_type_statuses', $wp_filter['init'][2] ); + $this->assertArrayHasKey( 'edd_setup_edd_post_types', $wp_filter['init'][1] ); + $this->assertArrayHasKey( 'edd_process_download', $wp_filter['init'][100] ); + } + + public function test_admin_init() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_register_settings', $wp_filter['admin_init'][10] ); + } + + public function test_delete_post() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_remove_download_logs_on_delete', $wp_filter['delete_post'][10] ); + } + + public function test_admin_enqueue_scripts() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_enqueue_admin_scripts', $wp_filter['admin_enqueue_scripts'][10] ); + } + + public function test_admin_enqueue_styles() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_enqueue_admin_styles', $wp_filter['admin_enqueue_scripts'][10] ); + } + + public function test_upload_mimes() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_allowed_mime_types', $wp_filter['upload_mimes'][10] ); + } + + public function test_widgets_init() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_register_widgets', $wp_filter['widgets_init'][10] ); + } + + public function test_wp_enqueue_scripts() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_load_scripts', $wp_filter['wp_enqueue_scripts'][10] ); + $this->assertArrayHasKey( 'edd_enqueue_styles', $wp_filter['wp_enqueue_scripts'][10] ); + } + + public function test_ajax() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_ajax_remove_from_cart', $wp_filter['wp_ajax_edd_remove_from_cart'][10] ); + $this->assertArrayHasKey( 'edd_ajax_remove_from_cart', $wp_filter['wp_ajax_nopriv_edd_remove_from_cart'][10] ); + $this->assertArrayHasKey( 'edd_ajax_add_to_cart', $wp_filter['wp_ajax_edd_add_to_cart'][10] ); + $this->assertArrayHasKey( 'edd_ajax_add_to_cart', $wp_filter['wp_ajax_nopriv_edd_add_to_cart'][10] ); + $this->assertArrayHasKey( 'edd_ajax_apply_discount', $wp_filter['wp_ajax_edd_apply_discount'][10] ); + $this->assertArrayHasKey( 'edd_ajax_apply_discount', $wp_filter['wp_ajax_nopriv_edd_apply_discount'][10] ); + $this->assertArrayHasKey( 'edd_load_checkout_login_fields', $wp_filter['wp_ajax_nopriv_checkout_login'][10] ); + $this->assertArrayHasKey( 'edd_load_checkout_register_fields', $wp_filter['wp_ajax_nopriv_checkout_register'][10] ); + $this->assertArrayHasKey( 'edd_ajax_get_download_title', $wp_filter['wp_ajax_edd_get_download_title'][10] ); + $this->assertArrayHasKey( 'edd_ajax_get_download_title', $wp_filter['wp_ajax_nopriv_edd_get_download_title'][10] ); + $this->assertArrayHasKey( 'edd_check_for_download_price_variations', $wp_filter['wp_ajax_edd_check_for_download_price_variations'][10] ); + $this->assertArrayHasKey( 'edd_load_ajax_gateway', $wp_filter['wp_ajax_edd_load_gateway'][10] ); + $this->assertArrayHasKey( 'edd_load_ajax_gateway', $wp_filter['wp_ajax_nopriv_edd_load_gateway'][10] ); + $this->assertArrayHasKey( 'edd_print_errors', $wp_filter['edd_ajax_checkout_errors'][10] ); + $this->assertArrayHasKey( 'edd_process_purchase_form', $wp_filter['wp_ajax_edd_process_checkout'][10] ); + $this->assertArrayHasKey( 'edd_process_purchase_form', $wp_filter['wp_ajax_nopriv_edd_process_checkout'][10] ); + } + + public function test_edd_after_download_content() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_append_purchase_link', $wp_filter['edd_after_download_content'][10] ); + $this->assertArrayHasKey( 'edd_show_added_to_cart_messages', $wp_filter['edd_after_download_content'][10] ); + } + + public function test_edd_purchase_link_top() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_purchase_variable_pricing', $wp_filter['edd_purchase_link_top'][10] ); + $this->assertArrayHasKey( 'edd_download_purchase_form_quantity_field', $wp_filter['edd_purchase_link_top'][10] ); + } + + public function test_edd_after_price_option() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_variable_price_quantity_field', $wp_filter['edd_after_price_option'][10] ); + } + + public function test_edd_downloads_excerpt() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_downloads_default_excerpt', $wp_filter['edd_downloads_excerpt'][10] ); + } + + public function test_edd_downloads_content() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_downloads_default_content', $wp_filter['edd_downloads_content'][10] ); + } + + public function test_edd_purchase_form() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_show_purchase_form', $wp_filter['edd_purchase_form'][10] ); + } + + public function test_edd_purchase_form_after_user_info() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_user_info_fields', $wp_filter['edd_purchase_form_after_user_info'][10] ); + } + + public function test_edd_cc_form() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_get_cc_form', $wp_filter['edd_cc_form'][10] ); + } + + public function test_edd_after_cc_fields() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_default_cc_address_fields', $wp_filter['edd_after_cc_fields'][10] ); + } + + public function test_edd_purchase_form_register_fields() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_get_register_fields', $wp_filter['edd_purchase_form_register_fields'][10] ); + } + + public function test_edd_purchase_form_login_fields() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_get_login_fields', $wp_filter['edd_purchase_form_login_fields'][10] ); + } + + public function test_edd_payment_mode_select() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_payment_mode_select', $wp_filter['edd_payment_mode_select'][10] ); + } + + public function test_edd_purchase_form_before_cc_form() { + global $wp_filter; + // No actions connected to edd_purchase_form_before_cc_form by default + $this->assertTrue( true ); + //$this->assertArrayHasKey( 'edd_discount_field', $wp_filter['edd_purchase_form_before_cc_form'][10] ); + } + + public function test_edd_purchase_form_after_cc_form() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_checkout_tax_fields', $wp_filter['edd_purchase_form_after_cc_form'][999] ); + $this->assertArrayHasKey( 'edd_checkout_submit', $wp_filter['edd_purchase_form_after_cc_form'][9999] ); + } + + public function test_edd_purchase_form_before_submit() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_print_errors', $wp_filter['edd_purchase_form_before_submit'][10] ); + $this->assertArrayHasKey( 'edd_checkout_final_total', $wp_filter['edd_purchase_form_before_submit'][999] ); + } + + public function test_edd_checkout_form_top() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_discount_field', $wp_filter['edd_checkout_form_top'][-1] ); + $this->assertArrayHasKey( 'edd_show_payment_icons', $wp_filter['edd_checkout_form_top'][10] ); + } + + public function test_edd_empty_cart() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_empty_checkout_cart', $wp_filter['edd_cart_empty'][10] ); + } + + public function test_edd_add_to_cart() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_add_to_cart', $wp_filter['edd_add_to_cart'][10] ); + } + + public function test_edd_remove() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_remove_from_cart', $wp_filter['edd_remove'][10] ); + } + + public function test_edd_purchase_collection() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_collection_purchase', $wp_filter['edd_purchase_collection'][10] ); + } + + public function test_edd_format_amount_decimals() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_currency_decimal_filter', $wp_filter['edd_format_amount_decimals'][10] ); + } + + public function test_edd_paypal_cc_form() { + global $wp_filter; + $this->assertArrayHasKey( '__return_false', $wp_filter['edd_paypal_cc_form'][10] ); + } + + public function test_edd_gateway_paypal() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_paypal_purchase', $wp_filter['edd_gateway_paypal'][10] ); + } + + public function test_edd_verify_paypal_ipn() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_paypal_ipn', $wp_filter['edd_verify_paypal_ipn'][10] ); + } + + public function test_edd_paypal_web_accept() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_paypal_web_accept_and_cart', $wp_filter['edd_paypal_web_accept'][10] ); + } + + public function test_edd_paypal_link_transaction_id() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_paypal_link_transaction_id', $wp_filter['edd_payment_details_transaction_id-paypal'][10] ); + } + + public function test_edd_manual_cc_form() { + global $wp_filter; + $this->assertArrayHasKey( '__return_false', $wp_filter['edd_manual_cc_form'][10] ); + } + + public function test_edd_gateway_manual() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_manual_payment', $wp_filter['edd_gateway_manual'][10] ); + } + + public function test_edd_remove_cart_discount() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_remove_cart_discount', $wp_filter['edd_remove_cart_discount'][10] ); + } + + public function test_comments_clauses() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_hide_payment_notes', $wp_filter['pre_get_comments'][10] ); + $this->assertArrayHasKey( 'edd_hide_payment_notes_pre_41', $wp_filter['comments_clauses'][10] ); + } + + public function test_edd_update_payment_status() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_complete_purchase', $wp_filter['edd_update_payment_status'][100] ); + $this->assertArrayHasKey( 'edd_record_order_status_change', $wp_filter['edd_transition_order_status'][100] ); + } + + public function test_edd_cleanup_file_symlinks() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_cleanup_file_symlinks', $wp_filter['edd_cleanup_file_symlinks'][10] ); + } + + public function test_edd_download_price() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_format_amount', $wp_filter['edd_download_price'][10] ); + $this->assertArrayHasKey( 'edd_currency_filter', $wp_filter['edd_download_price'][20] ); + } + + public function test_admin_head() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_admin_downloads_icon', $wp_filter['admin_head'][10] ); + } + + public function test_enter_title_here() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_change_default_title', $wp_filter['enter_title_here'][10] ); + } + + public function test_post_updated_messages() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_updated_messages', $wp_filter['post_updated_messages'][10] ); + } + + public function test_bulk_post_updated_messages() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_bulk_updated_messages', $wp_filter['bulk_post_updated_messages'][10] ); + } + + public function test_load_edit_php() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_remove_post_types_order', $wp_filter['load-edit.php'][10] ); + } + + public function test_edd_settings_misc() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_append_no_cache_param', $wp_filter['edd_settings_misc'][-1] ); + } + + public function test_edd_admin_sale_notice() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_admin_email_notice', $wp_filter['edd_admin_sale_notice'][10] ); + } + + public function test_edd_email_settings() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_email_template_preview', $wp_filter['edd_purchase_receipt_email_settings'][10] ); + } + + public function test_edd_view_receipt() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_render_receipt_in_browser', $wp_filter['edd_view_receipt'][10] ); + } + + public function test_edd_email_links() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_resend_purchase_receipt', $wp_filter['edd_email_links'][10] ); + } + + public function test_edd_send_test_email() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_send_test_email', $wp_filter['edd_send_test_email'][10] ); + } + + public function test_edd_purchase() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_purchase_form', $wp_filter['edd_purchase'][10] ); + } + + public function test_edd_user_login() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_login_form', $wp_filter['edd_user_login'][10] ); + } + + public function test_edd_edit_user_profile() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_process_profile_editor_updates', $wp_filter['edd_edit_user_profile'][10] ); + } + + public function test_post_class() { + global $wp_filter; + $this->assertArrayHasKey( 'edd_responsive_download_post_class', $wp_filter['post_class'][999] ); + } + +} diff --git a/tests/tests-formatting.php b/tests/tests-formatting.php new file mode 100755 index 00000000000..6a98deb5f6a --- /dev/null +++ b/tests/tests-formatting.php @@ -0,0 +1,170 @@ +assertEquals( 0.00, edd_sanitize_amount( '' ) ); + } + + public function test_sanitize_amount_comma_thousands() { + $this->assertEquals( '20000.20', edd_sanitize_amount( '20,000.20' ) ); + } + + public function test_sanitize_amount_space_thousands() { + $this->assertEquals( '22000.20', edd_sanitize_amount( '22 000.20' ) ); + } + + public function test_sanitize_amount_incomplete_amount() { + $this->assertEquals( '20.20', edd_sanitize_amount( '20.2' ) ); + } + + public function test_sanitize_amount_amount_too_long() { + $this->assertEquals( '25.42', edd_sanitize_amount( '25.42221112993' ) ); + } + + public function test_sanitize_amount_includes_currency_usd() { + $this->assertEquals( '20.20', edd_sanitize_amount( '$20.2' ) ); + } + + public function test_sanitize_amount_includes_currency_gpb() { + $this->assertEquals( '10.00', edd_sanitize_amount( '£10' ) ); + } + + public function test_sanitize_amount_includes_currency_philippine() { + $this->assertEquals( '20.20', edd_sanitize_amount( '₱20.2' ) ); + } + + public function test_sanitize_amount_includes_currency_yen() { + $this->assertEquals( '2000.00', edd_sanitize_amount( '¥2000' ) ); + } + + public function test_sanitize_amount_includes_currency_doge() { + $this->assertEquals( '20.00', edd_sanitize_amount( 'Ð20' ) ); + } + + public function test_sanitize_amount_negative_amount() { + $this->assertEquals( -20.20, edd_sanitize_amount( '-20.2' ) ); + } + + public function test_sanitize_amount_negative_amount_with_currency() { + $this->assertEquals( -20.20, edd_sanitize_amount( '-$20.2' ) ); + } + + public function test_format_amount() { + + $this->assertEquals( '20,000.20', edd_format_amount( '20000.20' ) ); + + edd_update_option( 'thousands_separator', '.' ); + edd_update_option( 'decimal_separator', ',' ); + + $this->assertEquals( '20.000,20', edd_format_amount( '20000.20' ) ); + + edd_update_option( 'thousands_separator', ' ' ); + edd_update_option( 'decimal_separator', '.' ); + + $this->assertEquals( '20 000.20', edd_format_amount( '20000.20' ) ); + } + + public function test_format_amount_typed() { + $this->assertEquals( 20000.20, edd_format_amount( '20000.20', true, '', 'typed' ) ); + + edd_update_option( 'thousands_separator', '.' ); + edd_update_option( 'decimal_separator', ',' ); + + $this->assertEquals( 20000.20, edd_format_amount( '20000.20', true, '', 'typed' ) ); + + edd_update_option( 'thousands_separator', ' ' ); + edd_update_option( 'decimal_separator', '.' ); + + $this->assertEquals( 20000.20, edd_format_amount( '20000.20', true, '', 'typed' ) ); + } + + public function test_currency_filter() { + $this->assertEquals( '$20,000.20', edd_currency_filter( '20,000.20' ) ); + } + + public function test_currency_symbol() { + $this->assertEquals( '$', edd_currency_symbol( 'USD' ) ); + $this->assertEquals( '¥', edd_currency_symbol( 'JPY' ) ); + $this->assertEquals( 'DKK', edd_currency_symbol( 'DKK' ) ); + } + + public function test_separators() { + + $thousands_sep = edd_get_option( 'thousands_separator', ',' ); + $decimal_sep = edd_get_option( 'decimal_separator', '.' ); + + $this->assertEquals( ' ', $thousands_sep ); + $this->assertEquals( '.', $decimal_sep ); + + edd_update_option( 'thousands_separator', '.' ); + edd_update_option( 'decimal_separator', ',' ); + + $thousands_sep = edd_get_option( 'thousands_separator', ',' ); + $decimal_sep = edd_get_option( 'decimal_separator', '.' ); + + $this->assertEquals( '.', $thousands_sep ); + $this->assertEquals( ',', $decimal_sep ); + + edd_update_option( 'thousands_separator', ',' ); + edd_update_option( 'decimal_separator', '.' ); + + $thousands_sep = edd_get_option( 'thousands_separator', ',' ); + $decimal_sep = edd_get_option( 'decimal_separator', '.' ); + + $this->assertEquals( ',', $thousands_sep ); + $this->assertEquals( '.', $decimal_sep ); + + } + + public function test_decimal_filter() { + $initial_currency = edd_get_currency(); + + $this->assertEquals( 2, edd_currency_decimal_filter() ); + + edd_update_option( 'currency', 'RIAL' ); + $this->assertEquals( 0, edd_currency_decimal_filter() ); + + edd_update_option( 'currency', 'JPY' ); + $this->assertEquals( 0, edd_currency_decimal_filter() ); + + edd_update_option( 'currency', 'HUF' ); + $this->assertEquals( 0, edd_currency_decimal_filter() ); + + edd_update_option( 'currency', 'TWD' ); + $this->assertEquals( 0, edd_currency_decimal_filter() ); + + // Reset the option + edd_update_option( 'currency', $initial_currency ); + } + + public function test_decimal_filter_with_currency_passed_should_return_0() { + $this->assertSame( 0, edd_currency_decimal_filter( 2, 'RIAL' ) ); + + $this->assertSame( 0, edd_currency_decimal_filter( 2, 'HUF' ) ); + + $this->assertSame( 0, edd_currency_decimal_filter( 2, 'JPY' ) ); + } + + public function test_address_type_label_billing() { + $this->assertSame( 'Billing', edd_get_address_type_label( 'billing' ) ); + } + + public function test_address_type_label_default() { + $this->assertSame( 'Billing', edd_get_address_type_label() ); + } + + public function test_address_type_label_unregistered() { + $this->assertSame( 'shipping', edd_get_address_type_label( 'shipping' ) ); + } + + public function test_address_type_label_unregistered_two_words() { + $this->assertSame( 'test type', edd_get_address_type_label( 'test type' ) ); + } +} diff --git a/tests/tests-gateways.php b/tests/tests-gateways.php new file mode 100755 index 00000000000..3487c8c253a --- /dev/null +++ b/tests/tests-gateways.php @@ -0,0 +1,180 @@ +assertArrayHasKey( 'paypal', $out ); + $this->assertArrayHasKey( 'manual', $out ); + + $this->assertEquals( 'PayPal Standard', $out['paypal']['admin_label'] ); + $this->assertEquals( 'PayPal', $out['paypal']['checkout_label'] ); + + $this->assertEquals( 'Store Gateway', $out['manual']['admin_label'] ); + $this->assertEquals( 'Store Gateway', $out['manual']['checkout_label'] ); + } + + public function test_enabled_gateways() { + $this->assertEmpty( edd_get_enabled_payment_gateways() ); + + global $edd_options; + $edd_options['gateways']['paypal'] = '1'; + $edd_options['gateways']['manual'] = '1'; + + // Verify PayPal comes back as default/first when none is set + $this->assertTrue( empty( $edd_options['default_gateway'] ) ); + + $enabled_gateway_list = edd_get_enabled_payment_gateways( true ); + $first_gateway_id = current( array_keys( $enabled_gateway_list ) ); + $this->assertEquals( 'paypal', $first_gateway_id ); + + // Test when default is set to paypal + $edd_options['default_gateway'] = 'paypal'; + $enabled_gateway_list = edd_get_enabled_payment_gateways( true ); + $first_gateway_id = current( array_keys( $enabled_gateway_list ) ); + $this->assertEquals( 'paypal', $first_gateway_id ); + + // Test default is set to manual and we ask for it sorted + $edd_options['default_gateway'] = 'manual'; + $enabled_gateway_list = edd_get_enabled_payment_gateways( true ); + $first_gateway_id = current( array_keys( $enabled_gateway_list ) ); + $this->assertEquals( 'manual', $first_gateway_id ); + + // Test the call does not return it sorted when manual is default + $enabled_gateway_list = edd_get_enabled_payment_gateways(); + $first_gateway_id = current( array_keys( $enabled_gateway_list ) ); + $this->assertEquals( 'paypal', $first_gateway_id ); + + // Reset these so the rest of the tests don't fail + unset( $edd_options['default_gateway'], $edd_options['gateways']['paypal'], $edd_options['gateways']['manual'] ); + } + + public function test_is_gateway_active() { + $this->assertFalse( edd_is_gateway_active( 'paypal' ) ); + } + + public function test_default_gateway() { + + global $edd_options; + + $this->assertFalse( edd_get_default_gateway() ); + + $edd_options['gateways'] = array(); + $edd_options['gateways']['paypal'] = '1'; + $edd_options['gateways']['manual'] = '1'; + + $this->assertEquals( 'paypal', edd_get_default_gateway() ); + + $edd_options['default_gateway'] = 'paypal'; + $edd_options['gateways'] = array(); + $edd_options['gateways']['manual'] = '1'; + $edd_options['gateways']['stripe'] = '1'; + + // If we have Stripe in this install, 'stripe' is the default'; + $expected_result = class_exists( 'EDD_Stripe' ) ? 'stripe' : 'manual'; + $this->assertEquals( $expected_result, edd_get_default_gateway() ); + } + + public function test_get_gateway_admin_label() { + global $edd_options; + + $edd_options['gateways'] = array(); + $edd_options['gateways']['paypal'] = '1'; + $edd_options['gateways']['manual'] = '1'; + + $this->assertEquals( 'PayPal Standard', edd_get_gateway_admin_label( 'paypal' ) ); + $this->assertEquals( 'Store Gateway', edd_get_gateway_admin_label( 'manual' ) ); + } + + public function test_get_gateway_checkout_label() { + global $edd_options; + + $edd_options['gateways'] = array(); + $edd_options['gateways']['paypal'] = '1'; + $edd_options['gateways']['manual'] = '1'; + + $this->assertEquals( 'PayPal', edd_get_gateway_checkout_label( 'paypal' ) ); + $this->assertEquals( 'Store Gateway', edd_get_gateway_checkout_label( 'manual' ) ); + } + + public function test_buy_now_supported_single_gateway() { + global $edd_options; + + $edd_options['default_gateway'] = 'paypal'; + $edd_options['gateways'] = array(); + $edd_options['gateways']['paypal'] = '1'; + + $this->assertTrue( edd_shop_supports_buy_now() ); + } + + public function test_buy_now_supported_multiple_gateways() { + global $edd_options; + + $edd_options['default_gateway'] = 'paypal'; + $edd_options['gateways'] = array(); + $edd_options['gateways']['paypal'] = '1'; + $edd_options['gateways']['manual'] = '1'; + + $this->assertFalse( edd_shop_supports_buy_now() ); + } + + public function test_show_gateways() { + edd_empty_cart(); + $this->assertFalse( edd_show_gateways() ); + } + + public function test_chosen_gateway() { + $this->assertEquals( 'manual', edd_get_chosen_gateway() ); + } + + public function test_no_gateway_error_with_priv() { + global $edd_options, $current_user; + + $original_user = $current_user; + $current_user = wp_set_current_user( 1 ); + + $download = Helpers\EDD_Helper_Download::create_simple_download(); + edd_add_to_cart( $download->ID ); + + $edd_options['gateways'] = array(); + + edd_no_gateway_error(); + + $errors = edd_get_errors(); + + // Reset to origin + $current_user = $original_user; + + $this->assertArrayHasKey( 'no_gateways', $errors ); + $this->assertEquals( 'You must enable a payment gateway to use Easy Digital Downloads', $errors['no_gateways'] ); + } + + public function test_no_gateway_error_no_priv() { + global $edd_options, $current_user; + + $original_user = $current_user; + $current_user = wp_set_current_user( 0 ); + + $download = Helpers\EDD_Helper_Download::create_simple_download(); + edd_add_to_cart( $download->ID ); + + $edd_options['gateways'] = array(); + + edd_no_gateway_error(); + + $errors = edd_get_errors(); + + // Reset to origin + $current_user = $original_user; + + $this->assertArrayHasKey( 'no_gateways', $errors ); + $this->assertEquals( 'Your order cannot be completed at this time. Please try again or contact site support.', $errors['no_gateways'] ); + } + +} diff --git a/tests/tests-html-elements.php b/tests/tests-html-elements.php new file mode 100755 index 00000000000..50bafc7a468 --- /dev/null +++ b/tests/tests-html-elements.php @@ -0,0 +1,252 @@ +'; + $this->assertStringContainsString( $expected, EDD()->html->product_dropdown() ); + } + + /** + * @covers ::edd_parse_product_dropdown_value + */ + public function test_product_dropdown_value_parse_should_be_123_1() { + $expected = array( + 'download_id' => '123', + 'price_id' => '1', + ); + + $this->assertEqualSetsWithIndex( $expected, edd_parse_product_dropdown_value( '123_1' ) ); + } + + /** + * @covers ::edd_parse_product_dropdown_value + */ + public function test_product_dropdown_value_parse_should_be_123() { + $expected = array( + 'download_id' => '123', + 'price_id' => false, + ); + + $this->assertEqualSetsWithIndex( $expected, edd_parse_product_dropdown_value( '123' ) ); + $this->assertEqualSetsWithIndex( $expected, edd_parse_product_dropdown_value( 123 ) ); + } + + /** + * @covers ::edd_parse_product_dropdown_value + */ + public function test_product_dropdown_array_parse() { + $saved_values = array( 123, '155_1', '155_2', 99 ); + $expected = array( + array( + 'download_id' => '123', + 'price_id' => false, + ), + array( + 'download_id' => '155', + 'price_id' => '1', + ), + array( + 'download_id' => '155', + 'price_id' => '2', + ), + array( + 'download_id' => '99', + 'price_id' => false, + ), + ); + + $this->assertEqualSetsWithIndex( $expected, edd_parse_product_dropdown_values( $saved_values ) ); + } + + /** + * @covers ::edd_parse_product_dropdown_value + */ + public function test_product_dropdown_string_parse() { + $saved_values = '155'; + $expected = array( + array( + 'download_id' => '155', + 'price_id' => false, + ), + ); + + $this->assertEqualSetsWithIndex( $expected, edd_parse_product_dropdown_values( $saved_values ) ); + + $saved_values = '155_1'; + $expected = array( + array( + 'download_id' => '155', + 'price_id' => '1', + ), + ); + + $this->assertEqualSetsWithIndex( $expected, edd_parse_product_dropdown_values( $saved_values ) ); + } + + /** + * @covers ::customer_dropdown + */ + public function test_customer_dropdown() { + $expected = ''; + + $this->assertStringContainsString( $expected, EDD()->html->customer_dropdown() ); + } + + /** + * @covers ::discount_dropdown + */ + public function test_discount_dropdown() { + $meta = array( + 'name' => '50 Percent Off', + 'type' => 'percent', + 'amount' => '50', + 'code' => '50PERCENTOFF', + 'product_condition' => 'all', + ); + + edd_store_discount( $meta ); + + $expected = ''; + + $this->assertSame( $expected, EDD()->html->discount_dropdown() ); + } + + /** + * @covers ::category_dropdown + */ + public function test_category_dropdown() { + $expected = ''; + $this->assertEquals( $expected, EDD()->html->category_dropdown() ); + } + + /** + * @covers ::year_dropdown + */ + public function test_year_dropdown() { + $current_year = date( 'Y' ); + $expected = ''; + + $this->assertEquals( $expected, EDD()->html->year_dropdown() ); + } + + /** + * @covers ::year_dropdown + */ + public function test_year_dropdown_variable() { + $years_before = 5; + $years_after = 5; + $current_year = date( 'Y' ); + + $start_year = $current_year - $years_before; + $end_year = $current_year + $years_after; + + $expected = ''; + + $this->assertEquals( $expected, EDD()->html->year_dropdown( 'year', 0, $years_before, $years_after ) ); + + } + + /** + * @covers ::month_dropdown + */ + public function test_month_dropdown() { + $out = EDD()->html->month_dropdown(); + + $this->assertStringContainsString( '', $actual ); + } + + public function test_profile_shortcode() { + $actual = edd_profile_editor_shortcode( array() ); + + $this->assertIsString( $actual ); + $this->assertStringContainsString( '', edd_profile_editor_shortcode( array() ) ); + } + + public function test_profile_pending_single_load() { + add_filter( 'edd_allow_template_part_account_pending', 'edd_load_verification_template_once', 10, 1 ); + edd_set_user_to_pending( self::$user_id ); + + $actual = edd_profile_editor_shortcode( array() ); + + $this->assertStringContainsString( '